Spring Security验证Okta JWT失败:application/okta-internal-at+jwt头不被允许
问题:Spring Boot WebFlux集成Okta OAuth2时JWT令牌typ头验证失败
使用Spring Boot 3.4.1结合WebFlux集成Okta OAuth2客户端,配置完成后遇到JWT令牌验证错误,报错提示JOSE头类型application/okta-internal-at+jwt不被允许。
已尝试自定义ReactiveJwtDecoder和令牌验证器来兼容Okta内部令牌,但自定义验证器未生效,仍因不支持的typ头导致验证失败。
相关配置信息
pom.xml
Spring Boot Version <version>3.4.1</version> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-webflux</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-oauth2-client</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-oauth2-resource-server</artifactId> </dependency>
应用配置
# Octa Security spring.security.oauth2.client.registration.okta.client-id=0oan0wkeiuzfUuHsw5d7 spring.security.oauth2.client.registration.okta.client-secret=uHLcvr9G8jT69Lmbxb_PoHJ7ltPeE5zd4PphpxrwNvD41gP7vHALXdF7CMMjdhr2 spring.security.oauth2.client.registration.okta.scope=openid, profile, email, offline_access spring.security.oauth2.client.registration.okta.provider=okta spring.security.oauth2.client.provider.okta.issuer-uri=https://dev-52900394.okta.com spring.security.oauth2.client.registration.okta.redirect-uri=http://localhost:8093/login/oauth2/code/okta
SecurityConfig
package com.gateway.ApiGateway.security; @Configuration @EnableWebFluxSecurity public class SecurityConfig { @Bean public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity httpSecurity) { httpSecurity .authorizeExchange(exchanges -> exchanges .anyExchange() .authenticated() ) .oauth2Login() .and() .oauth2ResourceServer() .jwt(); return httpSecurity.build(); } @Bean public ReactiveJwtDecoder jwtDecoder() { NimbusReactiveJwtDecoder jwtDecoder = NimbusReactiveJwtDecoder .withJwkSetUri("https://dev-52900394.okta.com/oauth2/v1/keys") .build(); // Custom JWT validator to allow Okta internal tokens OAuth2TokenValidator<Jwt> customValidator = jwt -> { String tokenType = (String) jwt.getHeaders().get("typ"); if (!"application/okta-internal-at+jwt".equals(tokenType) && !"JWT".equals(tokenType)) { throw new JwtValidationException("Invalid token type", null); } // Apply default validators (expiry, signature, etc.) return JwtValidators.createDefault().validate(jwt); }; jwtDecoder.setJwtValidator(customValidator); return jwtDecoder; } @Bean public ReactiveJwtAuthenticationConverter jwtAuthenticationConverter() { ReactiveJwtAuthenticationConverter converter = new ReactiveJwtAuthenticationConverter(); converter.setJwtGrantedAuthoritiesConverter(jwt -> { List<GrantedAuthority> authorities = ((List<String>) jwt.getClaims().getOrDefault("roles", List.of())) .stream() .map(SimpleGrantedAuthority::new) .collect(Collectors.toList()); return Flux.fromIterable(authorities); }); return converter; } }
测试请求
curl --location 'http://172.17.80.1:8093/questions' \ --header 'Authorization: Bearer eyJraWQiOiJaWTdvNEk5YWpMSmtQeENJNTFEVVJaQ1NvaEEtZjNKdFNDOWtLd1pva2ZnIiwidHlwIjoiYXBwbGljYXRpb25cL29rdGEtaW50ZXJuYWwtYXQrand0IiwiYWxnIjoiUlMyNTYifQ.eyJ2ZXIiOjEsImp0aSI6IkFULm5UTDRlRUxBc1RPRUVqZ21HMFlRc2NFdEQwbUlveXdhbDRzeXd4ODJWWTgub2FyMndybTE3ZEFYdllkdHA1ZDciLCJpc3MiOiJodHRwczovL2Rldi01MjkwMDM5NC5va3RhLmNvbSIsImF1ZCI6Imh0dHBzOi8vZGV2LTUyOTAwMzk0Lm9rdGEuY29tIiwic3ViIjoicmF2aWRvYmFyaXlhOTUzN0BnbWFpLmNvbSIsImlhdCI6MTczODIxODc4NiwiZXhwIjoxNzM4MjIyMzg2LCJjaWQiOiIwb2FuMHdrZWl1emZVdUhzdzVkNyIsInVpZCI6IjAwdW4wd202NTRrb3NZRm5mNWQ3Iiwic2NwIjpbIm9wZW5pZCIsInByb2ZpbGUiLCJlbWFpbCIsIm9mZmxpbmVfYWNjZXNzIl0sImF1dGhfdGltZSI6MTczODIxODc4Mn0.m4VJTDqpCl64PbE9dGYtzKokrU7Gf82UHfbPcroyVx4bO_psuLAjgzIinbqhbuiVRfHlN-nLGLS7jzSwY6Gib5uE1Fo-ioxkq1TUs7_bOYOI82O2XMoIe8H970FeMqprSZVdXYPMfEx5JlFdGSbl6rkQ6OG9QRRfkCk-GeZpG5O5BzE1eb9vMwO09oHntKaruv0OQJrIFu2lK1wxtfVcSBkM7xeTAWqrCQmHmuEjrj58PJFXU0Ci5RZgX6Yipp5LS0IhR7YnDmowy64PPkc9D1hOOJDxuDivT_X7-OcLhOZbH4bOhegr52va79nmMxquJlOsnE56xNCcCLA72xo6Aw' \ --header 'Cookie: SESSION=928008dd-57a2-470a-9565-13e606aaeb5d'
报错信息
org.springframework.security.oauth2.server.resource.InvalidBearerTokenException: Failed to validate the token at org.springframework.security.oauth2.server.resource.authentication.JwtReactiveAuthenticationManager.onError(JwtReactiveAuthenticationManager.java:79) ~[spring-security-oauth2-resource-server-6.4.2.jar:6.4.2] at reactor.core.publisher.Mono.lambda$onErrorMap$28(Mono.java:3848) ~[reactor-core-3.7.1.jar:3.7.1] at reactor.core.publisher.Mono.lambda$onErrorResume$30(Mono.java:3938) ~[reactor-core-3.7.1.jar:3.7.1] at reactor.core.publisher.FluxOnErrorResume$ResumeSubscriber.onError(FluxOnErrorResume.java:94) ~[reactor-core-3.7.1.jar:3.7.1] at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:562) ~[netty-transport-4.1.116.Final.jar:4.1.116.Final] at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:997) ~[netty-common-4.1.116.Final.jar:4.1.116.Final] at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74) ~[netty-common-4.1.116.Final.jar:4.1.116.Final] at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30) ~[netty-common-4.1.116.Final.jar:4.1.116.Final] at java.base/java.lang.Thread.run(Thread.java:1583) ~[na:na] Caused by: org.springframework.security.oauth2.jwt.BadJwtException: Failed to validate the token at org.springframework.security.oauth2.jwt.NimbusReactiveJwtDecoder.createClaimsSet(NimbusReactiveJwtDecoder.java:295) ~[spring-security-oauth2-jose-6.4.2.jar:6.4.2] at org.springframework.security.oauth2.jwt.NimbusReactiveJwtDecoder$JwkSetUriReactiveJwtDecoderBuilder.lambda$processor$13(NimbusReactiveJwtDecoder.java:449) ~[spring-security-oauth2-jose-6.4.2.jar:6.4.2] at reactor.core.publisher.FluxMap$MapSubscriber.onNext(FluxMap.java:106) ~[reactor-core-3.7.1.jar:3.7.1] ... 232 common frames omitted Caused by: com.nimbusds.jose.proc.BadJOSEException: JOSE header typ (type) application/okta-internal-at+jwt not allowed at com.nimbusds.jose.proc.DefaultJOSEObjectTypeVerifier.verify(DefaultJOSEObjectTypeVerifier.java:149) ~[nimbus-jose-jwt-9.37.3.jar:9.37.3] at com.nimbusds.jwt.proc.DefaultJWTProcessor.process(DefaultJWTProcessor.java:341) ~[nimbus-jose-jwt-9.37.3.jar:9.37.3] at com.nimbusds.jwt.proc.DefaultJWTProcessor.process(DefaultJWTProcessor.java:303) ~[nimbus-jose-jwt-9.37.3.jar:9.37.3] at org.springframework.security.oauth2.jwt.NimbusReactiveJwtDecoder.createClaimsSet(NimbusReactiveJwtDecoder.java:292) ~[spring-security-oauth2-jose-6.4.2.jar:6.4.2] ... 234 common frames omitted
解决方案
问题根源在于Nimbus库的DefaultJOSEObjectTypeVerifier默认只允许JWT类型的令牌,且这个验证逻辑在Spring Security自定义验证器执行前就会触发,导致自定义验证器无法生效。
需要直接修改Nimbus的JWTProcessor配置,添加允许的JOSE令牌类型:
@Bean public ReactiveJwtDecoder jwtDecoder() { NimbusReactiveJwtDecoder jwtDecoder = NimbusReactiveJwtDecoder .withJwkSetUri("https://dev-52900394.okta.com/oauth2/v1/keys") // 自定义Nimbus JWT处理器,添加允许的令牌类型 .jwtProcessorCustomizer(jwtProcessor -> { DefaultJOSEObjectTypeVerifier verifier = new DefaultJOSEObjectTypeVerifier(); verifier.setAllowedTypes( new JOSEObjectType("JWT"), new JOSEObjectType("application/okta-internal-at+jwt") ); jwtProcessor.setJOSEObjectTypeVerifier(verifier); }) .build(); // 保留原有自定义验证逻辑(用于额外业务校验) OAuth2TokenValidator<Jwt> customValidator = jwt -> { String tokenType = (String) jwt.getHeaders().get("typ"); if (!"application/okta-internal-at+jwt".equals(tokenType) && !"JWT".equals(tokenType)) { throw new JwtValidationException("Invalid token type", null); } return JwtValidators.createDefault().validate(jwt); }; jwtDecoder.setJwtValidator(customValidator); return jwtDecoder; }
说明
- 通过
jwtProcessorCustomizer直接修改Nimbus的核心处理器,让它认可application/okta-internal-at+jwt类型的令牌,解决底层验证拦截问题。 - 原有的自定义验证器可以保留,用于补充业务层面的额外校验规则。
修改后重启服务,使用原测试请求调用即可通过验证。
内容的提问来源于stack exchange,提问作者Ravi
相关产品推荐
相关产品推荐

