1. 概述

JSON Web 令牌 (JWT) 通常用于 REST API 安全。尽管Spring Security OAuth等框架可以解析JWT,但我们可能希望在自己的代码中处理token。

在本教程中,我们将学习如何解码并验证 JWT 的完整性

2. JWT 结构分析

首先,让我们了解一下 JWT 的数据结构:

  • header - 标头
  • payload - 负载数据
  • signature - 签名

签名是可选的。一个有效的 JWT 可以只包含header和payload部分。不过,我们一般都会使用签名部分来验证标头和负载数据的内容是否被篡改,以获得安全授权

JWT各部分使用 base64url-encoded 方式编码字符串,并以点(‘.’) 分隔符进行分隔。根据设计,任何人都可以解码 JWT 并读取header和payload的内容。但我们还需要验证签名,以防止数据作假。

通常,JWT 包含用户的 "声明(claims)"。这些表示用户相关的数据,应用程序可以用这些数据授予权限或跟踪提供token的用户。解码token可让应用程序使用数据,而验证则可让应用程序相信 JWT 是由可信来源生成的。

下面让我们看看如何在 Java 中解码和验证 JWT。

3. 解码 JWT

我们可以使用Java内置的函数对token进行解码。

首先,我们根据点“.”分隔符,将token拆分:

String[] chunks = token.split("\\.");

注意,“.” 在正则表达式中是有特殊含义的,所以使用String.split时需要使用转义字符。

下面解析出 header 和 payload:

Base64.Decoder decoder = Base64.getUrlDecoder();

String header = new String(decoder.decode(chunks[0]));
String payload = new String(decoder.decode(chunks[1]));

我们用下面这段 JWT 测试代码(也可以使用 在线解码 工具来对比结果):

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkJhZWxkdW5nIFVzZXIiLCJpYXQiOjE1MTYyMzkwMjJ9.qH7Zj_m3kY69kxhaQXTa-ivIpytKXXjZc1ZSmapZnGE

解析后的输出结果:

{"alg":"HS256","typ":"JWT"}{"sub":"1234567890","name":"Baeldung User","iat":1516239022}

如果 JWT 中只定义了header和payload部分,我们的解码工作就完成了。

4. 校验 JWT

接下来,我们可以使用签名部分来验证报文头和负载数据的完整性,确保它们未被篡改。

4.1. Maven 依赖

本文我们使用 jjwt 依赖库:

<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.7.0</version>
</dependency>

4.2. 配置签名算法和 Key Specification

要开始验证payload和header前,我们需要最初用来签署token的签名算法和秘钥:

SignatureAlgorithm sa = SignatureAlgorithm.HS256;
SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey.getBytes(), sa.getJcaName());

在本例中,我们将签名算法写死为 HS256。实际中,我们可以解码报头的 JSON 并读取 alg 字段来获取该值。

我们还应该注意,变量 secretKey 是密钥的字符串表示。我们可以通过配置或发布 JWT 的服务提供的 REST API 将其提供给应用程序。

4.3. 执行验证

现在我们有了签名算法和秘钥,就可以开始进行验证了。

首先我们把header和payload字符串使用 "." 拼接起来:

String tokenWithoutSignature = chunks[0] + "." + chunks[1];
String signature = chunks[2];

我们有了原始未签名的字符串和签名过的。现在我们可以使用jjtw库来验证它:

DefaultJwtSignatureValidator validator = new DefaultJwtSignatureValidator(sa, secretKeySpec);

if (!validator.isValid(tokenWithoutSignature, signature)) {
    throw new Exception("Could not verify JWT token integrity!");
}

让我们来分析一下。

首先,我们用选定的算法和秘密创建一个验证器。然后,我们向它传入未签名的token数据和所提供的签名。

然后,验证器生成一个新的签名,并与提供的签名进行比较。如果两者相等,我们就验证了header和payload的完整性。

5. 总结

在本文中,我们了解了 JWT 的结构以及如何将其解码为 JSON。

然后,我们使用一个库,利用其签名、算法和秘钥验证令牌的完整性。

最后本文的代码,可以在GitHub上找到