Spring Security: Complete Architecture Guide
Let me break down Spring Security's architecture from a bird's eye view to the implementation details in your codebase.
๐ Bird's Eye View: Spring Security Architectureโ
HTTP Request
โ
Filter Chain (Servlet Filters)
โโ CORS Filter
โโ JWT Authentication Filter โ Your custom filter
โโ UsernamePasswordAuthenticationFilter
โโ Exception Translation Filter
โโ Authorization Filter
โ
SecurityContext (Thread-Local Storage)
โโ Authentication Object
โโ Principal (UserDetails)
โโ Credentials (password)
โโ Authorities (roles/permissions)
โ
Your Controller
โโ Access SecurityContextHolder to get current user
๐ Core Concepts & Relationshipsโ
1. UserDetails Interfaceโ
Location: org.springframework.security.core.userdetails.UserDetails
public interface UserDetails {
String getUsername(); // Unique identifier
String getPassword(); // Credentials
Collection<? extends GrantedAuthority> getAuthorities(); // Roles/permissions
boolean isAccountNonExpired();
boolean isAccountNonLocked();
boolean isCredentialsNonExpired();
boolean isEnabled();
}
Purpose: Represents the user information that Spring Security needs for authentication and authorization.
In Your Code (User.java:15):
@Entity
public class User implements UserDetails {
private Long id;
private String email;
private String password;
private Set<Role> roles;
@Override
public String getUsername() {
return email; // Your email is the username
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return roles.stream()
.map(role -> new SimpleGrantedAuthority("ROLE_" + role.name()))
.collect(Collectors.toList());
}
// ... other UserDetails methods
}
2. Authentication Interfaceโ
Location: org.springframework.security.core.Authentication
public interface Authentication extends Principal {
Collection<? extends GrantedAuthority> getAuthorities(); // User roles
Object getCredentials(); // Password (usually null after auth)
Object getDetails(); // Additional details (IP, session, etc.)
Object getPrincipal(); // The user (UserDetails object)
boolean isAuthenticated();
void setAuthenticated(boolean isAuthenticated);
}
Purpose: Represents an authentication request or an authenticated user.
Common Implementation: UsernamePasswordAuthenticationToken
In Your Code (JwtAuthenticationFilter.java:64-66):
UsernamePasswordAuthenticationToken authToken =
new UsernamePasswordAuthenticationToken(
userDetails, // โ Principal (your User object)
null, // โ Credentials (null after authentication)
userDetails.getAuthorities() // โ Authorities (roles)
);
3. Principal Interfaceโ
Location: java.security.Principal
public interface Principal {
String getName(); // Returns the name of the principal
}
Purpose: Generic Java interface representing an identity. In Spring Security, the principal is typically the UserDetails object.
Relationship:
Authentication
โโ getPrincipal() โ Object (usually UserDetails)
โโ In your case: User object (which implements UserDetails)
Extracting Principal:
// From Authentication object
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
Object principal = auth.getPrincipal();
// principal can be:
// 1. UserDetails (your User object) - when authenticated
// 2. String (username) - when not fully authenticated
// 3. AnonymousAuthenticationToken - for anonymous users
๐ Complete Authentication Flowโ
Flow Diagram:โ
1. Client Request
โโ Headers: { Authorization: "Bearer eyJhbGc..." }
โ
2. Servlet Container
โโ Creates HttpServletRequest
โ
3. Filter Chain Starts
โโ CORS Filter (handles cross-origin)
โโ YOUR JWT Filter โ We are here!
โ โ
โ โโ Extract JWT from header
โ โโ Validate token
โ โโ Extract username from token
โ โโ Load UserDetails from database
โ โโ Create Authentication object
โ โโ Store in SecurityContext
โ
โโ Other Security Filters
โโ Authorization Filter (checks permissions)
โ
4. SecurityContext (Thread-Local)
โโ Contains Authentication object
โโ principal: User object
โโ authorities: [ROLE_USER, ROLE_ADMIN]
โ
5. Your Controller
โโ Can access current user via SecurityContextHolder
โ
6. Response
๐งต Thread-Local Security Contextโ
What is ThreadLocal?โ
Spring Security uses ThreadLocal to store the authentication for each HTTP request thread.
// Simplified version of SecurityContextHolder
public class SecurityContextHolder {
private static ThreadLocal<SecurityContext> contextHolder = new ThreadLocal<>();
public static SecurityContext getContext() {
return contextHolder.get(); // Gets context for CURRENT thread only
}
public static void setContext(SecurityContext context) {
contextHolder.set(context); // Sets for CURRENT thread only
}
}
Why ThreadLocal?
- Each HTTP request runs in its own thread
- ThreadLocal ensures each thread has its own isolated SecurityContext
- Request A's authentication cannot interfere with Request B's authentication
๐ Step-by-Step: Your JWT Authentication Flowโ
Let's trace a real request through your system:
Request: GET /api/cart with Authorization: Bearer <jwt_token>
Step 1: JwtAuthenticationFilter Interceptsโ
File: JwtAuthenticationFilter.java:34-77
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
// 1. Extract JWT token from header
final String requestTokenHeader = request.getHeader("Authorization");
String jwtToken = null;
String username = null;
if (requestTokenHeader != null && requestTokenHeader.startsWith("Bearer ")) {
jwtToken = requestTokenHeader.substring(7); // Remove "Bearer "
username = jwtUtil.getUsernameFromToken(jwtToken); // Extract email from JWT
}
// 2. Check if already authenticated
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
// 3. Load user from database
UserDetails userDetails = userService.loadUserByUsername(username);
// โ This returns your User object (which implements UserDetails)
// 4. Validate token
if (jwtUtil.validateToken(jwtToken, userDetails)) {
// 5. Create Authentication object
UsernamePasswordAuthenticationToken authToken =
new UsernamePasswordAuthenticationToken(
userDetails, // โ Principal (your User)
null, // โ Credentials (null for JWT)
userDetails.getAuthorities() // โ Roles [ROLE_USER]
);
// 6. Add request details (IP, session ID, etc.)
authToken.setDetails(
new WebAuthenticationDetailsSource().buildDetails(request)
);
// 7. Store in SecurityContext (ThreadLocal)
SecurityContextHolder.getContext().setAuthentication(authToken);
// โ
// This is now available
// in your controller!
}
}
// 8. Continue filter chain
filterChain.doFilter(request, response);
}
Step 2: Authorization Filter Checks Permissionsโ
File: SecurityConfig.java:77
.requestMatchers(AntPathRequestMatcher.antMatcher("/api/cart/**"))
.hasAnyRole("USER", "ADMIN") // โ Checks authorities in Authentication
Spring Security automatically:
- Gets
AuthenticationfromSecurityContext - Calls
authentication.getAuthorities() - Checks if authorities contain
ROLE_USERorROLE_ADMIN - Allows/denies access
Step 3: Your Controller Accesses Current Userโ
@GetMapping("/cart")
public ResponseEntity<CartDto> getCart(Authentication authentication) {
// โ Spring automatically injects this!
// Extract principal
Object principal = authentication.getPrincipal();
if (principal instanceof User user) {
Long userId = user.getId(); // Direct access to your User entity
// ... fetch cart ...
}
}
๐๏ธ Class Hierarchy & Interface Relationshipsโ
java.security.Principal (interface)
โ
org.springframework.security.core.Authentication (interface)
โ
AbstractAuthenticationToken (abstract class)
โ
UsernamePasswordAuthenticationToken (class) โ You use this
โโ principal: Object (your User object)
โโ credentials: Object (password or null)
โโ authorities: Collection<GrantedAuthority>
org.springframework.security.core.userdetails.UserDetails (interface)
โ
com.automotive.ecommerce.business.entity.User (your class) โ Your implementation
โโ implements all UserDetails methods
โโ adds custom fields (id, email, firstName, etc.)
๐ฏ How UserDetails is Extracted from Authenticationโ
Method 1: Direct Cast (Type-Safe)โ
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
Object principal = auth.getPrincipal();
if (principal instanceof User user) {
// โ
Type-safe access to your User entity
Long userId = user.getId();
String email = user.getEmail();
}
Method 2: Generic UserDetails Castโ
if (principal instanceof UserDetails userDetails) {
// โ
Access to UserDetails methods only
String username = userDetails.getUsername();
Collection<? extends GrantedAuthority> authorities = userDetails.getAuthorities();
// โ Cannot access User-specific fields like getId()
// Long userId = userDetails.getId(); // Compile error!
}
Method 3: Spring's @AuthenticationPrincipalโ
@GetMapping("/cart")
public ResponseEntity<CartDto> getCart(@AuthenticationPrincipal User user) {
// โ Spring automatically extracts and casts
Long userId = user.getId(); // Direct access!
}
๐ SecurityContext & SecurityContextHolderโ
Structure:โ
SecurityContextHolder (Thread-Local Storage)
โโ SecurityContext
โโ Authentication
โโ principal: UserDetails (your User)
โโ credentials: null
โโ authorities: [ROLE_USER, ROLE_ADMIN]
โโ details: WebAuthenticationDetails (IP, session)
โโ authenticated: true
Accessing Current User Anywhere:โ
// In any service, controller, or component
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth != null && auth.getPrincipal() instanceof User user) {
System.out.println("Current user: " + user.getEmail());
System.out.println("User ID: " + user.getId());
}
๐ Summary: The Big Pictureโ
| Concept | Purpose | Example in Your Code |
|---|---|---|
| UserDetails | Contract for user information Spring Security needs | Your User entity implements this |
| Authentication | Represents an authenticated user with credentials & authorities | UsernamePasswordAuthenticationToken |
| Principal | The identity (usually UserDetails) | Your User object stored in Authentication |
| SecurityContext | Container for Authentication | Thread-local storage per request |
| SecurityContextHolder | Static accessor for SecurityContext | Access current user anywhere |
| Filter Chain | Intercepts requests to authenticate | Your JwtAuthenticationFilter |
| AuthenticationManager | Validates credentials | Used in login (AuthController) |
| UserDetailsService | Loads user from database | Your UserService.loadUserByUsername() |
๐ Key Takeawaysโ
- UserDetails = Your User entity with Spring Security requirements
- Authentication = Container holding the authenticated user (principal) + authorities
- Principal = The "who" (your User object) inside Authentication
- SecurityContext = Thread-local storage for Authentication (one per request thread)
- Filter Chain = Where authentication happens before reaching your controller
- JWT Flow = Extract token โ Load user โ Create Authentication โ Store in SecurityContext
Next Steps: Now that you understand Spring Security's architecture, explore how it integrates with your backend scenarios in the Backend Scenarios guide.