diff options
| author | Kamal Wickramanayake <kamal@inbox.lk> | 2026-07-03 19:02:36 +0530 |
|---|---|---|
| committer | Kamal Wickramanayake <kamal@inbox.lk> | 2026-07-03 19:02:36 +0530 |
| commit | aa122113ade36f02dc8fdbebdf1232b5c4b8742c (patch) | |
| tree | c345b83db6c769bf5e72a09e3f19d43431961695 /microservices/03-resource-server/src/main/java/com/example/resourceserver/security | |
| parent | b221a83ecef1dd7f9583d5107017d24e668205a6 (diff) | |
Microservice sample projects
Diffstat (limited to 'microservices/03-resource-server/src/main/java/com/example/resourceserver/security')
5 files changed, 246 insertions, 0 deletions
diff --git a/microservices/03-resource-server/src/main/java/com/example/resourceserver/security/RolesResponse.java b/microservices/03-resource-server/src/main/java/com/example/resourceserver/security/RolesResponse.java new file mode 100644 index 0000000..9d93729 --- /dev/null +++ b/microservices/03-resource-server/src/main/java/com/example/resourceserver/security/RolesResponse.java @@ -0,0 +1,7 @@ +package com.example.resourceserver.security; + +import java.util.Set; + +public record RolesResponse(Set<String> roles) { + +} diff --git a/microservices/03-resource-server/src/main/java/com/example/resourceserver/security/User.java b/microservices/03-resource-server/src/main/java/com/example/resourceserver/security/User.java new file mode 100644 index 0000000..8f84297 --- /dev/null +++ b/microservices/03-resource-server/src/main/java/com/example/resourceserver/security/User.java @@ -0,0 +1,110 @@ +package com.example.resourceserver.security; + +import java.time.LocalDateTime; +import java.util.Collection; +import java.util.Date; +import java.util.HashSet; +import java.util.Set; +import java.util.stream.Collectors; + +import org.hibernate.annotations.CreationTimestamp; +import org.hibernate.annotations.UpdateTimestamp; + +import jakarta.persistence.CollectionTable; +import jakarta.persistence.Column; +import jakarta.persistence.ElementCollection; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.Table; + +/* + @Table - Used to set the database table name explicitely. +*/ +@Entity +@Table(name = "user_account") +public class User { + + /* + * GenerationType.IDENTITY - Auto generate value based on database's native auto + * increment feature + */ + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(unique = true) + private String username; + + private String password; + + @Column(nullable = false) + private String description; + + /* + * @ElementCollection: Marks the field as a collection of basic types (Strings, + * Integers, etc.) or embeddable objects. + * + * @CollectionTable: Customizes the junction table details. If omitted, JPA + * generates a default table name combining the entity name and the field name + * (e.g., User_roles). + * + * @Column: Defines the column name for the String values inside the collection + * table. + * + * Fetch Type: By default, element collections use FetchType.LAZY. If you access + * the set outside of an active transaction or Hibernate session, it throws a + * LazyInitializationException. You can switch it to fetch = FetchType.EAGER to + * load the strings immediately alongside the main entity. + * + */ + @ElementCollection(fetch = FetchType.EAGER) + @CollectionTable(name = "user_roles", // Name of the separate collection table + joinColumns = @JoinColumn(name = "user_id") // Foreign key linking back to this entity + ) + @Column(name = "role_name") // Name of the column storing the actual String values + private Set<String> roles = new HashSet<>(); // e.g., ["ADMIN", "USER"] + + public String getPassword() { + return password; + } + + public String getUsername() { + return username; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public void setUsername(String username) { + this.username = username; + } + + public void setPassword(String password) { + this.password = password; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public Set<String> getRoles() { + return roles; + } + + public void setRoles(Set<String> roles) { + this.roles = roles; + } +}
\ No newline at end of file diff --git a/microservices/03-resource-server/src/main/java/com/example/resourceserver/security/UserRepository.java b/microservices/03-resource-server/src/main/java/com/example/resourceserver/security/UserRepository.java new file mode 100644 index 0000000..9bf9422 --- /dev/null +++ b/microservices/03-resource-server/src/main/java/com/example/resourceserver/security/UserRepository.java @@ -0,0 +1,11 @@ +package com.example.resourceserver.security; + +import java.util.Optional; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface UserRepository extends JpaRepository<User, Long> { + Optional<User> findByUsername(String username); +}
\ No newline at end of file diff --git a/microservices/03-resource-server/src/main/java/com/example/resourceserver/security/UserRestController.java b/microservices/03-resource-server/src/main/java/com/example/resourceserver/security/UserRestController.java new file mode 100644 index 0000000..0eb0d51 --- /dev/null +++ b/microservices/03-resource-server/src/main/java/com/example/resourceserver/security/UserRestController.java @@ -0,0 +1,71 @@ +package com.example.resourceserver.security; + +import java.security.Principal; +import java.util.Collection; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; + +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.security.oauth2.jwt.Jwt; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/api/security/users") +public class UserRestController { + private UserService userService; + + public UserRestController(UserService userService) { + this.userService = userService; + } + + @GetMapping("/{username}/roles") + public ResponseEntity<RolesResponse> getRoles(@PathVariable String username) { + Optional<User> user = userService.findByUsername(username); + + if (user.isEmpty()) { + return ResponseEntity.ok(new RolesResponse(Set.of())); + } + + return ResponseEntity.ok(new RolesResponse(user.get().getRoles())); + } + + @GetMapping("/test") + public String test(Principal principal) { + return principal.toString(); + } + + @GetMapping("/test-username") + public String testName(Principal principal) { + return principal.getName(); + } + + // Get roles using the Authentication object + @GetMapping("/test-roles-auth") + public Collection<String> getRoles(Authentication authentication) { + return authentication.getAuthorities().stream() + .map(GrantedAuthority::getAuthority) + .collect(Collectors.toList()); + } + + // Inspect raw JWT claims directly + @GetMapping("/test-roles-jwt") + public Map<String, Object> getClaims(@AuthenticationPrincipal Jwt jwt) { + return jwt.getClaims(); // Extract custom JSON fields containing roles + } + + /* Not very secure because unverifiable headers can be introduced by unwanted parties */ + @GetMapping("/test-roles-headers") + public String getRolesFromHeaders(@RequestHeader("X-User-Name") String username, + @RequestHeader("X-User-Roles") String roles) { + return "Roles: " + roles + ", username: " + username ; + } +} diff --git a/microservices/03-resource-server/src/main/java/com/example/resourceserver/security/UserService.java b/microservices/03-resource-server/src/main/java/com/example/resourceserver/security/UserService.java new file mode 100644 index 0000000..1f6ddac --- /dev/null +++ b/microservices/03-resource-server/src/main/java/com/example/resourceserver/security/UserService.java @@ -0,0 +1,47 @@ +package com.example.resourceserver.security; + +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 jakarta.transaction.Transactional; + +@Service +public class UserService { + + @Autowired + private UserRepository userRepository; + + public Optional<User> findByUsername(String username) { + return userRepository.findByUsername(username); + } + + public Optional<User> findById(Long id) { + return userRepository.findById(id); + } + + public List<User> findAll() { + return userRepository.findAll(Sort.by(Sort.Direction.ASC, "username")); + } + + @PreAuthorize("hasRole('ADMIN')") + @Transactional + 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. + } +} |
