Java/JJWT为Office 365 OAuth客户端凭据签名JWT时遇签名无效错误
解决JJWT生成证书签名JWT时的"Client assertion contains an invalid signature"错误
我之前帮不少开发者排查过这个问题,这个错误基本上都是JWT签名环节的某个细节没符合微软Azure AD的严格要求,咱们一步步来定位解决:
1. 先确认证书和私钥加载是否正确
很多时候问题出在证书加载上——要么密码错了,要么没拿到正确的私钥,或者证书格式不对。
比如加载PFX格式证书的正确姿势应该是这样的(Java代码):
KeyStore keyStore = KeyStore.getInstance("PKCS12"); try (InputStream is = new FileInputStream("your-certificate.pfx")) { // 这里的密码是PFX文件的保护密码 keyStore.load(is, "your-pfx-password".toCharArray()); } // 注意:别名要和PFX里的一致,不确定的话用keytool命令查看 String alias = "your-cert-alias"; PrivateKey privateKey = (PrivateKey) keyStore.getKey(alias, "your-pfx-password".toCharArray()); X509Certificate cert = (X509Certificate) keyStore.getCertificate(alias);
如果不知道证书别名,打开终端用这个命令查看:
keytool -list -v -keystore your-certificate.pfx -storetype pkcs12
输出里的Alias name就是你要填的别名。
2. 严格匹配微软要求的JWT声明
Azure AD对客户端断言的JWT有硬性要求,少一个字段或者填错都不行,必须包含这些:
iss: 你的Azure AD应用的Client IDsub: 和iss完全一致,也是Client IDaud: 微软令牌端点的完整URL,比如v2端点是https://login.microsoftonline.com/{你的租户ID}/oauth2/v2.0/token,v1端点是https://login.microsoftonline.com/{你的租户ID}/oauth2/token,别搞混版本exp: 过期时间,不能超过1小时,建议设为当前时间+30分钟nbf: 生效时间,一般设为当前时间jti: 随机生成的唯一字符串,用来防止重放攻击
用JJWT构建的示例代码:
String clientId = "your-client-id"; String tenantId = "your-tenant-id"; String tokenEndpoint = String.format("https://login.microsoftonline.com/%s/oauth2/v2.0/token", tenantId); String jwtAssertion = Jwts.builder() .setIssuer(clientId) .setSubject(clientId) .setAudience(tokenEndpoint) .setExpiration(new Date(System.currentTimeMillis() + 30 * 60 * 1000)) // 30分钟后过期 .setNotBefore(new Date(System.currentTimeMillis())) .setId(UUID.randomUUID().toString()) // 唯一ID .signWith(privateKey, SignatureAlgorithm.RS256) // 必须用RS256算法! .compact();
⚠️ 重点:签名算法必须是RS256,微软不支持HS256用于证书类型的客户端断言,别选错了。
3. 验证生成的JWT是否合法
把生成的JWT字符串拿到jwt.io上(本地解析就行,不用上传任何数据),检查这几点:
- 头部的
alg字段是不是RS256 - 声明部分的所有字段是否和你设置的一致,特别是
aud的URL要完全匹配令牌端点 - 用你的公钥(从PFX里导出或者Azure门户里的公钥)验证签名,如果本地都验证不通过,那肯定是私钥加载或者签名过程的问题
4. 检查Azure AD应用的配置
别漏了Azure门户里的配置:
- 你的应用已经上传了对应的公钥证书(要确保是和你签名用的私钥配对的公钥)
- 客户端凭据流已经启用,并且你申请的Graph API权限(比如
Calendars.Read)已经获得了管理员同意 - 不要混用v1和v2端点:如果你的应用是v2版本,就用v2的令牌端点,v1同理,
aud字段要和端点对应
5. 确认JJWT版本的兼容性
旧版本的JJWT可能在RS256签名的实现上有兼容性问题,建议用0.11.5及以上的版本。Maven依赖示例:
<dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-api</artifactId> <version>0.11.5</version> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-impl</artifactId> <version>0.11.5</version> <scope>runtime</scope> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-jackson</artifactId> <version>0.11.5</version> <scope>runtime</scope> </dependency>
按这个顺序排查下来,基本上就能解决签名无效的问题了。
内容的提问来源于stack exchange,提问作者Jon Moore
相关产品推荐
相关产品推荐

