You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

自定义JWT信息后,OAuth2资源服务器取公钥时getDecodedDetails返回null

问题:通过端点获取JWT公钥时,getDecodedDetails()返回null

我自定义了OAuth2授权服务器,为JWT添加了自定义信息,期望资源服务器通过授权服务器的/oauth/token_key端点动态获取验证公钥,但调用OAuth2AuthenticationDetails.getDecodedDetails()始终返回null。


现有代码结构

授权服务器相关代码

自定义Token增强器

public class CustomTokenEnhancer implements TokenEnhancer {
    @Override
    public OAuth2AccessToken enhance(OAuth2AccessToken oauth2AccessToken,
            OAuth2Authentication oauth2Authentication) {
        var customToken = new DefaultOAuth2AccessToken(oauth2AccessToken);
        Map<String, Object> customInfo = Map.of("generatedIn", "Year " + LocalDateTime.now().getYear());
        customToken.setAdditionalInformation(customInfo);
        return customToken;
    }
}

授权服务器配置类

@Configuration
@EnableAuthorizationServer
public class AuthServerConfig extends AuthorizationServerConfigurerAdapter{
    @Value("${password}")
    private String password;
    
    @Value("${privateKey}")
    private String privateKey;
    
    @Value("${alias}")
    private String alias;
    
    @Autowired
    private AuthenticationManager authenticationManager;

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()
               .withClient("client")
               .secret("secret")
               .authorizedGrantTypes("password", "refresh_token")
               .scopes("read")
               .and()
               .withClient("resourceserver")
               .secret("resourceserversecret");
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
        TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
        var tokenEnhancers = List.of(new CustomTokenEnhancer(), jwtAccessTokenConverter());
        tokenEnhancerChain.setTokenEnhancers(tokenEnhancers);
        
        endpoints.authenticationManager(authenticationManager)
              .tokenStore(tokenStore())
              .tokenEnhancer(tokenEnhancerChain);
    }

    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security.tokenKeyAccess("isAuthenticated()");
    }

    @Bean
    public TokenStore tokenStore() {
        return new JwtTokenStore(jwtAccessTokenConverter());
    }

    @Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter() {
        var converter = new JwtAccessTokenConverter();
        KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(
                new ClassPathResource(privateKey),
                password.toCharArray()
        );
        converter.setKeyPair(keyStoreKeyFactory.getKeyPair(alias));
        return converter;
    }
}

授权服务器application.properties

password = somepassword
privateKey =key.jks
alias = somekey

资源服务器初始配置

资源服务器配置类

@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
}

资源服务器application.properties

server.port = 9090
security.oauth2.resource.jwt.key-uri=http://localhost:8080/oauth/token_key
security.oauth2.client.client-id=resourceserver
security.oauth2.client.client-secret=resourceserversecret

受保护端点

@RestController
public class HelloController {
    @GetMapping("/hello")
    public String hello(OAuth2Authentication authentication) {
        OAuth2AuthenticationDetails details =
                (OAuth2AuthenticationDetails) authentication.getDetails();
        return details.getDecodedDetails().toString();
    }
}

执行curl -H "Authorization:Bearer e1yhrjkkkfk....." http://localhost:9090/hello时,details.getDecodedDetails()返回null。


已知可行但不符合需求的方案

在资源服务器本地配置公钥,并自定义JwtAccessTokenConverter将JWT claims设置到authentication details时,代码能正常工作,但我不想在资源服务器存储公钥,希望通过端点动态获取。

可行但不满足需求的资源服务器配置

@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter{
    @Value("${publicKey}")
    private String publicKey;

    @Bean
    public TokenStore tokenStore() {
        return new JwtTokenStore(jwtAccessTokenConverter());
    }

    @Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter() {
        var converter = new OtherAccessTokenConverter();
        converter.setVerifierKey(publicKey);
        return converter;
    }
}

自定义AccessTokenConverter

public class OtherAccessTokenConverter extends JwtAccessTokenConverter {
    @Override
    public OAuth2Authentication extractAuthentication(Map<String, ?> map) {
        var authentication = super.extractAuthentication(map);
        authentication.setDetails(map);
        return authentication;
    }
}

解决方案

核心原理

当配置security.oauth2.resource.jwt.key-uri时,Spring Security会自动通过端点获取公钥,但默认的JwtAccessTokenConverter不会将JWT的claims映射到OAuth2AuthenticationDetails中。我们只需要自定义转换器保留claims映射逻辑,同时复用Spring的动态公钥获取机制即可。

步骤1:自定义JwtAccessTokenConverter

public class CustomJwtAccessTokenConverter extends JwtAccessTokenConverter {
    @Override
    public OAuth2Authentication extractAuthentication(Map<String, ?> claims) {
        OAuth2Authentication authentication = super.extractAuthentication(claims);
        // 将JWT的所有claims设置到authentication的details中
        authentication.setDetails(claims);
        return authentication;
    }
}

步骤2:配置资源服务器使用自定义转换器

只需注册自定义的JwtAccessTokenConverter Bean,Spring会自动结合key-uri配置的端点获取公钥:

@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {

    @Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter() {
        return new CustomJwtAccessTokenConverter();
    }
}

步骤3:验证端点逻辑

保持原端点代码不变,现在details.getDecodedDetails()会返回包含自定义信息的JWT claims:

@RestController
public class HelloController {
    @GetMapping("/hello")
    public String hello(OAuth2Authentication authentication) {
        OAuth2AuthenticationDetails details =
                (OAuth2AuthenticationDetails) authentication.getDetails();
        Map<String, Object> decodedDetails = (Map<String, Object>) details.getDecodedDetails();
        return "Generated in: " + decodedDetails.get("generatedIn");
    }
}

内容的提问来源于stack exchange,提问作者Chinedu

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.25 01:06:26