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

如何配置Spring授权服务器使用现有JWKS密钥签名JWT并适配多授权服务

问题解答

一、双授权服务器+单资源服务器方案的可行性

该方案完全可行,核心逻辑成立:只要两个授权服务器使用同一组非对称密钥的私钥签名JWT,资源服务器通过对应的公钥JWKS验证签名,就能实现跨授权服务器的令牌校验。但需满足几个前提:

  • 两个授权服务器使用完全相同的私钥进行签名(确保签名后的令牌能被同一JWKS公钥验证)
  • 令牌的核心声明(如iss签发者、aud受众)需符合资源服务器的验证规则(若资源服务器开启了这些声明的校验)
  • 签名算法保持一致(如统一使用RS256)

二、授权服务器配置错误分析与修复

你遇到的JwkException, This operation is not supported错误,原因是**JwkTokenStore仅适用于资源服务器的令牌验证,不支持授权服务器的令牌生成/写入操作**。授权服务器需要使用自包含的JWT存储方案,并配置私钥完成签名。

正确的授权服务器配置步骤

  1. 替换JwkTokenStore为JwtTokenStore(JWT是自包含令牌,无需额外存储)
  2. 配置JwtAccessTokenConverter使用与对方授权服务器一致的私钥签名
  3. 注入AuthenticationManager(密码授权模式必须依赖此组件)

完整配置代码

@Configuration
@EnableAuthorizationServer
public class AuthServerConfig extends AuthorizationServerConfigurerAdapter {

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Autowired
    private AuthenticationManager authenticationManager;

    // 注入私钥资源(需从对方授权服务器获取对应私钥,可存储为本地文件)
    @Value("classpath:auth-private-key.pem")
    private Resource privateKeyResource;

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()
            .withClient("your-client-id")
            .secret(passwordEncoder.encode("your-client-secret"))
            .scopes("read")
            .accessTokenValiditySeconds(300)
            .refreshTokenValiditySeconds(86400)
            .authorizedGrantTypes("client_credentials", "password", "refresh_token");
    }

    @Bean
    public TokenStore tokenStore() {
        // 使用JwtTokenStore,无需持久化存储令牌
        return new JwtTokenStore(accessTokenConverter());
    }

    @Bean
    public JwtAccessTokenConverter accessTokenConverter() throws IOException {
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        // 加载私钥用于签名JWT
        String privateKey = new String(Files.readAllBytes(Paths.get(privateKeyResource.getURI())));
        converter.setSigningKey(privateKey);
        
        // 可选:若需验证自身生成的令牌(如刷新令牌场景),可配置对应公钥
        // String publicKey = ""; // 可从对方JWKS获取或从私钥推导
        // converter.setVerifierKey(publicKey);
        
        return converter;
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.tokenStore(tokenStore())
                 .accessTokenConverter(accessTokenConverter())
                 .authenticationManager(authenticationManager); // 密码授权必须配置
    }
}

额外注意事项

  • 必须获取对方授权服务器JWKS对应的私钥(JWKS默认仅暴露公钥,需对方提供签名用私钥)
  • 若资源服务器校验iss声明,需确保两个授权服务器生成的JWT的iss值一致,或调整资源服务器关闭iss校验
  • 密码授权模式需在Spring Security配置中开启AuthenticationManager暴露:
    @Configuration
    @EnableWebSecurity
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
        @Override
        @Bean
        public AuthenticationManager authenticationManagerBean() throws Exception {
            return super.authenticationManagerBean();
        }
    }
    

三、资源服务器配置优化

你的资源服务器配置存在冗余,oauth2ResourceServer().jwt()已经内置了JWKS验证逻辑,无需手动配置JwkTokenStore和DefaultTokenServices,可简化为:

@Configuration
@EnableResourceServer
@Order(3)
@Slf4j
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {

    @Value("${spring.security.oauth2.resourceserver.jwt.jwk-set-uri}")
    private String jwkSetUri;
    @Autowired
    private CustomAccessTokenConverter customAccessTokenConverter;

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
            .anyRequest().authenticated()
            .and()
            .oauth2ResourceServer()
            .jwt()
            .jwtAuthenticationConverter(jwtAuthenticationConverter());
    }

    @Bean
    public JwtAuthenticationConverter jwtAuthenticationConverter() {
        JwtAuthenticationConverter converter = new JwtAuthenticationConverter();
        // 结合自定义的AccessTokenConverter逻辑处理认证信息
        converter.setAuthenticationConverter(jwt -> {
            Map<String, Object> claims = jwt.getClaims();
            OAuth2Authentication auth = customAccessTokenConverter.extractAuthentication(claims);
            return new UsernamePasswordAuthenticationToken(auth.getPrincipal(), null, auth.getAuthorities());
        });
        return converter;
    }

    @Override
    public void configure(ResourceServerSecurityConfigurer config) {
        config.resourceId(null);
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.15 12:50:26