JWT 签名验证失败:Java 与 PHP 互操作问题解决方案
JWT 签名验证失败:Java 与 PHP 互操作问题解决方案
问题描述
在跨语言 JWT 实现中,当使用 Java (JJWT 库) 生成 JWT 令牌,然后尝试在 PHP (php-jwt 库) 中验证时,出现了签名验证失败的问题。
Java 原始错误代码
JwtBuilder jwtBuilder = Jwts.builder();
String token = jwtBuilder
.claim("access_key","6uVG1xmibb3EX7XhUV3g6jflPidNhNon")
.claim("header","MARKETING_MALL")
.setExpiration(new Date(System.currentTimeMillis() + 1000*3600*24))
.signWith(SignatureAlgorithm.HS256, "hj4iRrB2DEGAMDRHzVYFed14weSN")
.compact();
header.add("Authorization", "Bearer "+token);
PHP 验证代码
$result = JWT::decode("java的jwt token", "hj4iRrB2DEGAMDRHzVYFed14weSN", ['HS256']);
问题原因
经过调查发现,问题根源在于 Java 的 JJWT 库和 PHP 的 php-jwt 库对密钥的处理方式不同:
- JJWT (Java) 要求签名密钥是 Base64 编码的字符串
- php-jwt (PHP) 则直接使用原始字符串作为密钥
这种不一致性导致了签名验证失败,因为两边使用的实际签名密钥不同。
解决方案
Java 端正确实现
需要对密钥进行 Base64 编码后再用于签名:
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.impl.TextCodec;
import java.util.Date;JwtBuilder jwtBuilder = Jwts.builder();
String token = jwtBuilder
.claim("access_key","6uVG1xmibb3EX7XhUV3g6jflPidNhNon")
.claim("header","MARKETING_MALL")
.setExpiration(new Date(System.currentTimeMillis() + 1000*3600*24))
.signWith(SignatureAlgorithm.HS256, TextCodec.BASE64.encode("hj4iRrB2DEGAMDRHzVYFed14weSN"))
.compact();
header.add("Authorization", "Bearer "+token);
PHP 端保持不变
PHP 端可以继续使用原始字符串密钥进行验证:
use \Firebase\JWT\JWT;$secretKey = "hj4iRrB2DEGAMDRHzVYFed14weSN";
$token = "从Java获取的JWT令牌"; // 实际使用中替换为从请求头获取的tokentry {
$decoded = JWT::decode($token, $secretKey, ['HS256']);
// 验证成功,处理业务逻辑
print_r($decoded);
} catch (Exception $e) {
// 验证失败处理
http_response_code(401);
echo json_encode(array(
"message" => "Access denied.",
"error" => $e->getMessage()
));
}
技术原理
JJWT 的密钥处理
JJWT 库在内部对 HMAC-SHA 签名算法的密钥有以下要求:
- 密钥必须是 Base64 编码的字符串
- 这是为了确保密钥的二进制数据能够正确传递
- 使用
TextCodec.BASE64.encode()
方法对原始密钥进行编码
php-jwt 的密钥处理
php-jwt 库则更加灵活:
- 可以直接接受原始字符串作为密钥
- 内部会自动处理密钥的编码转换
- 不需要开发者预先对密钥进行 Base64 编码
最佳实践建议
- 密钥标准化:在跨语言系统中,建议统一约定密钥的格式(是否 Base64 编码)
- 文档记录:在系统文档中明确说明密钥的处理方式
- 测试验证:实现后使用在线 JWT 调试工具(如 jwt.io)验证令牌能否被正确解析
- 错误处理:在两端都实现完善的错误处理机制,便于排查问题
扩展知识
如果需要从 PHP 生成 JWT 供 Java 验证,也需要考虑密钥处理方式的一致性问题。在 PHP 中生成 JWT 时:
use \Firebase\JWT\JWT;$secretKey = "hj4iRrB2DEGAMDRHzVYFed14weSN"; // 原始密钥
$payload = array(
"access_key" => "6uVG1xmibb3EX7XhUV3g6jflPidNhNon",
"header" => "MARKETING_MALL",
"exp" => time() + 3600*24
);$jwt = JWT::encode($payload, $secretKey, 'HS256');
Java 端验证时需要知道 PHP 使用的是原始密钥,因此 Java 端应使用:
String token = "从PHP获取的JWT令牌"; // 实际使用中替换为从请求头获取的token
Jwts.parser()
.setSigningKey("hj4iRrB2DEGAMDRHzVYFed14weSN") // 直接使用原始密钥
.parseClaimsJws(token);
这种对称性处理可以确保跨语言互操作性。