Spring资源服务器如何配置多个jwk-set-uri兼容Google/Apple登录
Spring Boot 2.4.2 资源服务器同时校验Google、Apple JWT令牌实现方案
Spring Boot默认的spring.security.oauth2.resourceserver.jwt.jwk-set-uri配置仅支持单个令牌颁发方,要同时支持多个来源的JWT校验,需要自定义多租户路由逻辑,按JWT自带的iss(颁发者)声明匹配对应的JWK校验源,具体实现步骤如下:
1. 清理默认单颁发方配置
注释或删除application.properties中原有的Google单JWK地址配置,避免自动配置覆盖自定义逻辑:
# 注释掉原有单节点配置 # spring.security.oauth2.resourceserver.jwt.jwk-set-uri = https://www.googleapis.com/oauth2/v3/certs
确保项目已经引入spring-boot-starter-oauth2-resource-server依赖,Spring Boot 2.4.2默认携带的Spring Security 5.4.x、Nimbus JOSE组件完全支持以下逻辑,无需额外引入第三方依赖。
2. 编写安全配置类,定义多颁发方JWT解码器
新增资源服务器安全配置类,实现按iss路由的JWT解码器,同时做解码器缓存避免重复拉取JWK集影响性能:
import org.springframework.context.annotation.Bean; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.oauth2.core.DelegatingOAuth2TokenValidator; import org.springframework.security.oauth2.core.OAuth2TokenValidator; import org.springframework.security.oauth2.jwt.*; import org.springframework.security.web.SecurityFilterChain; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @EnableWebSecurity public class ResourceServerSecurityConfig { // 定义两个信任的令牌颁发方标识与对应JWK集地址 private static final String GOOGLE_ISSUER = "https://accounts.google.com"; private static final String GOOGLE_JWK_SET_URI = "https://www.googleapis.com/oauth2/v3/certs"; private static final String APPLE_ISSUER = "https://appleid.apple.com"; private static final String APPLE_JWK_SET_URI = "https://appleid.apple.com/auth/keys"; @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http .authorizeRequests(auth -> auth // 替换为自身业务的接口权限规则 .anyRequest().authenticated() ) .oauth2ResourceServer(oauth2 -> oauth2 .jwt(jwt -> jwt.decoder(multiTenantJwtDecoder())) ); return http.build(); } @Bean public JwtDecoder multiTenantJwtDecoder() { // 缓存已初始化的颁发方解码器,避免重复请求JWK地址、重复初始化解码器 Map<String, JwtDecoder> decoderCache = new ConcurrentHashMap<>(); return token -> { // 无签名校验解析JWT,仅提取载荷中的iss字段做路由依据 com.nimbusds.jwt.JWT parsedJwt = com.nimbusds.jwt.JWTParser.parse(token); String issuer = parsedJwt.getJWTClaimsSet().getIssuer(); // 按iss匹配对应解码器,缓存无命中则初始化新的解码器 JwtDecoder targetDecoder = decoderCache.computeIfAbsent(issuer, iss -> { JwtDecoder decoder; if (GOOGLE_ISSUER.equals(iss)) { decoder = buildJwtDecoderForIssuer(GOOGLE_JWK_SET_URI, GOOGLE_ISSUER); } else if (APPLE_ISSUER.equals(iss)) { decoder = buildJwtDecoderForIssuer(APPLE_JWK_SET_URI, APPLE_ISSUER); } else { throw new JwtValidationException("不支持的令牌颁发来源", null); } return decoder; }); // 用匹配到的解码器做完整校验:签名校验、过期时间校验、iss校验、aud校验 return targetDecoder.decode(token); }; } private JwtDecoder buildJwtDecoderForIssuer(String jwkSetUri, String expectedIssuer) { NimbusJwtDecoder decoder = NimbusJwtDecoder.withJwkSetUri(jwkSetUri).build(); // 组合校验规则:默认时间校验 + 指定颁发方校验 + 自定义客户端ID(aud)校验 OAuth2TokenValidator<Jwt> defaultValidator = JwtValidators.createDefaultWithIssuer(expectedIssuer); OAuth2TokenValidator<Jwt> audValidator = new JwtClaimValidator<>("aud", aud -> { // 替换为自身应用的Google客户端ID、Apple客户端ID/Service ID列表 return aud.equals("你的Google客户端ID") || aud.equals("你的Apple客户端ID"); }); decoder.setJwtValidator(new DelegatingOAuth2TokenValidator<>(defaultValidator, audValidator)); return decoder; } }
3. 配置注意事项
- 解码器缓存用
ConcurrentHashMap实现,性能和单JWK配置方案无差异,不会出现每次请求都拉取公钥的问题 - Apple返回的JWT的
aud字段对应你在Apple开发者后台配置的Service ID(Web端)或iOS应用Bundle ID(App端),多个合法aud直接追加到校验逻辑里即可 - 如果需要新增其他令牌颁发方,只需要在
multiTenantJwtDecoder方法里新增对应iss和JWK地址的判断分支,不需要修改其他逻辑 - 非信任颁发方的令牌会直接抛出校验异常,返回401未授权,符合资源服务器安全要求
内容的提问来源于stack exchange,提问作者kamal essafi
相关产品推荐
相关产品推荐

