SecurityConfig.java
package com.ecommerce.identity.infrastructure.security;
import com.ecommerce.platform.common.api.ApiResponse;
import com.nimbusds.jose.JWSAlgorithm;
import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.OctetSequenceKey;
import com.nimbusds.jose.jwk.source.ImmutableJWKSet;
import com.nimbusds.jose.proc.SecurityContext;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
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.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
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.JwtValidators;
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
import org.springframework.security.oauth2.jwt.NimbusJwtEncoder;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.oauth2.server.resource.web.authentication.BearerTokenAuthenticationFilter;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.time.Clock;
@Configuration
@EnableMethodSecurity
@EnableConfigurationProperties({TokenProperties.class, LoginAttemptProperties.class,
InternalServiceProperties.class})
public class SecurityConfig {
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(12);
}
@Bean
public Clock clock() {
return Clock.systemUTC();
}
@Bean
public SecretKey jwtSecretKey(TokenProperties properties) {
return new SecretKeySpec(properties.secret().getBytes(StandardCharsets.UTF_8), "HmacSHA256");
}
@Bean
public JwtEncoder jwtEncoder(SecretKey secretKey) {
OctetSequenceKey jwtKey = new OctetSequenceKey.Builder(secretKey)
.algorithm(JWSAlgorithm.HS256)
.build();
ImmutableJWKSet<SecurityContext> keySource = new ImmutableJWKSet<>(new JWKSet(jwtKey));
return new NimbusJwtEncoder(keySource);
}
@Bean
public JwtDecoder jwtDecoder(SecretKey secretKey, TokenProperties properties) {
NimbusJwtDecoder decoder = NimbusJwtDecoder.withSecretKey(secretKey)
.macAlgorithm(MacAlgorithm.HS256)
.build();
decoder.setJwtValidator(JwtValidators.createDefaultWithIssuer(properties.issuer()));
return decoder;
}
@Bean
public SecurityFilterChain securityFilterChain(
HttpSecurity http,
ObjectMapper objectMapper,
InternalServiceAuthenticationFilter internalServiceAuthenticationFilter) throws Exception {
JwtGrantedAuthoritiesConverter authoritiesConverter = new JwtGrantedAuthoritiesConverter();
authoritiesConverter.setAuthoritiesClaimName("roles");
authoritiesConverter.setAuthorityPrefix("ROLE_");
JwtAuthenticationConverter authenticationConverter = new JwtAuthenticationConverter();
authenticationConverter.setJwtGrantedAuthoritiesConverter(authoritiesConverter);
http
.csrf(csrf -> csrf.disable())
.cors(Customizer.withDefaults())
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeHttpRequests(authorize -> authorize
.requestMatchers(
"/api/v1/identity/status",
"/api/v1/identity/auth/register",
"/api/v1/identity/auth/login",
"/api/v1/identity/auth/refresh",
"/api/v1/identity/auth/logout",
"/actuator/health",
"/actuator/health/**",
"/actuator/info"
).permitAll()
.requestMatchers("/api/v1/identity/internal/**").hasRole("INTERNAL_SERVICE")
.anyRequest().authenticated())
.oauth2ResourceServer(resourceServer -> resourceServer
.jwt(jwt -> jwt.jwtAuthenticationConverter(authenticationConverter))
.authenticationEntryPoint((request, response, exception) -> {
response.setStatus(HttpStatus.UNAUTHORIZED.value());
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
objectMapper.writeValue(
response.getOutputStream(),
ApiResponse.failure("UNAUTHORIZED", "Authentication is required")
);
}))
.exceptionHandling(exceptions -> exceptions
.accessDeniedHandler((request, response, exception) -> {
response.setStatus(HttpStatus.FORBIDDEN.value());
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
objectMapper.writeValue(
response.getOutputStream(),
ApiResponse.failure("FORBIDDEN", "Access is denied")
);
}));
http.addFilterBefore(internalServiceAuthenticationFilter, BearerTokenAuthenticationFilter.class);
return http.build();
}
}