KeycloakSecurityConfig.java

package com.taxonomy.security.config;

import com.taxonomy.security.keycloak.KeycloakAuthenticationEntryPoint;
import com.taxonomy.security.keycloak.KeycloakJwtAuthConverter;
import com.taxonomy.security.keycloak.KeycloakOidcUserService;
import com.taxonomy.security.keycloak.KeycloakLogoutHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.header.writers.ReferrerPolicyHeaderWriter;

/**
 * Spring Security configuration for Keycloak/OIDC mode.
 * <p>
 * Active when the {@code keycloak} profile is enabled.
 * Uses OAuth2 Login (browser SSO) and OAuth2 Resource Server (JWT for REST API).
 */
@Configuration
@EnableMethodSecurity
@Profile("keycloak")
public class KeycloakSecurityConfig {

    private final AuthorizationRulesConfigurer authRules;
    private final KeycloakJwtAuthConverter jwtAuthConverter;
    private final KeycloakOidcUserService oidcUserService;
    private final KeycloakLogoutHandler logoutHandler;
    private final KeycloakAuthenticationEntryPoint authenticationEntryPoint;

    public KeycloakSecurityConfig(AuthorizationRulesConfigurer authRules,
                                  KeycloakJwtAuthConverter jwtAuthConverter,
                                  KeycloakOidcUserService oidcUserService,
                                  KeycloakLogoutHandler logoutHandler,
                                  KeycloakAuthenticationEntryPoint authenticationEntryPoint) {
        this.authRules = authRules;
        this.jwtAuthConverter = jwtAuthConverter;
        this.oidcUserService = oidcUserService;
        this.logoutHandler = logoutHandler;
        this.authenticationEntryPoint = authenticationEntryPoint;
    }

    @Bean
    SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(auth -> authRules.configure(auth))
            .csrf(csrf -> csrf
                .ignoringRequestMatchers("/api/**")
                .ignoringRequestMatchers("/login/oauth2/code/**")
            )
            .headers(headers -> headers
                .contentTypeOptions(Customizer.withDefaults())
                .frameOptions(frame -> frame.sameOrigin())
                .httpStrictTransportSecurity(hsts -> hsts
                    .includeSubDomains(true)
                    .maxAgeInSeconds(31536000))
                .referrerPolicy(referrer -> referrer
                    .policy(ReferrerPolicyHeaderWriter.ReferrerPolicy.STRICT_ORIGIN_WHEN_CROSS_ORIGIN))
            )
            // Browser: OAuth2 Login → Keycloak login page
            .oauth2Login(oauth2 -> oauth2
                .userInfoEndpoint(userInfo -> userInfo
                    .oidcUserService(oidcUserService)
                )
            )
            // REST API: JWT Bearer Token
            .oauth2ResourceServer(oauth2 -> oauth2
                .authenticationEntryPoint(authenticationEntryPoint)
                .jwt(jwt -> jwt
                    .jwtAuthenticationConverter(jwtAuthConverter)
                )
            )
            // Logout: also log out at Keycloak (RP-Initiated Logout)
            .logout(logout -> logout
                .logoutSuccessHandler(logoutHandler)
            );

        return http.build();
    }
}