实现OAuth资源服务器时遇Jwt解码错误:Malformed Jwk set
OAuth资源服务器JWT解析错误:Malformed Jwk set
问题现象
向测试用OAuth资源服务器的受保护API发送携带access token的请求时,触发以下错误:
An error occurred while attempting to decode the Jwt: Malformed Jwk set.
认证服务器配置
@Bean @Order(1) public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http); http.getConfigurer(OAuth2AuthorizationServerConfigurer.class) .oidc(Customizer.withDefaults()); // 启用OpenID Connect 1.0 http // 未认证时从授权端点重定向到登录页 .exceptionHandling((exceptions) -> exceptions .defaultAuthenticationEntryPointFor( new LoginUrlAuthenticationEntryPoint("/login"), new MediaTypeRequestMatcher(MediaType.TEXT_HTML) )) // 接受用于用户信息和/或客户端注册的access token .oauth2ResourceServer((resourceServer) -> resourceServer .jwt(Customizer.withDefaults())); return http.build(); } @Bean @Order(2) public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests((authorize) -> authorize .requestMatchers("/jkws").permitAll() .anyRequest().authenticated() ); // 表单登录处理来自授权服务器过滤器链的登录页重定向 http.formLogin(Customizer.withDefaults()); http.csrf(csrf -> csrf.disable()); http.cors(cors -> cors.disable()); return http.build(); } @Bean public RegisteredClientRepository registeredClientRepository() { RegisteredClient oidcClient = RegisteredClient.withId(UUID.randomUUID().toString()) .clientId("oidc-client") //.clientSecret("{noop}secret") .clientSecret("$2a$04$R.Nl31CwJtKOtCDcE69e8OO0O8ryb8Rx2vhcUS5wde6Zpj2750kt2") .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC) .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN) .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS) .redirectUri("https://oauth.pstmn.io/v1/callback") .postLogoutRedirectUri("http://127.0.0.1:8080/") .scope(OidcScopes.OPENID) .scope(OidcScopes.PROFILE) .clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build()) .build(); return new InMemoryRegisteredClientRepository(oidcClient); } @Bean public JWKSource<SecurityContext> jwkSource() { KeyPair keyPair = generateRsaKey(); RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic(); RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate(); RSAKey rsaKey = new RSAKey.Builder(publicKey) .privateKey(privateKey) .keyID(UUID.randomUUID().toString()) .build(); JWKSet jwkSet = new JWKSet(rsaKey); return new ImmutableJWKSet<>(jwkSet); } private static KeyPair generateRsaKey() { KeyPair keyPair; try { KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA"); keyPairGenerator.initialize(2048); keyPair = keyPairGenerator.generateKeyPair(); } catch (Exception ex) { throw new IllegalStateException(ex); } return keyPair; } @Bean public JwtDecoder jwtDecoder(JWKSource<SecurityContext> jwkSource) { return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource); } @Bean public AuthorizationServerSettings authorizationServerSettings() { return AuthorizationServerSettings.builder().jwkSetEndpoint("/jwks").build(); } @Bean public OAuth2TokenCustomizer<JwtEncodingContext> jwtTokenCustomizer() { return (context) -> { if (OAuth2TokenType.ACCESS_TOKEN.equals(context.getTokenType())) { context.getClaims().claims((claims) -> { Set<String> roles = AuthorityUtils.authorityListToSet(context.getPrincipal().getAuthorities()) .stream() .map(c -> c.replaceFirst("^ROLE_", "")) .collect(Collectors.collectingAndThen(Collectors.toSet(), Collections::unmodifiableSet)); claims.put("roles", roles); }); } }; }
资源服务器配置
@Value("${spring.security.oauth2.resourceserver.jwt.jwk-set-uri}") private String jwkSetUri; @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests((authorize) -> authorize .anyRequest().authenticated() ) .oauth2ResourceServer((oauth2) -> oauth2 .jwt(Customizer.withDefaults()) ); return http.build(); } /* * http://localhost:8080/.well-known/openid-configuration */ @Bean public JwtDecoder jwtDecoder() { return NimbusJwtDecoder .withJwkSetUri(jwkSetUri) .jwsAlgorithm(SignatureAlgorithm.RS256).build(); }
资源服务器属性
spring.security.oauth2.resourceserver.jwt.jwk-set-uri=http://localhost:8080/jkws
问题根源与修复方案
核心问题:端点路径拼写不一致
- 认证服务器通过
AuthorizationServerSettings配置的JWK集端点是/jwks,但defaultSecurityFilterChain中开放匿名访问的路径写成了/jkws(字母顺序错误)。 - 资源服务器配置的
jwk-set-uri同样错误地指向了/jkws,导致请求到不存在的端点,返回的内容并非合法的JWK集,最终触发解析错误。
修复步骤
修正认证服务器的权限配置
将defaultSecurityFilterChain中的requestMatchers("/jkws").permitAll()修改为:.requestMatchers("/jwks").permitAll()修正资源服务器的配置属性
将资源服务器的配置文件中的属性改为:spring.security.oauth2.resourceserver.jwt.jwk-set-uri=http://localhost:8080/jwks
验证修复
完成修改后,直接访问http://localhost:8080/jwks,确认返回的是标准的JWK集JSON格式,示例如下:
{ "keys": [ { "kty": "RSA", "e": "AQAB", "use": "sig", "kid": "xxxxxx", "alg": "RS256", "n": "xxxxxx" } ] }
内容的提问来源于stack exchange,提问作者Kapil
相关产品推荐
相关产品推荐

