package com.yau.digitalrmb.security.config; import com.nimbusds.jose.jwk.source.ImmutableSecret; import com.nimbusds.jose.proc.SecurityContext; import com.yau.digitalrmb.shared.api.ApiResponse; import com.yau.digitalrmb.shared.api.ErrorCode; import com.yau.digitalrmb.shared.web.TraceIdFilter; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.MediaType; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.oauth2.jose.jws.MacAlgorithm; import org.springframework.security.oauth2.jwt.JwtDecoder; import org.springframework.security.oauth2.jwt.JwtEncoder; import org.springframework.security.oauth2.jwt.NimbusJwtDecoder; import org.springframework.security.oauth2.jwt.NimbusJwtEncoder; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import javax.crypto.spec.SecretKeySpec; import java.nio.charset.StandardCharsets; import java.util.UUID; @Configuration @EnableWebSecurity @EnableConfigurationProperties(SecurityProperties.class) public class SecurityConfig { @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Bean public JwtEncoder jwtEncoder(SecurityProperties properties) { return new NimbusJwtEncoder(new ImmutableSecret(secretKey(properties))); } @Bean public JwtDecoder jwtDecoder(SecurityProperties properties) { return NimbusJwtDecoder.withSecretKey(secretKey(properties)).macAlgorithm(MacAlgorithm.HS256).build(); } @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { return http.csrf(AbstractHttpConfigurer::disable) .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .authorizeHttpRequests(authorize -> authorize .requestMatchers("/actuator/health", "/api/v1/auth/login", "/api/v1/auth/sso", "/api/v1/auth/cas/**", "/api/v1/auth/session/exchange", "/v3/api-docs/**", "/swagger-ui/**", "/swagger-ui.html") .permitAll() .anyRequest().authenticated()) .oauth2ResourceServer(resourceServer -> resourceServer.jwt(jwt -> { })) .exceptionHandling(exceptions -> exceptions .authenticationEntryPoint((request, response, exception) -> writeError(response, ErrorCode.UNAUTHORIZED, 401)) .accessDeniedHandler((request, response, exception) -> writeError(response, ErrorCode.FORBIDDEN, 403))) .build(); } private static SecretKeySpec secretKey(SecurityProperties properties) { return new SecretKeySpec(properties.getJwt().getSecret().getBytes(StandardCharsets.UTF_8), "HmacSHA256"); } private static void writeError(jakarta.servlet.http.HttpServletResponse response, ErrorCode code, int status) throws java.io.IOException { response.setStatus(status); response.setContentType(MediaType.APPLICATION_JSON_VALUE); String traceId = response.getHeader(TraceIdFilter.HEADER_NAME); if (traceId == null) traceId = UUID.randomUUID().toString(); response.getWriter().write("{\"code\":\"" + code.name() + "\",\"message\":\"" + code.name() + "\",\"data\":null,\"traceId\":\"" + traceId + "\"}"); } }