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

Azure SSO令牌签名验证问题:Spring Boot处理非法Base64字符密钥

问题解决指南:Azure SSO令牌签名验证失败

核心问题分析

  • 密钥来源错误:你当前使用的Azure客户端密钥是用于客户端身份认证(如客户端凭证流)的凭证,并非验证RS256令牌签名的公钥。RS256格式的令牌由Azure AD租户的私钥签名,后端必须使用Azure AD租户的公钥才能完成签名验证,和客户端密钥无关。
  • Base64编码格式不匹配:即使拿到正确的公钥,Azure返回的是Base64 URL安全编码(用-替代+、_替代/,可能省略填充符=),而你使用的标准Base64解码器无法处理这种格式,导致解码失败。

解决方案步骤

1. 获取正确的Azure AD签名公钥

Azure AD提供JWKS(JSON Web Key Set)端点,用于获取当前有效的租户签名公钥,端点格式为:

https://login.microsoftonline.com/{你的租户ID}/discovery/v2.0/keys

发送HTTP请求获取该端点的JSON数据,从中匹配令牌头部kid(密钥ID)对应的公钥条目,提取x5c字段(X.509证书链)或n/e字段来构建公钥。

2. 修正Base64解码逻辑

如果手动处理公钥,需适配Base64 URL安全编码规则:

public PublicKey generateJwtKeyDecryption(String jwtPublicKey) throws NoSuchAlgorithmException, InvalidKeySpecException {
    // 转换为标准Base64格式
    jwtPublicKey = jwtPublicKey.replace('-', '+').replace('_', '/');
    // 补全缺失的填充符
    int padding = jwtPublicKey.length() % 4;
    if (padding > 0) {
        jwtPublicKey += "====".substring(padding);
    }
    
    KeyFactory keyFactory = KeyFactory.getInstance("RSA");
    byte[] keyBytes = Base64.getDecoder().decode(jwtPublicKey);
    X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(keyBytes);
    return keyFactory.generatePublic(x509EncodedKeySpec);
}

或直接使用URL安全解码器:

byte[] keyBytes = Base64.getUrlDecoder().decode(jwtPublicKey);

3. 推荐使用Spring Security Azure AD Starter(简化实现)

无需手动编写JWT解析代码,引入官方依赖后自动处理令牌验证:

<!-- Maven依赖 -->
<dependency>
    <groupId>com.azure.spring</groupId>
    <artifactId>spring-cloud-azure-starter-active-directory</artifactId>
</dependency>

配置文件application.yml:

spring:
  cloud:
    azure:
      active-directory:
        tenant-id: {你的租户ID}
        client-id: {你的客户端ID}

该依赖会自动从JWKS端点获取公钥、验证签名并提取声明,无需手动处理密钥和解码逻辑。

4. 手动实现令牌验证的优化方案

若坚持手动处理,需先解析令牌头部获取kid,再匹配JWKS中的对应公钥:

public Claims extractAllClaims(String token) {
    try {
        // 解析令牌头部获取密钥ID
        JwtHeader header = Jwts.parserBuilder().build().parseClaimsJws(token).getHeader();
        String kid = header.getKeyId();
        
        // 从JWKS缓存中获取对应kid的公钥(需自行实现JWKS的获取与缓存逻辑)
        String validPublicKey = getPublicKeyFromJwksCache(kid);
        
        return Jwts.parserBuilder()
                .setSigningKey(generateJwtKeyDecryption(validPublicKey))
                .build()
                .parseClaimsJws(token)
                .getBody();
    } catch (JwtException | IllegalArgumentException | NoSuchAlgorithmException | InvalidKeySpecException e) {
        e.printStackTrace();
    }
    return null;
}

关键注意事项

  • 禁止将客户端密钥用于令牌签名验证,两者用途完全独立:客户端密钥是客户端向Azure AD证明身份的凭证,令牌签名公钥是Azure AD用来证明令牌合法性的公钥。
  • 缓存JWKS中的公钥,避免每次请求都调用JWKS端点,减少延迟和请求开销。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.17 18:23:09