diff options
| author | Kamal Wickramanayake <kamal@inbox.lk> | 2026-06-20 22:02:32 +0530 |
|---|---|---|
| committer | Kamal Wickramanayake <kamal@inbox.lk> | 2026-06-20 22:02:32 +0530 |
| commit | 69b18c1fe1d7d876c86de850b716de396877ad9d (patch) | |
| tree | 832330933dccb4848ba0ff89a9f4f5953b3da3e7 | |
| parent | 59c2d3502259b338c36991ced8dc25d8dc9713b1 (diff) | |
A list of users are shown. Added error pages.
12 files changed, 245 insertions, 6 deletions
diff --git a/spring-boot/10-role-based-security/README b/spring-boot/10-role-based-security/README index 9a79809..95aaa8a 100644 --- a/spring-boot/10-role-based-security/README +++ b/spring-boot/10-role-based-security/README @@ -17,4 +17,13 @@ How is role based security enabled? 3. Controllers and services can be annotated with @PreAuthorize(). Example: ContractController.java -4. In Thymeleaf templates, sec:authorize() works with role names. Example: layout/main.html
\ No newline at end of file +4. In Thymeleaf templates, sec:authorize() works with role names. Example: layout/main.html + + +What other modifications are found in this sample app? + +1. List of users are shown in a table. UserRepository extends JPARepository class that supports paging and sorting. + +2. The edit link for each user can be used to show the update form. + +3. Error pages have been added. See templates/error.html and templates/error/403.html .
\ No newline at end of file diff --git a/spring-boot/10-role-based-security/src/main/java/com/example/spring/security/controllers/UserController.java b/spring-boot/10-role-based-security/src/main/java/com/example/spring/security/controllers/UserController.java new file mode 100644 index 0000000..d9f5fdd --- /dev/null +++ b/spring-boot/10-role-based-security/src/main/java/com/example/spring/security/controllers/UserController.java @@ -0,0 +1,67 @@ +package com.example.spring.security.controllers; + +import java.util.List; +import java.util.Optional; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.validation.BindingResult; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; + +import com.example.spring.security.entity.User; +import com.example.spring.security.repository.UserRepository; +import com.example.spring.security.service.UserService; + +import jakarta.validation.Valid; + +@Controller +public class UserController { + + @Autowired + private UserService userService; + + @GetMapping("/security/users") + @PreAuthorize("hasRole('ADMIN')") + public String showUsers(Model model) { + List<User> users = userService.findAll(); + + model.addAttribute("users", users); + + return "security/users"; + } + + @GetMapping("/security/user/update/{id}") + @PreAuthorize("hasRole('ADMIN')") + public String showEditForm(@PathVariable Long id, Model model) { + Optional<User> userOpt = userService.findById(id); + + User user = userOpt.get(); + + UserForm form = new UserForm(); + form.setId(user.getId()); + form.setUsername(user.getUsername()); + form.setDescription(user.getDescription()); + + model.addAttribute("userForm", form); + + return "security/user-form"; + } + + @PostMapping("/security/user/update") + @PreAuthorize("hasRole('ADMIN')") + public String updateUser(@Valid @ModelAttribute UserForm userForm, BindingResult bindingResult, + Model model) { + if (bindingResult.hasErrors()) { + return "security/user-form"; + } + + userService.updateUser(userForm.getId(), userForm.getDescription()); + + return "redirect:/security/users"; + } +} diff --git a/spring-boot/10-role-based-security/src/main/java/com/example/spring/security/controllers/UserForm.java b/spring-boot/10-role-based-security/src/main/java/com/example/spring/security/controllers/UserForm.java new file mode 100644 index 0000000..8df4932 --- /dev/null +++ b/spring-boot/10-role-based-security/src/main/java/com/example/spring/security/controllers/UserForm.java @@ -0,0 +1,27 @@ +package com.example.spring.security.controllers; + +public class UserForm { + + private Long id; + private String username; + private String description; + + public Long getId() { + return id; + } + public void setId(Long id) { + this.id = id; + } + public String getUsername() { + return username; + } + public void setUsername(String username) { + this.username = username; + } + public String getDescription() { + return description; + } + public void setDescription(String description) { + this.description = description; + } +} diff --git a/spring-boot/10-role-based-security/src/main/java/com/example/spring/security/repository/UserRepository.java b/spring-boot/10-role-based-security/src/main/java/com/example/spring/security/repository/UserRepository.java index f67039c..24cf579 100644 --- a/spring-boot/10-role-based-security/src/main/java/com/example/spring/security/repository/UserRepository.java +++ b/spring-boot/10-role-based-security/src/main/java/com/example/spring/security/repository/UserRepository.java @@ -2,12 +2,12 @@ package com.example.spring.security.repository; import java.util.Optional; -import org.springframework.data.repository.ListCrudRepository; +import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; import com.example.spring.security.entity.User; @Repository -public interface UserRepository extends ListCrudRepository<User, Long> { +public interface UserRepository extends JpaRepository<User, Long> { Optional<User> findByUsername(String username); -} +}
\ No newline at end of file diff --git a/spring-boot/10-role-based-security/src/main/java/com/example/spring/security/service/CustomUserDetailsService.java b/spring-boot/10-role-based-security/src/main/java/com/example/spring/security/service/CustomUserDetailsService.java index cdced4a..1795109 100644 --- a/spring-boot/10-role-based-security/src/main/java/com/example/spring/security/service/CustomUserDetailsService.java +++ b/spring-boot/10-role-based-security/src/main/java/com/example/spring/security/service/CustomUserDetailsService.java @@ -21,5 +21,4 @@ public class CustomUserDetailsService implements UserDetailsService { return userRepository.findByUsername(username) .orElseThrow(() -> new UsernameNotFoundException("User not found")); } - }
\ No newline at end of file diff --git a/spring-boot/10-role-based-security/src/main/java/com/example/spring/security/service/UserService.java b/spring-boot/10-role-based-security/src/main/java/com/example/spring/security/service/UserService.java new file mode 100644 index 0000000..a4e18be --- /dev/null +++ b/spring-boot/10-role-based-security/src/main/java/com/example/spring/security/service/UserService.java @@ -0,0 +1,12 @@ +package com.example.spring.security.service; + +import java.util.List; +import java.util.Optional; + +import com.example.spring.security.entity.User; + +public interface UserService { + public Optional<User> findById(Long id); + public List<User> findAll(); + public void updateUser(Long userId, String description); +} diff --git a/spring-boot/10-role-based-security/src/main/java/com/example/spring/security/service/UserServiceImpl.java b/spring-boot/10-role-based-security/src/main/java/com/example/spring/security/service/UserServiceImpl.java new file mode 100644 index 0000000..0d44d21 --- /dev/null +++ b/spring-boot/10-role-based-security/src/main/java/com/example/spring/security/service/UserServiceImpl.java @@ -0,0 +1,49 @@ +package com.example.spring.security.service; + +import java.util.List; +import java.util.Optional; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Sort; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.stereotype.Service; + +import com.example.spring.security.entity.User; +import com.example.spring.security.repository.UserRepository; + +import jakarta.transaction.Transactional; + +@Service +public class UserServiceImpl implements UserService { + + @Autowired + private UserRepository userRepository; + + @Override + public Optional<User> findById(Long id) { + return userRepository.findById(id); + } + + @Override + public List<User> findAll() { + return userRepository.findAll(Sort.by(Sort.Direction.ASC, "username")); + } + + @PreAuthorize("hasRole('ADMIN')") + @Transactional + @Override + public void updateUser(Long id, String description) { + // Do whatever the work needed. + // ... + + Optional<User> userOpt = userRepository.findById(id); + + User user = userOpt.get(); + + user.setDescription(description); + + // NO userRepository.save(user) IS ACTUALLY REQUIRED HERE since the method + // has been annotated with @Transactional! + // Transaction commits -> Hibernate issues the SQL UPDATE. + } +} diff --git a/spring-boot/10-role-based-security/src/main/resources/templates/error.html b/spring-boot/10-role-based-security/src/main/resources/templates/error.html new file mode 100644 index 0000000..ea5a329 --- /dev/null +++ b/spring-boot/10-role-based-security/src/main/resources/templates/error.html @@ -0,0 +1,12 @@ +<!DOCTYPE html> +<html xmlns:th="http://thymeleaf.org"> +<head> + <title>Error Occurred</title> +</head> +<body> + <h1>An error occurred!</h1> + <p>Status: <span th:text="${status}">500</span></p> + <p>Message: <span th:text="${message}">Error Message</span></p> + <p>Timestamp: <span th:text="${timestamp}">Date</span></p> +</body> +</html>
\ No newline at end of file diff --git a/spring-boot/10-role-based-security/src/main/resources/templates/error/403.html b/spring-boot/10-role-based-security/src/main/resources/templates/error/403.html new file mode 100644 index 0000000..c1db2c0 --- /dev/null +++ b/spring-boot/10-role-based-security/src/main/resources/templates/error/403.html @@ -0,0 +1,12 @@ +<!DOCTYPE html> +<html xmlns:th="http://thymeleaf.org"> +<head> + <title>Error Occurred</title> +</head> +<body> + <h1>You have no permission!</h1> + <p>Status: <span th:text="${status}">500</span></p> + <p>Message: <span th:text="${message}">Error Message</span></p> + <p>Timestamp: <span th:text="${timestamp}">Date</span></p> +</body> +</html>
\ No newline at end of file diff --git a/spring-boot/10-role-based-security/src/main/resources/templates/layout/main.html b/spring-boot/10-role-based-security/src/main/resources/templates/layout/main.html index df96fc5..0b20361 100644 --- a/spring-boot/10-role-based-security/src/main/resources/templates/layout/main.html +++ b/spring-boot/10-role-based-security/src/main/resources/templates/layout/main.html @@ -18,7 +18,7 @@ <ul> <li><a th:href="@{/}">Home</a></li> <li><a th:href="@{/contact}">Contact Us</a></li> - <li sec:authorize="hasRole('ADMIN')"><a th:href="@{/admin}">Admin</a></li> + <li sec:authorize="hasRole('ADMIN')"><a th:href="@{/security/users}">Users</a></li> <li sec:authorize="isAuthenticated()"><a th:href="@{/profile/{name}(name=${#authentication.name})}">Profile</a></li> <li sec:authorize="isAuthenticated()"><a th:href="@{/logout}">Logout</a></li> </ul> diff --git a/spring-boot/10-role-based-security/src/main/resources/templates/security/user-form.html b/spring-boot/10-role-based-security/src/main/resources/templates/security/user-form.html new file mode 100644 index 0000000..a27ae7d --- /dev/null +++ b/spring-boot/10-role-based-security/src/main/resources/templates/security/user-form.html @@ -0,0 +1,22 @@ +<!DOCTYPE HTML> +<html xmlns:th="http://www.thymeleaf.org" + th:replace="~{layout/main :: layout(title='User', content=~{::main})}"> + +<body> + <main> + <form th:action="@{/security/user/update}" th:object="${userForm}" method="post"> + <!-- Hidden field binds the "id" property seamlessly --> + <input type="hidden" th:field="*{id}" /> + + <div class="form-field-container"> + <label>Description</label> + <textarea th:field="*{description}" placeholder="Description"></textarea> + <div class="validation-error" th:if="${#fields.hasErrors('description')}" th:errors="*{description}"></div> + </div> + + <button type="submit">Save</button> + </form> + </main> +</body> + +</html>
\ No newline at end of file diff --git a/spring-boot/10-role-based-security/src/main/resources/templates/security/users.html b/spring-boot/10-role-based-security/src/main/resources/templates/security/users.html new file mode 100644 index 0000000..81752c8 --- /dev/null +++ b/spring-boot/10-role-based-security/src/main/resources/templates/security/users.html @@ -0,0 +1,30 @@ +<!DOCTYPE HTML> +<html xmlns:th="http://www.thymeleaf.org" + th:replace="~{layout/main :: layout(title='Users', content=~{::main})}"> + +<body> + <main> + <table> + <thead> + <tr> + <th>ID</th> + <th>Username</th> + <th>Description</th> + <th>Edit</th> + </tr> + </thead> + <tbody> + <!-- th:each loops over the "userss" list sent by the controller --> + <tr th:each="user : ${users}"> + <!-- th:text updates the cell contents dynamically --> + <td th:text="${user.id}">1</td> + <td th:text="${user.username}">Username</td> + <td th:text="${user.description}">User description</td> + <td><a th:href="@{/security/user/update/{id}(id=${user.id})}">Edit</a></td> + </tr> + </tbody> + </table> + </main> +</body> + +</html>
\ No newline at end of file |
