diff --git a/src/main/java/net/siegeln/cameleer/saas/config/SecurityConfig.java b/src/main/java/net/siegeln/cameleer/saas/config/SecurityConfig.java index 17bf99a..99e4e3d 100644 --- a/src/main/java/net/siegeln/cameleer/saas/config/SecurityConfig.java +++ b/src/main/java/net/siegeln/cameleer/saas/config/SecurityConfig.java @@ -1,6 +1,7 @@ package net.siegeln.cameleer.saas.config; import net.siegeln.cameleer.saas.auth.JwtAuthenticationFilter; +import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.annotation.Order; @@ -10,10 +11,23 @@ import org.springframework.security.config.annotation.web.configuration.EnableWe 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.SignatureAlgorithm; +import org.springframework.security.oauth2.jwt.JwtDecoder; +import org.springframework.security.oauth2.jwt.JwtValidators; +import org.springframework.security.oauth2.jwt.NimbusJwtDecoder; import org.springframework.security.oauth2.server.resource.web.authentication.BearerTokenAuthenticationFilter; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; +import com.nimbusds.jose.proc.JWSVerificationKeySelector; +import com.nimbusds.jose.proc.SecurityContext; +import com.nimbusds.jose.util.ResourceRetriever; +import com.nimbusds.jwt.proc.DefaultJWTProcessor; +import com.nimbusds.jose.JWSAlgorithm; +import com.nimbusds.jose.jwk.source.JWKSourceBuilder; +import java.net.URL; +import java.util.Set; + @Configuration @EnableWebSecurity @EnableMethodSecurity @@ -61,6 +75,28 @@ public class SecurityConfig { return http.build(); } + @Bean + public JwtDecoder jwtDecoder( + @Value("${spring.security.oauth2.resourceserver.jwt.jwk-set-uri}") String jwkSetUri, + @Value("${spring.security.oauth2.resourceserver.jwt.issuer-uri:}") String issuerUri) throws Exception { + // Logto issues tokens with typ "at+jwt" (RFC 9068). Spring Security's default + // decoder only allows typ "JWT". Build a custom processor that accepts both. + var jwkSource = JWKSourceBuilder.create(new URL(jwkSetUri)).build(); + var keySelector = new JWSVerificationKeySelector( + JWSAlgorithm.ES384, jwkSource); + + var jwtProcessor = new DefaultJWTProcessor(); + jwtProcessor.setJWSKeySelector(keySelector); + // Allow both "JWT" and "at+jwt" token types + jwtProcessor.setJWSTypeVerifier((type, context) -> { /* accept any type */ }); + + var decoder = new NimbusJwtDecoder(jwtProcessor); + if (issuerUri != null && !issuerUri.isEmpty()) { + decoder.setJwtValidator(JwtValidators.createDefaultWithIssuer(issuerUri)); + } + return decoder; + } + @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder();