Spring Security

Complete guide to Spring Security: learn how to secure Spring applications with authentication, authorization, OAuth2, JWT, and more. Master Spring Security's comprehensive security framework for enterprise applications.

Table of Contents

1. What is Spring Security?

Spring Security is a powerful and highly customizable authentication and access-control framework for Java applications. It provides comprehensive security services for Spring-based applications, including authentication, authorization, protection against common attacks, and integration with OAuth2 and JWT.

Spring Security provides:

  • Authentication and authorization mechanisms
  • Protection against common vulnerabilities (CSRF, XSS, etc.)
  • Session management
  • Method-level security
  • OAuth2 and JWT support
  • LDAP integration
  • Remember-me functionality
  • Password encoding

The following diagram illustrates the Spring Security architecture:

graph TB subgraph "Client Request" A[HTTP Request] end subgraph "Spring Security Filter Chain" B[Security Filter Chain] C[Authentication Filter] D[Authorization Filter] E[Exception Translation Filter] F[Filter Security Interceptor] end subgraph "Authentication" G[Authentication Manager] H[Authentication Provider] I[User Details Service] J[Password Encoder] end subgraph "Authorization" K[Access Decision Manager] L[Security Expression Handler] M[Method Security] end A --> B B --> C C --> G G --> H H --> I H --> J C --> D D --> K K --> L D --> F F --> M style A fill:#e1f5ff,stroke:#0273bd,stroke-width:2px style B fill:#fff4e1,stroke:#f57c00,stroke-width:3px style G fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px style K fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px

2. Why Use Spring Security?

  • Comprehensive Security: Provides authentication, authorization, and protection against common attacks
  • Spring Integration: Seamlessly integrates with Spring Framework and Spring Boot
  • Flexible Configuration: Supports both Java configuration and XML configuration
  • Multiple Authentication Methods: Supports form-based, HTTP Basic, OAuth2, JWT, and more
  • Method-Level Security: Secure individual methods with annotations
  • OAuth2 Support: Built-in support for OAuth2 resource servers and clients
  • JWT Support: Full support for JSON Web Tokens
  • CSRF Protection: Built-in protection against Cross-Site Request Forgery
  • Session Management: Advanced session management and security

3. Core Concepts

3.1 Authentication

Authentication is the process of verifying who a user is. Spring Security supports various authentication mechanisms.

The authentication flow is illustrated below:

graph LR subgraph "Authentication Flow" A[User Credentials] --> B[Authentication Filter] B --> C[Authentication Manager] C --> D[Authentication Provider] D --> E[User Details Service] E --> F[Load User] F --> G[Password Verification] G --> H[Authentication Object] H --> I[Security Context] end subgraph "Failure Path" G -->|Invalid| J[Authentication Exception] J --> K[401 Unauthorized] end style A fill:#e1f5ff,stroke:#0273bd,stroke-width:2px style C fill:#fff4e1,stroke:#f57c00,stroke-width:2px style H fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px style I fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px style J fill:#ffebee,stroke:#c62828,stroke-width:2px

3.1.1 In-Memory Authentication

@Configuration
@EnableWebSecurity
public class SecurityConfig {
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/public/**").permitAll()
                .anyRequest().authenticated()
            )
            .httpBasic();
        
        return http.build();
    }
    
    @Bean
    public UserDetailsService userDetailsService() {
        UserDetails user = User.withDefaultPasswordEncoder()
            .username("user")
            .password("password")
            .roles("USER")
            .build();
        
        UserDetails admin = User.withDefaultPasswordEncoder()
            .username("admin")
            .password("admin")
            .roles("USER", "ADMIN")
            .build();
        
        return new InMemoryUserDetailsManager(user, admin);
    }
}

3.1.2 Database Authentication

@Configuration
@EnableWebSecurity
public class SecurityConfig {
    @Autowired
    private UserDetailsService userDetailsService;
    
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/register").permitAll()
                .anyRequest().authenticated()
            )
            .formLogin(form -> form
                .loginPage("/login")
                .defaultSuccessUrl("/home")
                .permitAll()
            )
            .logout(logout -> logout
                .logoutSuccessUrl("/login?logout")
                .permitAll()
            );
        
        return http.build();
    }
    
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
    
    @Bean
    public AuthenticationManager authenticationManager(
            AuthenticationConfiguration config) throws Exception {
        return config.getAuthenticationManager();
    }
}

@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"));
        
        return org.springframework.security.core.userdetails.User
            .withUsername(user.getUsername())
            .password(user.getPassword())
            .authorities(user.getRoles())
            .build();
    }
}

3.2 Authorization

Authorization is the process of determining what a user is allowed to do. Spring Security provides multiple ways to configure authorization.

3.2.1 URL-Based Authorization

@Configuration
@EnableWebSecurity
public class SecurityConfig {
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/public/**").permitAll()
                .requestMatchers("/admin/**").hasRole("ADMIN")
                .requestMatchers("/user/**").hasAnyRole("USER", "ADMIN")
                .requestMatchers(HttpMethod.GET, "/api/**").hasRole("USER")
                .requestMatchers(HttpMethod.POST, "/api/**").hasRole("ADMIN")
                .anyRequest().authenticated()
            );
        
        return http.build();
    }
}

3.2.2 Method-Level Security

@Configuration
@EnableMethodSecurity
public class MethodSecurityConfig {
    // Configuration
}

@Service
public class UserService {
    @PreAuthorize("hasRole('ADMIN')")
    public void deleteUser(Long id) {
        // Only admins can delete users
    }
    
    @PreAuthorize("hasRole('USER') and #userId == authentication.principal.id")
    public User getUser(Long userId) {
        // Users can only view their own profile
    }
    
    @PreAuthorize("hasPermission(#user, 'WRITE')")
    public void updateUser(User user) {
        // Custom permission check
    }
    
    @Secured("ROLE_ADMIN")
    public List<User> getAllUsers() {
        // Only admins can see all users
    }
    
    @RolesAllowed({"USER", "ADMIN"})
    public void updateProfile(User user) {
        // Users and admins can update profiles
    }
}

3.3 Security Filter Chain

Spring Security uses a filter chain to process security-related concerns. Each filter handles a specific security aspect.

@Configuration
@EnableWebSecurity
public class SecurityConfig {
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            .csrf(csrf -> csrf
                .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
            )
            .sessionManagement(session -> session
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            )
            .authorizeHttpRequests(auth -> auth
                .anyRequest().authenticated()
            )
            .oauth2ResourceServer(oauth2 -> oauth2
                .jwt(jwt -> jwt
                    .decoder(jwtDecoder())
                )
            );
        
        return http.build();
    }
    
    @Bean
    public JwtDecoder jwtDecoder() {
        return NimbusJwtDecoder.withJwkSetUri("https://example.com/.well-known/jwks.json")
            .build();
    }
}

4. Configuration

Spring Security can be configured using Java configuration or XML. Modern applications use Java configuration.

4.1 Basic Security Configuration

@Configuration
@EnableWebSecurity
public class SecurityConfig {
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/", "/home", "/register").permitAll()
                .requestMatchers("/admin/**").hasRole("ADMIN")
                .anyRequest().authenticated()
            )
            .formLogin(form -> form
                .loginPage("/login")
                .defaultSuccessUrl("/dashboard")
                .failureUrl("/login?error=true")
                .permitAll()
            )
            .logout(logout -> logout
                .logoutUrl("/logout")
                .logoutSuccessUrl("/login?logout=true")
                .invalidateHttpSession(true)
                .deleteCookies("JSESSIONID")
                .permitAll()
            )
            .rememberMe(remember -> remember
                .tokenValiditySeconds(86400)
                .key("remember-me-key")
            );
        
        return http.build();
    }
    
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

4.2 CORS Configuration

@Configuration
@EnableWebSecurity
public class SecurityConfig {
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            .cors(cors -> cors.configurationSource(corsConfigurationSource()))
            .authorizeHttpRequests(auth -> auth
                .anyRequest().authenticated()
            );
        
        return http.build();
    }
    
    @Bean
    public CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration configuration = new CorsConfiguration();
        configuration.setAllowedOrigins(Arrays.asList("http://localhost:3000"));
        configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE"));
        configuration.setAllowedHeaders(Arrays.asList("*"));
        configuration.setAllowCredentials(true);
        
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", configuration);
        return source;
    }
}

5. OAuth2

OAuth2 is an authorization framework that enables applications to obtain limited access to user accounts. Spring Security provides comprehensive OAuth2 support.

5.1 OAuth2 Concepts

OAuth2 involves several key concepts and roles:

The OAuth2 flow is illustrated below:

graph LR subgraph "OAuth2 Flow" A[Client Application] -->|1. Authorization Request| B[Authorization Server] B -->|2. User Login| C[Resource Owner] C -->|3. Authorization Grant| B B -->|4. Authorization Code| A A -->|5. Exchange Code| B B -->|6. Access Token| A A -->|7. Request with Token| D[Resource Server] D -->|8. Validate Token| B D -->|9. Protected Resource| A end style A fill:#e1f5ff,stroke:#0273bd,stroke-width:2px style B fill:#fff4e1,stroke:#f57c00,stroke-width:3px style C fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px style D fill:#fce4ec,stroke:#c2185b,stroke-width:2px
  • Resource Owner: The user who owns the resource
  • Client: The application requesting access
  • Authorization Server: Issues access tokens
  • Resource Server: Hosts protected resources
  • Access Token: Credential used to access resources
  • Refresh Token: Used to obtain new access tokens

5.2 OAuth2 Resource Server

A resource server validates access tokens and serves protected resources.

5.2.1 JWT Resource Server Configuration

@Configuration
@EnableWebSecurity
public class ResourceServerConfig {
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            .oauth2ResourceServer(oauth2 -> oauth2
                .jwt(jwt -> jwt
                    .decoder(jwtDecoder())
                    .jwtAuthenticationConverter(jwtAuthenticationConverter())
                )
            )
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/api/public/**").permitAll()
                .requestMatchers("/api/**").authenticated()
            );
        
        return http.build();
    }
    
    @Bean
    public JwtDecoder jwtDecoder() {
        return NimbusJwtDecoder.withJwkSetUri("https://auth.example.com/.well-known/jwks.json")
            .build();
    }
    
    @Bean
    public JwtAuthenticationConverter jwtAuthenticationConverter() {
        JwtGrantedAuthoritiesConverter authoritiesConverter = 
            new JwtGrantedAuthoritiesConverter();
        authoritiesConverter.setAuthorityPrefix("ROLE_");
        authoritiesConverter.setAuthoritiesClaimName("roles");
        
        JwtAuthenticationConverter converter = new JwtAuthenticationConverter();
        converter.setJwtGrantedAuthoritiesConverter(authoritiesConverter);
        return converter;
    }
}

5.2.2 Opaque Token Resource Server

@Configuration
@EnableWebSecurity
public class ResourceServerConfig {
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            .oauth2ResourceServer(oauth2 -> oauth2
                .opaqueToken(opaqueToken -> opaqueToken
                    .introspectionUri("https://auth.example.com/oauth2/introspect")
                    .introspectionClientCredentials("client-id", "client-secret")
                )
            );
        
        return http.build();
    }
}

5.3 OAuth2 Client

An OAuth2 client application requests authorization and uses access tokens to access resources.

5.3.1 OAuth2 Client Configuration

@Configuration
@EnableWebSecurity
public class OAuth2ClientConfig {
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            .oauth2Login(oauth2 -> oauth2
                .userInfoEndpoint(userInfo -> userInfo
                    .userService(customOAuth2UserService())
                )
                .successHandler(oauth2AuthenticationSuccessHandler())
            )
            .authorizeHttpRequests(auth -> auth
                .anyRequest().authenticated()
            );
        
        return http.build();
    }
    
    @Bean
    public ClientRegistrationRepository clientRegistrationRepository() {
        return new InMemoryClientRegistrationRepository(
            googleClientRegistration(),
            githubClientRegistration()
        );
    }
    
    private ClientRegistration googleClientRegistration() {
        return ClientRegistration.withRegistrationId("google")
            .clientId("google-client-id")
            .clientSecret("google-client-secret")
            .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
            .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
            .redirectUri("{baseUrl}/login/oauth2/code/{registrationId}")
            .scope("openid", "profile", "email")
            .authorizationUri("https://accounts.google.com/o/oauth2/v2/auth")
            .tokenUri("https://www.googleapis.com/oauth2/v4/token")
            .userInfoUri("https://www.googleapis.com/oauth2/v3/userinfo")
            .userNameAttributeName(IdTokenClaimNames.SUB)
            .jwkSetUri("https://www.googleapis.com/oauth2/v3/certs")
            .clientName("Google")
            .build();
    }
    
    private ClientRegistration githubClientRegistration() {
        return ClientRegistration.withRegistrationId("github")
            .clientId("github-client-id")
            .clientSecret("github-client-secret")
            .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
            .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
            .redirectUri("{baseUrl}/login/oauth2/code/{registrationId}")
            .scope("read:user")
            .authorizationUri("https://github.com/login/oauth/authorize")
            .tokenUri("https://github.com/login/oauth/access_token")
            .userInfoUri("https://api.github.com/user")
            .userNameAttributeName("id")
            .clientName("GitHub")
            .build();
    }
}

5.3.2 OAuth2 Client Properties

# application.yml
spring:
  security:
    oauth2:
      client:
        registration:
          google:
            client-id: ${GOOGLE_CLIENT_ID}
            client-secret: ${GOOGLE_CLIENT_SECRET}
            scope:
              - openid
              - profile
              - email
          github:
            client-id: ${GITHUB_CLIENT_ID}
            client-secret: ${GITHUB_CLIENT_SECRET}
            scope:
              - read:user
        provider:
          google:
            authorization-uri: https://accounts.google.com/o/oauth2/v2/auth
            token-uri: https://www.googleapis.com/oauth2/v4/token
            user-info-uri: https://www.googleapis.com/oauth2/v3/userinfo
            user-name-attribute: sub
          github:
            authorization-uri: https://github.com/login/oauth/authorize
            token-uri: https://github.com/login/oauth/access_token
            user-info-uri: https://api.github.com/user
            user-name-attribute: id

6. JWT (JSON Web Tokens)

JSON Web Tokens (JWT) are a compact, URL-safe means of representing claims to be transferred between parties. Spring Security provides full JWT support.

6.1 JWT Structure

A JWT consists of three parts: header, payload, and signature, separated by dots.

// JWT Structure: header.payload.signature
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

6.2 JWT Configuration

@Configuration
@EnableWebSecurity
public class JwtSecurityConfig {
    @Value("${jwt.secret}")
    private String jwtSecret;
    
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            .csrf(csrf -> csrf.disable())
            .sessionManagement(session -> session
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            )
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/api/auth/**").permitAll()
                .anyRequest().authenticated()
            )
            .addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
        
        return http.build();
    }
    
    @Bean
    public JwtAuthenticationFilter jwtAuthenticationFilter() {
        return new JwtAuthenticationFilter(jwtTokenProvider());
    }
    
    @Bean
    public JwtTokenProvider jwtTokenProvider() {
        return new JwtTokenProvider(jwtSecret);
    }
}

@Component
public class JwtTokenProvider {
    private final String secret;
    private final long validityInMilliseconds = 3600000; // 1 hour
    
    public JwtTokenProvider(String secret) {
        this.secret = secret;
    }
    
    public String createToken(String username, List<String> roles) {
        Claims claims = Jwts.claims().setSubject(username);
        claims.put("roles", roles);
        
        Date now = new Date();
        Date validity = new Date(now.getTime() + validityInMilliseconds);
        
        return Jwts.builder()
            .setClaims(claims)
            .setIssuedAt(now)
            .setExpiration(validity)
            .signWith(SignatureAlgorithm.HS256, secret)
            .compact();
    }
    
    public Authentication getAuthentication(String token) {
        UserDetails userDetails = userDetailsService.loadUserByUsername(getUsername(token));
        return new UsernamePasswordAuthenticationToken(userDetails, "", userDetails.getAuthorities());
    }
    
    public String getUsername(String token) {
        return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody().getSubject();
    }
    
    public boolean validateToken(String token) {
        try {
            Jwts.parser().setSigningKey(secret).parseClaimsJws(token);
            return true;
        } catch (JwtException | IllegalArgumentException e) {
            return false;
        }
    }
}

7. Real-World Examples

7.1 Example 1: REST API Security

@Configuration
@EnableWebSecurity
@EnableMethodSecurity
public class RestApiSecurityConfig {
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            .csrf(csrf -> csrf.disable())
            .sessionManagement(session -> session
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            )
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/api/public/**").permitAll()
                .requestMatchers("/api/admin/**").hasRole("ADMIN")
                .requestMatchers("/api/user/**").hasAnyRole("USER", "ADMIN")
                .anyRequest().authenticated()
            )
            .oauth2ResourceServer(oauth2 -> oauth2
                .jwt(jwt -> jwt.decoder(jwtDecoder()))
            );
        
        return http.build();
    }
    
    @Bean
    public JwtDecoder jwtDecoder() {
        return NimbusJwtDecoder.withJwkSetUri("https://auth.example.com/.well-known/jwks.json")
            .build();
    }
}

@RestController
@RequestMapping("/api/users")
public class UserController {
    @GetMapping("/me")
    @PreAuthorize("hasRole('USER')")
    public ResponseEntity<User> getCurrentUser(Authentication authentication) {
        String username = authentication.getName();
        // Return current user
        return ResponseEntity.ok(userService.findByUsername(username));
    }
    
    @DeleteMapping("/{id}")
    @PreAuthorize("hasRole('ADMIN')")
    public ResponseEntity<Void> deleteUser(@PathVariable Long id) {
        userService.deleteUser(id);
        return ResponseEntity.noContent().build();
    }
}

7.2 Example 2: OAuth2 Login

@Configuration
@EnableWebSecurity
public class OAuth2LoginConfig {
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            .oauth2Login(oauth2 -> oauth2
                .userInfoEndpoint(userInfo -> userInfo
                    .userService(customOAuth2UserService())
                )
                .successHandler(oauth2AuthenticationSuccessHandler())
            )
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/", "/login", "/error").permitAll()
                .anyRequest().authenticated()
            );
        
        return http.build();
    }
    
    @Bean
    public OAuth2UserService<OAuth2UserRequest, OAuth2User> customOAuth2UserService() {
        return new CustomOAuth2UserService();
    }
    
    @Bean
    public AuthenticationSuccessHandler oauth2AuthenticationSuccessHandler() {
        return new OAuth2AuthenticationSuccessHandler();
    }
}

@Service
public class CustomOAuth2UserService implements OAuth2UserService<OAuth2UserRequest, OAuth2User> {
    @Autowired
    private UserRepository userRepository;
    
    @Override
    public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
        OAuth2User oauth2User = new DefaultOAuth2UserService().loadUser(userRequest);
        
        String email = oauth2User.getAttribute("email");
        User user = userRepository.findByEmail(email)
            .orElseGet(() -> createNewUser(oauth2User));
        
        return new CustomOAuth2User(user, oauth2User.getAttributes());
    }
    
    private User createNewUser(OAuth2User oauth2User) {
        User user = new User();
        user.setEmail(oauth2User.getAttribute("email"));
        user.setName(oauth2User.getAttribute("name"));
        user.setProvider(Provider.GOOGLE);
        return userRepository.save(user);
    }
}

7.3 Example 3: Method Security

@Configuration
@EnableMethodSecurity(prePostEnabled = true)
public class MethodSecurityConfig {
    // Configuration
}

@Service
public class OrderService {
    @PreAuthorize("hasRole('USER')")
    public Order createOrder(Order order) {
        // Only authenticated users can create orders
        return orderRepository.save(order);
    }
    
    @PreAuthorize("hasRole('ADMIN') or #order.userId == authentication.principal.id")
    public Order updateOrder(Long orderId, Order order) {
        // Admins or order owners can update
        return orderRepository.save(order);
    }
    
    @PreAuthorize("hasRole('ADMIN')")
    @PostAuthorize("returnObject.userId == authentication.principal.id or hasRole('ADMIN')")
    public Order getOrder(Long orderId) {
        // Users can only see their own orders, admins can see all
        return orderRepository.findById(orderId).orElseThrow();
    }
    
    @PreFilter("filterObject.userId == authentication.principal.id or hasRole('ADMIN')")
    public List<Order> processOrders(List<Order> orders) {
        // Filter orders before processing
        return orders.stream()
            .map(this::processOrder)
            .collect(Collectors.toList());
    }
}

8. Best Practices

8.1 Password Security

// Always use strong password encoding
@Bean
public PasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder(12); // Use strength 12 or higher
}

// Never store plain text passwords
// Always hash passwords before storing
String encodedPassword = passwordEncoder.encode(rawPassword);

// Verify passwords securely
boolean matches = passwordEncoder.matches(rawPassword, encodedPassword);

8.2 Token Security

// Use short-lived access tokens
long accessTokenValidity = 3600000; // 1 hour

// Use refresh tokens for long-term sessions
long refreshTokenValidity = 86400000; // 24 hours

// Always validate tokens on resource server
// Use HTTPS for token transmission
// Store tokens securely (httpOnly cookies preferred)

8.3 CSRF Protection

// Enable CSRF for state-changing operations
http.csrf(csrf -> csrf
    .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
);

// Disable CSRF only for stateless APIs
http.csrf(csrf -> csrf.disable()); // Only for stateless APIs

8.4 Security Headers

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    http
        .headers(headers -> headers
            .contentSecurityPolicy(csp -> csp
                .policyDirectives("default-src 'self'")
            )
            .frameOptions(frame -> frame.deny())
            .httpStrictTransportSecurity(hsts -> hsts
                .maxAgeInSeconds(31536000)
                .includeSubdomains(true)
            )
        );
    
    return http.build();
}

9. Advanced Concepts

9.1 Custom Authentication Provider

@Component
public class CustomAuthenticationProvider implements AuthenticationProvider {
    @Autowired
    private UserDetailsService userDetailsService;
    
    @Autowired
    private PasswordEncoder passwordEncoder;
    
    @Override
    public Authentication authenticate(Authentication authentication) 
            throws AuthenticationException {
        String username = authentication.getName();
        String password = authentication.getCredentials().toString();
        
        UserDetails user = userDetailsService.loadUserByUsername(username);
        
        if (passwordEncoder.matches(password, user.getPassword())) {
            return new UsernamePasswordAuthenticationToken(
                user, password, user.getAuthorities()
            );
        } else {
            throw new BadCredentialsException("Invalid credentials");
        }
    }
    
    @Override
    public boolean supports(Class<?> authentication) {
        return UsernamePasswordAuthenticationToken.class
            .isAssignableFrom(authentication);
    }
}

@Configuration
@EnableWebSecurity
public class SecurityConfig {
    @Autowired
    private CustomAuthenticationProvider customAuthenticationProvider;
    
    @Bean
    public AuthenticationManager authenticationManager(
            AuthenticationConfiguration config) throws Exception {
        return config.getAuthenticationManager();
    }
    
    @Bean
    public AuthenticationProvider authenticationProvider() {
        return customAuthenticationProvider;
    }
}

9.2 Custom Access Decision Voter

public class CustomAccessDecisionVoter implements AccessDecisionVoter<Object> {
    @Override
    public boolean supports(ConfigAttribute attribute) {
        return attribute.getAttribute().startsWith("CUSTOM_");
    }
    
    @Override
    public boolean supports(Class<?> clazz) {
        return true;
    }
    
    @Override
    public int vote(Authentication authentication, Object object,
                   Collection<ConfigAttribute> attributes) {
        // Custom voting logic
        return ACCESS_GRANTED;
    }
}

@Configuration
@EnableWebSecurity
public class SecurityConfig {
    @Bean
    public AccessDecisionManager accessDecisionManager() {
        List<AccessDecisionVoter<?>> decisionVoters = Arrays.asList(
            new RoleVoter(),
            new AuthenticatedVoter(),
            new CustomAccessDecisionVoter()
        );
        return new UnanimousBased(decisionVoters);
    }
}

9.3 Security Event Publishing

@Component
public class SecurityEventListener {
    @EventListener
    public void handleAuthenticationSuccess(AuthenticationSuccessEvent event) {
        // Log successful authentication
        log.info("User {} authenticated successfully", 
            event.getAuthentication().getName());
    }
    
    @EventListener
    public void handleAuthenticationFailure(AbstractAuthenticationFailureEvent event) {
        // Log failed authentication
        log.warn("Authentication failed for user: {}", 
            event.getAuthentication().getName());
    }
    
    @EventListener
    public void handleAuthorizationFailure(AuthorizationFailureEvent event) {
        // Log authorization failure
        log.warn("Authorization failed for user: {}", 
            event.getAuthentication().getName());
    }
}

10. Conclusion

Spring Security provides a comprehensive and flexible security framework for Spring applications. With support for authentication, authorization, OAuth2, JWT, and protection against common vulnerabilities, Spring Security enables you to build secure enterprise applications.

By understanding core concepts like authentication, authorization, the security filter chain, and OAuth2 flows, you can implement robust security in your Spring applications. Whether you're building REST APIs with JWT, implementing OAuth2 login, or securing web applications, Spring Security provides the tools you need.

Remember to follow security best practices: use strong password encoding, implement proper token management, enable CSRF protection, and configure security headers. With Spring Security, you can build applications that are both secure and maintainable.

Post a Comment

0 Comments