Spring Boot Cheatsheet
Security
Use this Spring Boot reference while you build software engineering projects, review code for technical interview prep, or polish examples for a software engineer resume.
Dependency
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>Adding this starter immediately secures all endpoints with HTTP Basic and a generated password (printed to console on startup). Override by defining a SecurityFilterChain bean.
SecurityFilterChain — the Main Config Bean
@Configuration @EnableWebSecurity public class SecurityConfig { @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http .csrf(csrf -> csrf.disable()) // disable for stateless REST APIs .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .authorizeHttpRequests(auth -> auth .requestMatchers("/api/auth/**", "/api/public/**").permitAll() .requestMatchers(HttpMethod.GET, "/api/products/**").permitAll() .requestMatchers("/api/admin/**").hasRole("ADMIN") .requestMatchers("/api/**").authenticated() .anyRequest().permitAll() ) .addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class) .exceptionHandling(ex -> ex .authenticationEntryPoint(unauthorizedEntryPoint()) .accessDeniedHandler(forbiddenHandler()) ); return http.build(); } }
Method-Level Security
@Configuration @EnableMethodSecurity // replaces deprecated @EnableGlobalMethodSecurity public class MethodSecurityConfig {}
@Service public class ArticleService { @PreAuthorize("hasRole('ADMIN') or #authorId == authentication.principal.id") public Article getPrivate(Long articleId, Long authorId) { ... } @PostAuthorize("returnObject.authorId == authentication.principal.id") public Article getOwned(Long id) { ... } @PreAuthorize("hasAuthority('articles:write')") public Article create(CreateArticleRequest req) { ... } @Secured({"ROLE_ADMIN", "ROLE_EDITOR"}) // simpler, no SpEL public void delete(Long id) { ... } }
| Annotation | Evaluates | Notes |
|---|---|---|
@PreAuthorize | Before method invocation | SpEL; most flexible |
@PostAuthorize | After (can inspect return value) | SpEL; use sparingly |
@Secured | Before invocation | Role names only, no SpEL |
@RolesAllowed | Before invocation | JSR-250; like @Secured |
JWT Authentication Filter
@Component @RequiredArgsConstructor public class JwtAuthFilter extends OncePerRequestFilter { private final JwtService jwtService; private final UserDetailsService userDetailsService; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException { String authHeader = request.getHeader(HttpHeaders.AUTHORIZATION); if (authHeader == null || !authHeader.startsWith("Bearer ")) { chain.doFilter(request, response); return; } String token = authHeader.substring(7); String username = jwtService.extractUsername(token); if (username != null && SecurityContextHolder.getContext() .getAuthentication() == null) { UserDetails user = userDetailsService.loadUserByUsername(username); if (jwtService.isTokenValid(token, user)) { UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities()); authToken.setDetails(new WebAuthenticationDetailsSource() .buildDetails(request)); SecurityContextHolder.getContext().setAuthentication(authToken); } } chain.doFilter(request, response); } }
UserDetailsService Implementation
@Service @RequiredArgsConstructor public class AppUserDetailsService implements UserDetailsService { private final UserRepository userRepository; @Override public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException { return userRepository.findByEmail(email) .map(user -> org.springframework.security.core.userdetails.User .withUsername(user.getEmail()) .password(user.getPasswordHash()) .roles(user.getRole().name()) // adds ROLE_ prefix .build()) .orElseThrow(() -> new UsernameNotFoundException("User not found: " + email)); } }
Password Encoding
@Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(12); // work factor 12 (default 10) } // Encoding String hash = passwordEncoder.encode(rawPassword); // Verification boolean matches = passwordEncoder.matches(rawPassword, storedHash);
Authentication Entry Points and Access Denied Handlers
// 401 — not authenticated @Bean public AuthenticationEntryPoint unauthorizedEntryPoint() { return (request, response, ex) -> { response.setContentType(MediaType.APPLICATION_JSON_VALUE); response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); response.getWriter().write("{\"error\":\"Unauthorized\"}"); }; } // 403 — authenticated but no permission @Bean public AccessDeniedHandler forbiddenHandler() { return (request, response, ex) -> { response.setContentType(MediaType.APPLICATION_JSON_VALUE); response.setStatus(HttpServletResponse.SC_FORBIDDEN); response.getWriter().write("{\"error\":\"Forbidden\"}"); }; }
Session-Based Auth (Stateful)
http
.formLogin(form -> form
.loginPage("/login")
.loginProcessingUrl("/login")
.defaultSuccessUrl("/dashboard", true)
.failureUrl("/login?error")
.permitAll()
)
.logout(logout -> logout
.logoutUrl("/logout")
.logoutSuccessUrl("/login?logout")
.invalidateHttpSession(true)
.deleteCookies("JSESSIONID")
.permitAll()
)
.rememberMe(remember -> remember
.key("my-unique-key")
.tokenValiditySeconds(86400 * 14) // 14 days
);OAuth2 Resource Server (JWT)
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>spring.security.oauth2.resourceserver.jwt.issuer-uri=https://auth.example.com
# or public key location
spring.security.oauth2.resourceserver.jwt.public-key-location=classpath:public.pemhttp.oauth2ResourceServer(oauth2 -> oauth2.jwt(jwt -> jwt.jwtAuthenticationConverter(jwtAuthenticationConverter())) ); @Bean public JwtAuthenticationConverter jwtAuthenticationConverter() { JwtGrantedAuthoritiesConverter grantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter(); grantedAuthoritiesConverter.setAuthoritiesClaimName("roles"); grantedAuthoritiesConverter.setAuthorityPrefix("ROLE_"); JwtAuthenticationConverter converter = new JwtAuthenticationConverter(); converter.setJwtGrantedAuthoritiesConverter(grantedAuthoritiesConverter); return converter; }
CORS with Security
// Security must delegate to the CorsConfigurationSource bean http.cors(Customizer.withDefaults()); // picks up CorsConfigurationSource bean @Bean public CorsConfigurationSource corsConfigurationSource() { CorsConfiguration config = new CorsConfiguration(); config.setAllowedOrigins(List.of("https://myapp.com")); config.setAllowedMethods(List.of("GET","POST","PUT","DELETE","OPTIONS")); config.setAllowedHeaders(List.of("Authorization","Content-Type")); config.setAllowCredentials(true); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", config); return source; }
CSRF
// Disable for stateless REST APIs (use JWT/tokens instead) http.csrf(csrf -> csrf.disable()); // Stateful apps — CSRF is enabled by default // With Thymeleaf: _csrf token is auto-included in forms // With SPA: use CookieCsrfTokenRepository http.csrf(csrf -> csrf .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()) );
Security Context Access
// Get current user anywhere Authentication auth = SecurityContextHolder.getContext().getAuthentication(); String username = auth.getName(); Collection<? extends GrantedAuthority> authorities = auth.getAuthorities(); // In a controller @GetMapping("/me") public UserDto me(@AuthenticationPrincipal UserDetails userDetails) { return userService.findByEmail(userDetails.getUsername()); } // With a custom UserDetails implementation @GetMapping("/me") public UserDto me(@AuthenticationPrincipal AppUser user) { return mapper.toDto(user); }
Security Testing
@SpringBootTest @AutoConfigureMockMvc class ArticleControllerTest { @Autowired MockMvc mvc; @Test @WithMockUser(roles = "ADMIN") // simulate logged-in admin void adminCanDelete() throws Exception { mvc.perform(delete("/api/articles/1")) .andExpect(status().isNoContent()); } @Test void anonymousUserGets401() throws Exception { mvc.perform(get("/api/articles/1")) .andExpect(status().isUnauthorized()); } }
Common Pitfalls
- Blank page after login —
defaultSuccessUrlneedsalwaysUse=trueas the second argument if users might hit/logindirectly. - CORS preflight blocked by Security — OPTIONS requests need
.requestMatchers(HttpMethod.OPTIONS, "/**").permitAll()or rely onhttp.cors(withDefaults())which handles preflight automatically. @PreAuthorizenot firing —@EnableMethodSecurityis required; it does not activate automatically.- Role prefix confusion —
hasRole("ADMIN")checks forROLE_ADMIN;hasAuthority("ROLE_ADMIN")is the explicit equivalent; never double-prefix. SecurityContextHolderin async threads — the security context is thread-local; for@Asyncmethods, setMODE_INHERITABLETHREADLOCALor pass credentials explicitly.- JWT filter running for non-API paths — use
shouldNotFilter()override to skip static resources and public paths.