详细理解 JWT(Json Web Token)

在分布式微服务架构中,特别是跨域访问的情况下,通常会使用 JWT 技术来实现安全认证。

JSON Web Tokens 是一种开放的、行业标准的 RFC7519 规范,用于安全地表示双方之间的声明。

JWT 官网JWT IntroductionAuth0 > JWT

JWT 介绍及结构

学一门技术,优先阅读官方文档,才能系统地了解并理解其概念和应用。

JWT 简介

JWT 是 Json Web Token 的简写,是一种开放标准(RFC 7519),它定义了一种简洁独立的数据规范,用于在各方之间作为 JSON 对象安全地传输信息,这些信息可通过数字签名进行验签和信任。

JWT 可以使用密钥(如,HMAC 算法)或使用 RSA 或 ECDSA 的公钥/私钥对进行签名。

虽然 JWT 可以加密以在各方之间提供保密,但我们将专注于签名令牌。签名令牌可以验证其中包含的声明的完整性,而加密令牌则隐藏其他方的声明。当使用公钥/私钥对签名令牌时,签名还证明只有持有私钥的一方是签署它的一方。

JWT 使用场景

  • Authorization:授权,这是 JWT 最常见的方案。一旦用户登录,后续每个请求都会包含 JWT,允许用户访问授予该令牌的路由、服务和资源。SSO(Single Sign On - 单点登录)是 JWT 广泛应用的场景,开销小,且能在不同的域中轻松使用;其它一些一次性验证场景,如邮件激活用户等。
  • Information Exchange:信息交换,JSON Web 令牌是在各方之间安全传输信息的好方法。因为 JWT 可以签名。例如,使用 公钥/私钥对,可以确信息是持有私钥的人发送的。另外,签名是对头和消息体(有效负载)计算得到的,可以验证内容是否被篡改。

JWT 数据结构

JWT 数据结构由三部分组成,分别是:Header、Payload、Signature,使用点号分隔(.),所以 JWT 通常显示如下格式:xxxxx.yyyyy.zzzzz

  1. Header:消息头
    Header 通常由两部分组成:Token 类型,即 JWT,以及使用的签名算法,如 HMAC、SHA256 或 RSA。如下示例:
    1
    2
    3
    4
    {
    "alg": "HS256",
    "typ": "JWT"
    }
    然后对这串 header json 使用 Base64Url 编码
  2. Payload:消息体
    Payload 指有效负载(消息体),包含关于实体(通常为用户)和其他数据的声明。声明有三种类型:注册声明,公开声明,私人声明。
    • Registered claims:注册声明,一组预先定义的声明,非强制的,但建议提供一组有用的,可互操作的声明。其中包括:ISS(发行人),EXP(过期时间),SUB(主题)、AUD(受众)等。
      注意:声明名称只有三个字符,因为 JWT 意味着紧溱。
    • Public claims:公开声明,由使用 JWT 的人随意定义。但为了避免冲突,最好使用 IANA JSON Web Token 注册表中的声明名称,或者将其定义为包含在一个防止冲突命名空间的 URI。
    • Private claims:私人声明,这是为了使用各方之间共享信息而创建的自定义声明。
    payload 示例如下:
    1
    2
    3
    4
    5
    {
    "sub": "1234567890",
    "name": "John Doe",
    "admin": true
    }
    然后对这串 payload json 使用 Base64Url 编码。
    注意:对于已签名的令牌,这些信息虽然可以防止被篡改,但任何人都可以读取。除非 JWT 是加密的,否则不要将敏感信息放在 Header 或 Payload 中。
  3. Signature:签名
    要创建签名,必须获取已编码的 Header、已编码的 Payload,一个密钥,和在 Header 中指定的算法,并对其进行签名。
    例如,如果使用 HMAC SHA256 算法,将按以下方式创建签名:
    1
    HMACSHA256(base64UrlEncode(header) + "." +base64UrlEncode(payload), secret)
    签名用于验证消息是否在传输过程中被篡改,对于使用私钥签名的令牌,还可以验证 JWT 的发送者是否可信息。

JWT 数据组合

JWT 最终输出的是三个由点(.) 分隔的Base64-URL 字符串,这些点可以在 HTML 和 HTTP 环境中轻松传递。

下面显示了一个JWT,包含了 Header 和 Payload 编码,并使用密钥签名:
JWT

可以使用 JWT.IO 调试器来解码、验证和生成 JWT,如下示例:
JWT.IO

JWT 工作原理

在身份验证中,当用户使用其凭据成功登录时,将返回 JSON Web Token。 由于 Token 就是凭证,因此必须非常小心以防止出现安全问题,通常会对令牌设置过期时间。
每当用户想要访问受保护的路由或资源时,用户代理应该使用 Bearer 模式发送 JWT,Token 通常在 Authorization 头中。 如下所示:

1
Authorization: Bearer <token>

在某些情况下,可以是无状态授权机制。 服务器的受保护路由将在 Authorization 头中检查有效的 JWT,如果存在,则允许用户访问受保护的资源。 如果 JWT 包含必要的数据,则可以减少查询数据库以进行某些操作的需要。

如果在 Authorization 头中发送 Token,则跨域资源共享(CORS)将不会成为问题,因为它不使用 cookies

下图显示了如何获取 JWT 并用于访问 API 或资源:
JWT Authorization

JWT Auth

  1. 应用或客户端向受权服务器请求受权。
  2. 授权服务器向应用程序返回访问令牌(Token)。
  3. 应用程序使用访问令牌访问资源服务器中受保护资源。
  4. 资源服务器对令牌进行核验,包括对令牌自身的核验和向授权服务器请求核验。

注意:对于签名的令牌,令牌中包含的所有信息都是对外公开的,即使外界无法修改它,所以不应该将秘密信息放在令牌中。

关于授权,JSON Web Token 允许粒度安全性,即能够在令牌中指定一组特定权限,从而提高可调试性。

JWT 优点

先谈谈 JSON Web Tokens(JWT)与 Simple Web Tokens(SWT)和 Security Assertion Markup Language Tokens(SAML)相比的好处。

  • 由于 JSON 比 XML 更简洁,所以它被编码时,它的大小也更小,使得 JWT 比 SAML 更紧凑。这使得JWT 成为在 HTML 和 HTTP 环境中传递的一个很好的选择。

  • 在安全方面,SWT 只能使用 HMAC 算法通过共享密钥对称签名。但是,JWT 和 SAML 令牌可以使用 X.509 证书形式的公钥/私钥对进行签名。与简单的 JSON 签名相比,使用 XML 数字签名来签名 XML 而不会引入模糊的安全漏洞非常困难。

  • JSON 解析器在大多数编程语言中很常见,因为它们直接映射到对象。相反,XML 没有一个自然的文档映射到对象。这使得使用 JWT 比使用 SAML 断言更容易。

  • 关于使用,JWT 用于互联网规模。这突出了在多个平台(尤其是移动平台)上客户端处理 JSON Web Token 的便利性。

JWT 缺点

  • JWT 生成严重依赖于密钥和生成算法,并且是硬编码存中,或存在外部配置文件中,这样密钥增加了泄漏的风险,威胁系统安全。
  • JWT 使用 Base64 编码,并没有加密,不参存储敏感数据,而 Session 信息存服务器,相对更安全。
  • JWT 是无状态一次性的,一旦签发,无法中途废弃,只能重新签发,但旧的未过期,仍可使用。
  • 若不控制 JWT 签名的有效载荷(payload)中的数据量,则数据可能非常大,增加网络开销,远比只是很短的字符串 sessionId 开销大得多。

JWT 安全使用

  1. 清除已泄漏的 Token:将 JWT 在服务端(Redis)存储一份,若发现令牌异常,则从服务端将此 Token 清除,当用户发起请求时,强制用户重新进行身份验证。这种处理方式相对就比较重了,与 JWT 轻量级验证有些违背,但也不失为一种可行的选择。

  2. 敏感操作增加二次验证,如手机验证码,扫二维码等方式,确认操作者是用户本人,若验证不通过,则终止操作,同时要求重新验证用户身份信息。

  3. 地域检查:检查用户请求所在的地域,若短时间内在多个地域活动,则终止当前请求,强制用户重新进行身份认证,签发新的 Token,并提醒(或要求)用户重置密码。

  4. 监控请求频率:如果 JWT 密钥被盗,攻击者伪造用户身份,可能会高频次对系统发送请求,以窃取用户数据。

    可以监控单位时间内的用户请求次数,若超出阀值则认为异常,服务端终止请求并清除该用户的 Token ,转到认证中心对用户身份进行验证。

  5. 客户端环境检测:可以 Token 与设备的机器码进行绑定,并存储在服务端中,当客户端发起请求时,可以先校验机器码,如果不匹配则视为非法,终止请求。

  6. 将废弃的 JWT 加入黑名单:重新签发 JWT 后,将旧的未过期的 Token 加入黑名单(Redis)避免再次使用。

  7. JWT 续签:最简单的方式是在每次请求时刷新 JWT,或计算剩余有效时间小于某个值时就刷新 JWT,在HTTP 请求时返回一个新的 JWT。此方法暴力且不优雅。

    另一种方式是在 Redis 中为每个 JWT 设置过期时间,每次访问时刷新 JWT 的过期时间。

JWT 使用建议

  1. JWT 是无状态的,只有过期时间,无法主动使其失效,因此 Payload 中的 exp 过期时间不要设的太长。
  2. JWT 是无状态的,适用于无状态的 Rest API,适用于移动端,前后端分离的 Web 端。
  3. JWT 的 Base64Url 编码是为了 token 存在于 url 中, Base64Url 解码后是明文,不可存放敏感数据。
  4. 在服务端开启 HttpOnly,预防 XSS 攻击。
  5. 若要预防重放攻击,可以增加 jti(JWT ID)来作为唯一验证。
  6. JWT 最好用于一次性授权 Token 的设计,时效短。
  7. 在实际应用中,强烈建议走 HTTPS 协议,对 JWT 串进行二次加密。

使用 KeyProvider

通过使用 KeyProvider,可以在运行时更改用于验证令牌签名或为 RSAECDSA 算法签署新令牌的密钥。 这是通过实现 RSAKeyProviderECDSAKeyProvider 方法达到的:

  • getPublicKeyById(String kid):在令牌签名验证期间调用,返回用于验证令牌的密钥。如果使用钥匙旋转,例如 JWK,它可以使用ID获取正确的旋转钥匙(或始终返回相同的钥匙)。
  • **getPrivateKey()**:在令牌签名期间调用,返回将用于签名 JWT 的密钥。
  • **getPrivateKeyId()**:在令牌签名期间调用,返回 getPrivateKey() 方法返回的密钥的 ID。此值优先于 jwtcreator.builder withkeyid(string) 方法中的设置。如果不需要设置 kid 值,请避免使用 KeyProvider 实例化算法。

下面的示例展示了如何使用 JWkstore,一个虚构的 JWK 集实现。对于使用 JWKS 的简单密钥旋转,请尝试 JWKS RSA Java 库。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
final JwkStore jwkStore = new JwkStore("{JWKS_FILE_HOST}");
final RSAPrivateKey privateKey = //Get the key instance
final String privateKeyId = //Create an Id for the above key

RSAKeyProvider keyProvider = new RSAKeyProvider() {
@Override
public RSAPublicKey getPublicKeyById(String kid) {
//Received 'kid' value might be null if it wasn't defined in the Token's header
RSAPublicKey publicKey = jwkStore.get(kid);
return (RSAPublicKey) publicKey;
}

@Override
public RSAPrivateKey getPrivateKey() {
return privateKey;
}

@Override
public String getPrivateKeyId() {
return privateKeyId;
}
};

Algorithm algorithm = Algorithm.RSA256(keyProvider);
//Use the Algorithm to create and verify JWTs.

JWT 实现 java-jwt

在 JWT 官网的 Libraries 里可以看到分别为不同语言提供了 JWT 标准的实现库,甚至一种语言有多个类似的库,只是支持的签名算法和检验略有区别。

JWT 的 Java 实现有:Auth0 > java-jwtjjwtjose4j,参考库官网或 GitHub Wiki 学习对其使用。

添加依赖

1
2
3
4
5
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.8.1</version>
</dependency>

选择算法

算法定义如何对签名和验证令牌。

当使用 RSAECDSA 算法并且只需要对 JWT 进行签名时,可以通过传递空值来避免指定公钥。当只需要验证 JWT 时,也可以对私钥进行同样的操作。

使用静态密码或密钥:

1
2
3
4
5
6
7
//HMAC
Algorithm algorithmHS = Algorithm.HMAC256("secret");

//RSA
RSAPublicKey publicKey = //Get the key instance
RSAPrivateKey privateKey = //Get the key instance
Algorithm algorithmRS = Algorithm.RSA256(publicKey, privateKey);

创建并签署令牌

首先要创建一个 JWTCreator 实例,调用 JWT.create() 并使用 Builder 模式自定义需要加入到 Token 中的声明,最后调用 sign() 入传入 Algorithm 实例。

HS256 示例:

1
2
3
4
5
6
7
8
try {
Algorithm algorithm = Algorithm.HMAC256("secret");
String token = JWT.create()
.withIssuer("auth0")
.sign(algorithm);
} catch (JWTCreationException exception){
//Invalid Signing configuration / Couldn't convert Claims.
}

RS256 示例:

1
2
3
4
5
6
7
8
9
10
RSAPublicKey publicKey = //Get the key instance
RSAPrivateKey privateKey = //Get the key instance
try {
Algorithm algorithm = Algorithm.RSA256(publicKey, privateKey);
String token = JWT.create()
.withIssuer("auth0")
.sign(algorithm);
} catch (JWTCreationException exception){
//Invalid Signing configuration / Couldn't convert Claims.
}

验证令牌

首先调用 JWT.require() 创建 JWTVerifier 实例,并且传入 Algorithm 实例。如果需要验证令牌具有特定声明值,可使用 Builder 来定义。最后调用 *verifier.verify(token)*。

HS256 示例:

1
2
3
4
5
6
7
8
9
10
String token = "";
try {
Algorithm algorithm = Algorithm.HMAC256("secret");
JWTVerifier verifier = JWT.require(algorithm)
.withIssuer("auth0")
.build(); //Reusable verifier instance
DecodedJWT jwt = verifier.verify(token);
} catch (JWTVerificationException exception){
//Invalid signature/claims
}

RS256 示例:

1
2
3
4
5
6
7
8
9
10
11
12
String token = "";
RSAPublicKey publicKey = //Get the key instance
RSAPrivateKey privateKey = //Get the key instance
try {
Algorithm algorithm = Algorithm.RSA256(publicKey, privateKey);
JWTVerifier verifier = JWT.require(algorithm)
.withIssuer("auth0")
.build(); //Reusable verifier instance
DecodedJWT jwt = verifier.verify(token);
} catch (JWTVerificationException exception){
//Invalid signature/claims
}

如果 Token 含有无效的签名,或未满足声明要求,则会抛出:JWTVerificationException 异常。

时间校验

JWT Token 可能包含 DateNumber 类型字段,可用于验证:

  • iat(Issued At):toke 签发时间,必须小于当前时间。
  • exp(Expires At):token 过期时间,必须大于当前时间。
  • nbf(Not Before):token 开始生效日期,在此日期之前不可用。

验证令牌时,将自动对时间进行验证,从而在值无效时引发 JWTverificationException 。如果上面的任何字段不存在,则在验证中不检验这些字段。

给 Token 指定一个额外有效期的窗口期(相当延长有时间),使用 JWTVerifier 生成器中的 acceptLeeway() 方法并传递一个正秒值。适用于上面列出的每一项。

过期的窗口期是最终实现是将当前时间往回推 N 秒,相对延长过期时间。

1
2
3
JWTVerifier verifier = JWT.require(algorithm)
.acceptLeeway(1) // 1 sec for nbf, iat and exp
.build();

还可以为的日期声明指定自定义值,只覆盖该声明的默认值。

1
2
3
4
JWTVerifier verifier = JWT.require(algorithm)
.acceptLeeway(1) //1 sec for nbf and iat
.acceptExpiresAt(5) //5 secs for exp
.build();

如果您需要在 lib/app 中测试此行为,请将 Verification 实例强制转换为 BaseVerification,以获得接受自定义 Clock 的verification.build() 方法的可见性。如下示例:

1
2
3
4
5
BaseVerification verification = (BaseVerification) JWT.require(algorithm)
.acceptLeeway(1)
.acceptExpiresAt(5);
Clock clock = new CustomClock(); //Must implement Clock interface
JWTVerifier verifier = verification.build(clock);

解码令牌

1
2
3
4
5
6
String token = "";
try {
DecodedJWT jwt = JWT.decode(token);
} catch (JWTDecodeException exception){
//Invalid token
}

如果 Token 存无效的语法,或不是 JSON ,则抛出 JWTDecodeException 异常。

java-jwt 示例

示例中使用 FastJson 和 HuTools 库。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
@Test
public void jwtTest() throws Exception {

//签名算法
Algorithm algorithm = Algorithm.HMAC256("123456");
//创建并签名 Token
JWTCreator.Builder builder = JWT.create();
//发布时间
DateTime issueDateTime = DateUtil.date();
builder.withClaim("sub", "1234567890")
.withClaim("name", "John Doe")
.withIssuedAt(issueDateTime)//发布时间
.withExpiresAt(DateUtil.offsetSecond(issueDateTime, 10))//2秒过期
.withIssuer("Rocky");

String token = builder.sign(algorithm);
System.out.println(token);

//Token 解码
DecodedJWT jwt = JWT.decode(token);
System.out.println(JSON.toJSONString(jwt));

//Token 验证
Verification verification = JWT.require(algorithm);
JWTVerifier jwtVerifier = verification
.withIssuer("Rocky")
.acceptLeeway(2)//2秒时间窗口
.build();
DecodedJWT verify = jwtVerifier.verify(token);
System.out.println(JSON.toJSONString(verify));
}

JWT 实现 jjwt

JJWT 旨在成为最容易使用和理解的库,用于在 JVM 和 Android 上创建和验证 JSON Web Token(JWT)。

JJWT 是一个开源的纯 Java 实现,完全基于 JWT,JWS,JWE,JWK 和 JWA RFC 规范,该库由 Okta 的高级架构师 Les Hazlewood 创建,JJWT 还添加了一些不属于规范的便利扩展,例如 JWT 压缩和声明实施。

添加依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<!--jjwt-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>

<!-- 或者添加以下依赖 -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.10.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.10.5</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.10.5</version>
<scope>runtime</scope>
</dependency>
<!-- 如果使用 RSASSA-PSS (PS256, PS384, PS512) 算法,则取消此注释
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<version>1.60</version>
<scope>runtime</scope>
</dependency>
-->

快速开始

1
2
3
4
5
6
7
8
9
10
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;
import java.security.Key;

// We need a signing key, so we'll create one just for this example. Usually
// the key would be read from your application configuration instead.
Key key = Keys.secretKeyFor(SignatureAlgorithm.HS256);
// jwt token
String jws = Jwts.builder().setSubject("Joe").signWith(key).compact();

断言 jwt

1
2
3
4
5
6
7
8
9
assert Jwts.parser().setSigningKey(key).parseClaimsJws(jws).getBody().getSubject().equals("Joe");

//捕抓 jwt 校验异常
try {
Jwts.parser().setSigningKey(key).parseClaimsJws(compactJws);
//OK, we can trust this JWT
} catch (JwtException e) {
//don't trust the JWT!
}

创建 Keys

1
2
3
4
5
6
SecretKey key = Keys.secretKeyFor(SignatureAlgorithm.HS256); //or HS384 or HS512

byte[] keyBytes = getSigningKeyFromApplicationConfiguration();
SecretKey key = Keys.hmacShaKeyFor(keyBytes);

KeyPair keyPair = Keys.keyPairFor(SignatureAlgorithm.RS256); //or RS384, RS512, PS256, PS384, PS512, ES256, ES384, ES512

创建 JWS

  1. 使用 Jwts.builder() 创建 wtBuilder 实例,可以使用 Builder 模式设置消息头,消息体(声明),签名算法,最后调用 compact() 方法。

    1
    2
    3
    4
    String jws = Jwts.builder() // (1)
    .setSubject("Bob") // (2)
    .signWith(key) // (3)
    .compact(); // (4)
  2. 设置 Header,jjwt 默认会设置两个头信息,分别是algzip

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    //设置头
    String jws = Jwts.builder()
    .setHeaderParam("kid", "myKeyId")
    // ... etc ...

    // Header 实例
    Header header = Jwts.header();
    populate(header); //implement me
    String jws = Jwts.builder()
    .setHeader(header)
    // ... etc ...

    // Header Map
    Map<String,Object> header = getMyHeaderMap(); //implement me
    String jws = Jwts.builder()
    .setHeader(header)
    // ... etc ...
  3. 设置 Payload

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    String jws = Jwts.builder()
    .setIssuer("me")
    .setSubject("Bob")
    .setAudience("you")
    .setExpiration(expiration) //a java.util.Date
    .setNotBefore(notBefore) //a java.util.Date
    .setIssuedAt(new Date()) // for example, now
    .setId(UUID.randomUUID()) //just an example id

    //自定义 Claims 声明
    String jws = Jwts.builder()
    .claim("hello", "world")

    //创建 Claims 实例
    Claims claims = Jwts.claims();
    populate(claims); //implement me
    String jws = Jwts.builder()
    .setClaims(claims)

    //创建 Claims Map
    Map<String,Object> claims = getMyClaimsMap(); //implement me
    String jws = Jwts.builder()
    .setClaims(claims)

    //设置算法密钥
    String jws = Jwts.builder()
    .signWith(key)
    .compact();

    //覆盖默认算法,如果设置的 RSA PrivateKey,JJWT默认自动选择 RS256算法
    .signWith(privateKey, SignatureAlgorithm.RS512)
    .compact();

读取 JWS

使用 Jwts.parser() 方法创建 JwtParser 实例,指定 SecretKey 或 非对称 PublicKey 用于验证 JWS 签名,最后调用 parseClaimsJws(String) 方法。

  1. 解析(验证) JWS

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    Jws<Claims> jws;

    try {
    jws = Jwts.parser() // (1)
    .setSigningKey(key) // (2)
    .parseClaimsJws(jwsString); // (3)
    // we can safely trust the JWT
    catch (JwtException ex) { // (4)
    // we *cannot* use the JWT as intended by its creator
    }
  2. 验证密钥 Key

    1
    2
    3
    4
    5
    6
    7
    8
    9
    //使用单个密钥
    Jwts.parser()
    .setSigningKey(secretKey)
    .parseClaimsJws(jwsString);

    //非对称签名,私钥签名,公钥验证
    Jwts.parser()
    .setSigningKey(publicKey)
    .parseClaimsJws(jwsString);
  3. 如果想要使用不同的 Keys,就不要调用 setSigningKey() 方法,而是实现 SigningKeyResolver 接口,传入实例到 JwtParser

    1
    2
    3
    4
    SigningKeyResolver signingKeyResolver = getMySigningKeyResolver();
    Jwts.parser()
    .setSigningKeyResolver(signingKeyResolver) // <----
    .parseClaimsJws(jwsString);

    继承 SigningKeyResolverAdapter 适配器,实现 resolveSigningKey(JwsHeader, Claims) 方法:

    1
    2
    3
    4
    5
    6
    public class MySigningKeyResolver extends SigningKeyResolverAdapter {
    @Override
    public Key resolveSigningKey(JwsHeader jwsHeader, Claims claims) {
    // implement me
    }
    }

自定义压缩

JJWT 默认对生成的 jws 进行了压缩,压缩后的 jws 是非标准的 JWT,因此,创建和解析都需要使用 JJWT 库。

压缩 JWT

1
2
Jwts.builder()   
.compressWith(CompressionCodecs.DEFLATE) // or CompressionCodecs.GZIP

解析 JWT 设置压缩解析器

1
2
3
CompressionCodecResolver ccr = new MyCompressionCodecResolver();
Jwts.parser()
.setCompressionCodecResolver(ccr)

自定义压缩实现

1
2
3
4
5
6
7
8
9
10
11
12
public class MyCompressionCodecResolver implements CompressionCodecResolver {

@Override
public CompressionCodec resolveCompressionCodec(Header header) throws CompressionException {

String alg = header.getCompressionAlgorithm();

CompressionCodec codec = getCompressionCodec(alg); //implement me

return codec;
}
}

自定 Base64

1
2
3
4
5
6
7
8
9
//编码
Encoder<byte[], String> base64UrlEncoder = getMyBase64UrlEncoder(); //implement me
String jws = Jwts.builder()
.base64UrlEncodeWith(base64UrlEncoder)

//解码
Decoder<String, byte[]> base64UrlDecoder = getMyBase64UrlDecoder(); //implement me
Jwts.parser()
.base64UrlDecodeWith(base64UrlEncoder)

其它参考

  1. 深入理解JWT的使用场景和优劣
  2. 讲真,别再使用JWT了
作者

光星

发布于

2019-05-20

更新于

2022-07-12

许可协议

评论