Cheat Sheet · Spring Boot 3.x · Security 6.x
PrincipalUserDetails (who is the user)CredentialsPassword (null after auth)AuthoritiesGranted roles/permissionsSTATELESSNever create/use HTTP sessionALWAYSAlways create sessionIF_REQUIREDCreate only if required (default)NEVERNever create, but will use existing<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency>
SecurityContextHolderStatic class holding SecurityContext. Thread-local. Call .clearContext() after request.SecurityContextContainer for Authentication objectAuthenticationHolds principal, credentials, authorities. Core interface.AuthenticationManagerInterface. ProviderManager is default impl — delegates to providers.AuthenticationProviderDoes actual auth. DaoAuthenticationProvider for username/pass.UserDetailsServiceloadUserByUsername(). Only for loading user data from DB.UserDetailsSpring's user interface: username, password, authorities, flags.PasswordEncoderBCryptPasswordEncoder — encodes & verifies. Adds random salt.GrantedAuthorityRepresents ROLE_ADMIN, ROLE_USER etc. hasRole("ADMIN") → checks ROLE_ADMIN.// 2-arg = NOT authenticated (pre-auth, for login attempt) new UsernamePasswordAuthenticationToken(principal, credentials); // 3-arg = IS authenticated (post-auth, set in SecurityContext) new UsernamePasswordAuthenticationToken( userDetails, // principal null, // credentials — null for JWT (token IS the proof) userDetails.getAuthorities() // roles );
@Component
public class CustomAuthProvider
implements AuthenticationProvider {
@Override
public Authentication authenticate(
Authentication auth) throws AuthenticationException {
String user = auth.getName();
String pass = auth.getCredentials().toString();
// Throw BadCredentialsException if invalid
return new UsernamePasswordAuthenticationToken(
user, null, List.of(new SimpleGrantedAuthority("ROLE_USER")));
}
@Override
public boolean supports(Class<?> auth) {
return UsernamePasswordAuthenticationToken.class.isAssignableFrom(auth);
}
}@Service
public class CustomUserDetailsService
implements UserDetailsService {
@Autowired
private UserRepository userRepository;
@Override
public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException {
User user = userRepository.findByUsername(username)
.orElseThrow(() -> new UsernameNotFoundException(
"User not found: " + username));
return User.builder()
.username(user.getUsername())
.password(user.getPassword()) // BCrypt in DB
.roles(user.getRole()) // "ADMIN" → ROLE_ADMIN
.build();
}
}SecurityContextPersistenceFilterLoads SecurityContext from session at start, saves at end
CorsFilterHandles CORS preflight and response headers
CsrfFilterValidates CSRF token (disable for stateless JWT APIs)
LogoutFilterHandles /logout. Clears SecurityContext.
UsernamePasswordAuthenticationFilterProcesses /login form POST. Add JWT filter BEFORE this.
BasicAuthenticationFilterProcesses Authorization: Basic header
BearerTokenAuthenticationFilterOAuth2 — extracts Bearer token from header
JwtAuthenticationFilter*Your custom filter. Extends OncePerRequestFilter.
ExceptionTranslationFilterConverts AccessDeniedException / AuthenticationException to HTTP responses
AuthorizationFilterWas FilterSecurityInterceptor. Final authorization check.
doFilterInternal()doFilter()@Configuration
@EnableWebSecurity
@EnableMethodSecurity // enables @PreAuthorize etc.
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf.disable())
.cors(cors -> cors.configurationSource(corsConfigurationSource()))
.sessionManagement(s -> s.sessionCreationPolicy(STATELESS))
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/auth/**").permitAll()
.requestMatchers("/api/admin/**").hasRole("ADMIN")
.requestMatchers("/api/users/**").hasAnyRole("USER","ADMIN")
.anyRequest().authenticated()
)
.addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class)
.exceptionHandling(ex -> ex
.authenticationEntryPoint(customEntryPoint) // 401
.accessDeniedHandler(customAccessDeniedHandler) // 403
);
return http.build();
}
@Bean public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean public AuthenticationManager authenticationManager(
AuthenticationConfiguration config) throws Exception {
return config.getAuthenticationManager();
}
@Bean public DaoAuthenticationProvider authenticationProvider() {
var p = new DaoAuthenticationProvider();
p.setUserDetailsService(userDetailsService);
p.setPasswordEncoder(passwordEncoder());
return p;
}
}{"alg":"HS256","typ":"JWT"}{"sub":"alice","role":"ADMIN",
"iat":1700000000,"exp":1700003600,
"iss":"myapp","email":"a@a.com"}subSubject (username/userId)iatIssued At (Unix timestamp)expExpiration TimeissIssueraudAudiencejtiJWT ID (for revocation)<!-- groupId: io.jsonwebtoken --> <dependency>jjwt-api</dependency> <dependency>jjwt-impl</dependency> <dependency>jjwt-jackson</dependency>
@Component
public class JwtUtil {
@Value("${jwt.secret}") private String secretKey;
@Value("${jwt.expiration}") private long expirationMs;
private Key getSigningKey() {
byte[] keyBytes = Decoders.BASE64.decode(secretKey);
return Keys.hmacShaKeyFor(keyBytes);
}
public String generateToken(UserDetails userDetails) {
Map<String, Object> claims = new HashMap<>();
claims.put("roles", userDetails.getAuthorities());
return Jwts.builder()
.setClaims(claims)
.setSubject(userDetails.getUsername())
.setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + expirationMs))
.signWith(getSigningKey(), SignatureAlgorithm.HS256)
.compact();
}
private Claims extractAllClaims(String token) {
return Jwts.parserBuilder()
.setSigningKey(getSigningKey()).build()
.parseClaimsJws(token).getBody();
}
// Generic claim extractor
public <T> T extractClaim(String token, Function<Claims,T> resolver) {
return resolver.apply(extractAllClaims(token));
}
public String extractUsername(String token) {
return extractClaim(token, Claims::getSubject);
}
public Date extractExpiration(String token) {
return extractClaim(token, Claims::getExpiration);
}
private boolean isTokenExpired(String token) {
return extractExpiration(token).before(new Date());
}
public boolean validateToken(String token, UserDetails ud) {
return extractUsername(token).equals(ud.getUsername())
&& !isTokenExpired(token);
}
}@Component
public class JwtAuthenticationFilter
extends OncePerRequestFilter {
@Autowired private JwtUtil jwtUtil;
@Autowired private UserDetailsService userDetailsService;
@Override
protected void doFilterInternal(
HttpServletRequest req,
HttpServletResponse res,
FilterChain chain) throws ServletException, IOException {
String authHeader = req.getHeader("Authorization");
String jwt = null, username = null;
if (authHeader != null && authHeader.startsWith("Bearer ")) {
jwt = authHeader.substring(7);
try {
username = jwtUtil.extractUsername(jwt);
} catch (ExpiredJwtException e) {
res.sendError(SC_UNAUTHORIZED, "Token expired");
return;
} catch (MalformedJwtException e) {
res.sendError(SC_UNAUTHORIZED, "Invalid token");
return;
}
}
if (username != null &&
SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails ud = userDetailsService.loadUserByUsername(username);
if (jwtUtil.validateToken(jwt, ud)) {
// 3-arg = authenticated
var authToken = new UsernamePasswordAuthenticationToken(
ud, null, ud.getAuthorities());
authToken.setDetails(
new WebAuthenticationDetailsSource().buildDetails(req));
SecurityContextHolder.getContext().setAuthentication(authToken);
}
}
chain.doFilter(req, res);
}
}jwt.secret=your_base64_encoded_256bit_secret_here jwt.expiration=900000 # 15 minutes in ms # Refresh token: 604800000 = 7 days
@RestController @RequestMapping("/api/auth")
public class AuthController {
@Autowired private AuthenticationManager authManager;
@Autowired private JwtUtil jwtUtil;
@Autowired private UserDetailsService userDetailsService;
@PostMapping("/login")
public ResponseEntity<AuthResponse> login(@RequestBody AuthRequest req) {
// 1. Verify credentials via AuthenticationManager (throws BadCredentialsException if invalid)
authManager.authenticate(new UsernamePasswordAuthenticationToken(req.username(), req.password()));
// 2. Load UserDetails
UserDetails ud = userDetailsService.loadUserByUsername(req.username());
// 3. Generate token
String token = jwtUtil.generateToken(ud);
return ResponseEntity.ok(new AuthResponse(token, "Bearer", ud.getUsername(), 900));
}
}
// DTOs
record AuthRequest(String username, String password) {}
record AuthResponse(String token, String type, String username, int expiresIn) {}JwtDecoder uses Nimbus to verify signature and parse claims.Resource OwnerThe user granting accessClientApp requesting accessAuth ServerIssues tokens (Google, Auth0, Okta)Resource ServerHosts & protects resourcesID TokenJWT with user identity (OpenID Connect login)Access TokenFor API access. Short-lived. Opaque or JWT.Refresh TokenGets new access tokens. Long-lived.<!-- For UI login (Thymeleaf, etc.) --> spring-boot-starter-oauth2-client <!-- For validating tokens in REST APIs (Auth0, Google, Okta) --> spring-boot-starter-oauth2-resource-server
spring.security.oauth2.client.registration.google.client-id=CLIENT_ID
spring.security.oauth2.client.registration.google.client-secret=SECRET
spring.security.oauth2.client.registration.google.redirect-uri=http://localhost:8080/login/oauth2/code/{registrationId}
spring.security.oauth2.client.registration.google.scope=profile,email
# Resource Server (validate tokens from external Auth server)
spring.security.oauth2.resourceserver.jwt.issuer-uri=https://accounts.google.com@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf.disable())
.sessionManagement(s -> s.sessionCreationPolicy(STATELESS))
.authorizeHttpRequests(auth -> auth
.requestMatchers(GET, "/employees/**").authenticated()
.requestMatchers(POST, "/employees").hasRole("ADMIN")
.anyRequest().authenticated()
)
.oauth2ResourceServer(oauth2 ->
oauth2.jwt(jwt -> jwt.jwtAuthenticationConverter(jwtAuthConverter()))
);
return http.build();
}
@Bean
public JwtAuthenticationConverter jwtAuthConverter() {
var grantedConverter = new JwtGrantedAuthoritiesConverter();
grantedConverter.setAuthoritiesClaimName("roles"); // Your token's claim name
grantedConverter.setAuthorityPrefix("ROLE_"); // Spring needs ROLE_ prefix
var converter = new JwtAuthenticationConverter();
converter.setJwtGrantedAuthoritiesConverter(grantedConverter);
return converter;
}
@PostMapping
@PreAuthorize("hasRole('ADMIN')")
public Employee create(
@Valid @RequestBody Employee employee,
@AuthenticationPrincipal Jwt jwt) { // Inject decoded JWT
String who = jwt.getClaimAsString("email"); // Audit logging
return service.create(employee);
}@EnableMethodSecurity // Modern (6.x) // @EnableGlobalMethodSecurity // Legacy - deprecated
@PreAuthorize("hasRole('ADMIN')")
@PreAuthorize("hasAnyRole('ADMIN','USER')")
@PreAuthorize("hasAuthority('READ_PRIVILEGE')")
@PreAuthorize("#id == authentication.principal.id") // SpEL — param check
@PreAuthorize("isAuthenticated()")
@PreAuthorize("isAnonymous()")// Only return if the fetched resource belongs to current user
@PostAuthorize("returnObject.username == authentication.name")@Secured({"ROLE_ADMIN", "ROLE_USER"})@RolesAllowed({"ADMIN", "USER"})authentication.nameCurrent usernameauthentication.authoritiesCollection of GrantedAuthorityauthentication.principalUserDetails object#paramMethod parameter named 'param'returnObjectReturn value (@PostAuthorize)principal.usernameUsername from principal// Mock any user
@WithMockUser(username="alice",
roles="USER")
// Load real UserDetails from DB
@WithUserDetails("alice")
// JWT-specific
@WithMockUser + SecurityMockMvcRequestPostProcessors.jwt()mockMvc.perform(
get("/api/admin")
.with(user("alice").roles("USER")))
.andExpect(status().isForbidden());
mockMvc.perform(
get("/api/admin")
.with(user("bob").roles("ADMIN")))
.andExpect(status().isOk());
// JWT token mock
mockMvc.perform(
get("/api/data")
.with(jwt().jwt(j -> j.claim("roles","ADMIN"))));// 1. Login first → get real JWT
var loginReq = new AuthRequest("alice","pass");
var tokenRes = mockMvc.perform(
post("/api/auth/login")
.content(toJson(loginReq))
.contentType(APPLICATION_JSON))
.andReturn();
String token = extractToken(tokenRes);
// 2. Use real JWT in subsequent requests
mockMvc.perform(
get("/api/protected")
.header("Authorization","Bearer " + token))
.andExpect(status().isOk());@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration config = new CorsConfiguration();
config.setAllowedOrigins(List.of("https://yourfrontend.com"));
config.setAllowedMethods(List.of("GET","POST","PUT","DELETE","OPTIONS"));
config.setAllowedHeaders(List.of("Authorization","Content-Type"));
config.setAllowCredentials(true);
config.setMaxAge(3600L); // cache preflight 1hr
var source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
return source;
}
// Add to SecurityFilterChain:
http.cors(cors -> cors.configurationSource(corsConfigurationSource()))http.headers(headers -> headers
.frameOptions(f -> f.deny()) // Prevent clickjacking
.xssProtection(xss -> xss.enable()) // Legacy - some browsers ignore
.contentSecurityPolicy(csp ->
csp.policyDirectives("default-src 'self'"))
.httpStrictTransportSecurity(hsts -> hsts
.includeSubDomains(true)
.maxAgeInSeconds(31536000)) // Force HTTPS 1 year
);server.ssl.enabled=true
server.ssl.key-store=classpath:keystore.p12
server.ssl.key-store-password=${SSL_KEYSTORE_PASSWORD}
server.ssl.key-store-type=PKCS12
security.require-ssl=true@ConfigurationMarks Spring config class@EnableWebSecurityEnables Spring Security@EnableMethodSecurityEnables @PreAuthorize etc.@BeanRegisters Spring bean@PreAuthorizeBefore-method SpEL check@PostAuthorizeAfter-method SpEL check@SecuredLegacy role check@AuthenticationPrincipalInject current user@WithMockUserMock user in tests@WithUserDetailsReal UserDetails in testUserDetailsServiceloadUserByUsername()UserDetailsUser model for Spring SecurityAuthenticationManagerauthenticate(Authentication)AuthenticationProviderCustom auth logicPasswordEncoderencode() + matches()GrantedAuthoritygetAuthority() → ROLE_XSecurityContextHolderHolds auth per threadUsernamePasswordAuthenticationTokenStandard auth tokenDaoAuthenticationProviderUserDetails + PasswordEncoderBCryptPasswordEncoderBcrypt with random saltOncePerRequestFilterBase for JWT filterWebAuthenticationDetailsSourceAttaches request detailsAuthenticationExceptionBase auth failureBadCredentialsExceptionWrong password/userUsernameNotFoundExceptionUser not foundAccessDeniedException403 — no permissionExpiredJwtExceptionJWT past expiryMalformedJwtExceptionBad JWT formatSignatureExceptionJWT signature invalidhasRole("ADMIN")ROLE_ADMIN. Prepends ROLE_ automatically.hasAuthority("ROLE_ADMIN")hasAuthority("READ_DATA")