Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Build Spring Boot 3.x REST APIs, microservices, and reactive WebFlux apps with Spring Security 6 and JPA.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
references/security.md
1# Security - Spring Security 623## Security Configuration45```java6@Configuration7@EnableWebSecurity8@EnableMethodSecurity9public class SecurityConfig {1011@Bean12public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {13http14.csrf(csrf -> csrf15.ignoringRequestMatchers("/api/auth/**")16.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())17)18.cors(cors -> cors.configurationSource(corsConfigurationSource()))19.authorizeHttpRequests(auth -> auth20.requestMatchers("/api/auth/**", "/actuator/health").permitAll()21.requestMatchers("/api/admin/**").hasRole("ADMIN")22.requestMatchers("/api/users/**").hasAnyRole("USER", "ADMIN")23.anyRequest().authenticated()24)25.sessionManagement(session -> session26.sessionCreationPolicy(SessionCreationPolicy.STATELESS)27)28.exceptionHandling(ex -> ex29.authenticationEntryPoint(authenticationEntryPoint())30.accessDeniedHandler(accessDeniedHandler())31)32.addFilterBefore(jwtAuthenticationFilter(),33UsernamePasswordAuthenticationFilter.class);3435return http.build();36}3738@Bean39public CorsConfigurationSource corsConfigurationSource() {40CorsConfiguration configuration = new CorsConfiguration();41configuration.setAllowedOrigins(List.of("http://localhost:3000"));42configuration.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "OPTIONS"));43configuration.setAllowedHeaders(List.of("*"));44configuration.setAllowCredentials(true);45configuration.setMaxAge(3600L);4647UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();48source.registerCorsConfiguration("/**", configuration);49return source;50}5152@Bean53public AuthenticationManager authenticationManager(54AuthenticationConfiguration config) throws Exception {55return config.getAuthenticationManager();56}5758@Bean59public PasswordEncoder passwordEncoder() {60return new BCryptPasswordEncoder(12);61}62}63```6465## JWT Authentication Filter6667```java68@Component69@RequiredArgsConstructor70public class JwtAuthenticationFilter extends OncePerRequestFilter {71private final JwtService jwtService;72private final UserDetailsService userDetailsService;7374@Override75protected void doFilterInternal(76@NonNull HttpServletRequest request,77@NonNull HttpServletRequest response,78@NonNull FilterChain filterChain) throws ServletException, IOException {7980final String authHeader = request.getHeader("Authorization");81final String jwt;82final String username;8384if (authHeader == null || !authHeader.startsWith("Bearer ")) {85filterChain.doFilter(request, response);86return;87}8889jwt = authHeader.substring(7);9091try {92username = jwtService.extractUsername(jwt);9394if (username != null && SecurityContextHolder.getContext()95.getAuthentication() == null) {96UserDetails userDetails = userDetailsService.loadUserByUsername(username);9798if (jwtService.isTokenValid(jwt, userDetails)) {99UsernamePasswordAuthenticationToken authToken =100new UsernamePasswordAuthenticationToken(101userDetails,102null,103userDetails.getAuthorities()104);105106authToken.setDetails(107new WebAuthenticationDetailsSource().buildDetails(request)108);109110SecurityContextHolder.getContext().setAuthentication(authToken);111}112}113} catch (JwtException e) {114log.error("JWT validation failed", e);115}116117filterChain.doFilter(request, response);118}119}120```121122## JWT Service123124```java125@Service126public class JwtService {127@Value("${jwt.secret}")128private String secretKey;129130@Value("${jwt.expiration}")131private long jwtExpiration;132133@Value("${jwt.refresh-expiration}")134private long refreshExpiration;135136public String extractUsername(String token) {137return extractClaim(token, Claims::getSubject);138}139140public <T> T extractClaim(String token, Function<Claims, T> claimsResolver) {141final Claims claims = extractAllClaims(token);142return claimsResolver.apply(claims);143}144145public String generateToken(UserDetails userDetails) {146Map<String, Object> extraClaims = new HashMap<>();147extraClaims.put("roles", userDetails.getAuthorities().stream()148.map(GrantedAuthority::getAuthority)149.collect(Collectors.toList()));150151return generateToken(extraClaims, userDetails);152}153154public String generateToken(155Map<String, Object> extraClaims,156UserDetails userDetails) {157return buildToken(extraClaims, userDetails, jwtExpiration);158}159160public String generateRefreshToken(UserDetails userDetails) {161return buildToken(new HashMap<>(), userDetails, refreshExpiration);162}163164private String buildToken(165Map<String, Object> extraClaims,166UserDetails userDetails,167long expiration) {168return Jwts169.builder()170.setClaims(extraClaims)171.setSubject(userDetails.getUsername())172.setIssuedAt(new Date(System.currentTimeMillis()))173.setExpiration(new Date(System.currentTimeMillis() + expiration))174.signWith(getSignInKey(), SignatureAlgorithm.HS256)175.compact();176}177178public boolean isTokenValid(String token, UserDetails userDetails) {179final String username = extractUsername(token);180return username.equals(userDetails.getUsername()) && !isTokenExpired(token);181}182183private boolean isTokenExpired(String token) {184return extractExpiration(token).before(new Date());185}186187private Date extractExpiration(String token) {188return extractClaim(token, Claims::getExpiration);189}190191private Claims extractAllClaims(String token) {192return Jwts193.parserBuilder()194.setSigningKey(getSignInKey())195.build()196.parseClaimsJws(token)197.getBody();198}199200private Key getSignInKey() {201byte[] keyBytes = Decoders.BASE64.decode(secretKey);202return Keys.hmacShaKeyFor(keyBytes);203}204}205```206207## UserDetailsService Implementation208209```java210@Service211@RequiredArgsConstructor212public class CustomUserDetailsService implements UserDetailsService {213private final UserRepository userRepository;214215@Override216@Transactional(readOnly = true)217public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {218User user = userRepository.findByEmailWithRoles(username)219.orElseThrow(() -> new UsernameNotFoundException(220"User not found with email: " + username));221222return org.springframework.security.core.userdetails.User223.builder()224.username(user.getEmail())225.password(user.getPassword())226.authorities(user.getRoles().stream()227.map(role -> new SimpleGrantedAuthority("ROLE_" + role.getName()))228.collect(Collectors.toList()))229.accountExpired(false)230.accountLocked(!user.getActive())231.credentialsExpired(false)232.disabled(!user.getActive())233.build();234}235}236```237238## Authentication Controller239240```java241@RestController242@RequestMapping("/api/auth")243@RequiredArgsConstructor244public class AuthenticationController {245private final AuthenticationService authenticationService;246247@PostMapping("/register")248public ResponseEntity<AuthenticationResponse> register(249@Valid @RequestBody RegisterRequest request) {250AuthenticationResponse response = authenticationService.register(request);251return ResponseEntity.status(HttpStatus.CREATED).body(response);252}253254@PostMapping("/login")255public ResponseEntity<AuthenticationResponse> login(256@Valid @RequestBody LoginRequest request) {257AuthenticationResponse response = authenticationService.login(request);258return ResponseEntity.ok(response);259}260261@PostMapping("/refresh")262public ResponseEntity<AuthenticationResponse> refreshToken(263@RequestBody RefreshTokenRequest request) {264AuthenticationResponse response = authenticationService.refreshToken(request);265return ResponseEntity.ok(response);266}267268@PostMapping("/logout")269@PreAuthorize("isAuthenticated()")270public ResponseEntity<Void> logout() {271SecurityContextHolder.clearContext();272return ResponseEntity.noContent().build();273}274}275```276277## Authentication Service278279```java280@Service281@RequiredArgsConstructor282@Transactional283public class AuthenticationService {284private final UserRepository userRepository;285private final PasswordEncoder passwordEncoder;286private final JwtService jwtService;287private final AuthenticationManager authenticationManager;288289public AuthenticationResponse register(RegisterRequest request) {290if (userRepository.existsByEmail(request.email())) {291throw new DuplicateResourceException("Email already registered");292}293294User user = User.builder()295.email(request.email())296.password(passwordEncoder.encode(request.password()))297.username(request.username())298.active(true)299.roles(Set.of(Role.builder().name("USER").build()))300.build();301302user = userRepository.save(user);303304String accessToken = jwtService.generateToken(convertToUserDetails(user));305String refreshToken = jwtService.generateRefreshToken(convertToUserDetails(user));306307return new AuthenticationResponse(accessToken, refreshToken);308}309310public AuthenticationResponse login(LoginRequest request) {311authenticationManager.authenticate(312new UsernamePasswordAuthenticationToken(313request.email(),314request.password()315)316);317318User user = userRepository.findByEmail(request.email())319.orElseThrow(() -> new UsernameNotFoundException("User not found"));320321String accessToken = jwtService.generateToken(convertToUserDetails(user));322String refreshToken = jwtService.generateRefreshToken(convertToUserDetails(user));323324return new AuthenticationResponse(accessToken, refreshToken);325}326327public AuthenticationResponse refreshToken(RefreshTokenRequest request) {328String username = jwtService.extractUsername(request.refreshToken());329330User user = userRepository.findByEmail(username)331.orElseThrow(() -> new UsernameNotFoundException("User not found"));332333UserDetails userDetails = convertToUserDetails(user);334335if (!jwtService.isTokenValid(request.refreshToken(), userDetails)) {336throw new InvalidTokenException("Invalid refresh token");337}338339String accessToken = jwtService.generateToken(userDetails);340341return new AuthenticationResponse(accessToken, request.refreshToken());342}343344private UserDetails convertToUserDetails(User user) {345return org.springframework.security.core.userdetails.User346.builder()347.username(user.getEmail())348.password(user.getPassword())349.authorities(user.getRoles().stream()350.map(role -> new SimpleGrantedAuthority("ROLE_" + role.getName()))351.collect(Collectors.toList()))352.build();353}354}355```356357## Method Security358359```java360@Service361@RequiredArgsConstructor362public class UserService {363private final UserRepository userRepository;364365@PreAuthorize("hasRole('ADMIN')")366public List<User> getAllUsers() {367return userRepository.findAll();368}369370@PreAuthorize("hasRole('ADMIN') or #userId == authentication.principal.id")371public User getUserById(Long userId) {372return userRepository.findById(userId)373.orElseThrow(() -> new ResourceNotFoundException("User not found"));374}375376@PreAuthorize("isAuthenticated()")377@PostAuthorize("returnObject.email == authentication.principal.username")378public User updateProfile(Long userId, UserUpdateRequest request) {379User user = getUserById(userId);380// Update logic381return userRepository.save(user);382}383384@Secured({"ROLE_ADMIN", "ROLE_MANAGER"})385public void deleteUser(Long userId) {386userRepository.deleteById(userId);387}388}389```390391## OAuth2 Resource Server (JWT)392393```java394@Configuration395@EnableWebSecurity396public class OAuth2ResourceServerConfig {397398@Bean399public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {400http401.authorizeHttpRequests(auth -> auth402.requestMatchers("/public/**").permitAll()403.anyRequest().authenticated()404)405.oauth2ResourceServer(oauth2 -> oauth2406.jwt(jwt -> jwt407.jwtAuthenticationConverter(jwtAuthenticationConverter())408)409);410411return http.build();412}413414@Bean415public JwtDecoder jwtDecoder() {416return JwtDecoders.fromIssuerLocation("https://auth.example.com");417}418419@Bean420public JwtAuthenticationConverter jwtAuthenticationConverter() {421JwtGrantedAuthoritiesConverter grantedAuthoritiesConverter =422new JwtGrantedAuthoritiesConverter();423grantedAuthoritiesConverter.setAuthoritiesClaimName("roles");424grantedAuthoritiesConverter.setAuthorityPrefix("ROLE_");425426JwtAuthenticationConverter jwtAuthenticationConverter =427new JwtAuthenticationConverter();428jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(429grantedAuthoritiesConverter);430431return jwtAuthenticationConverter;432}433}434```435436## Quick Reference437438| Annotation | Purpose |439|------------|---------|440| `@EnableWebSecurity` | Enables Spring Security |441| `@EnableMethodSecurity` | Enables method-level security annotations |442| `@PreAuthorize` | Checks authorization before method execution |443| `@PostAuthorize` | Checks authorization after method execution |444| `@Secured` | Role-based method security |445| `@WithMockUser` | Mock authenticated user in tests |446| `@AuthenticationPrincipal` | Inject current user in controller |447448## Security Best Practices449450- Always use HTTPS in production451- Store JWT secret in environment variables452- Use strong password encoding (BCrypt with strength 12+)453- Implement token refresh mechanism454- Add rate limiting to authentication endpoints455- Validate all user inputs456- Log security events457- Keep dependencies updated458- Use CSRF protection for state-changing operations459- Implement proper session timeout460