JWT令牌

JWT令牌

1. 什么是JWT令牌

JWT(JSON Web Token)是一种开放标准(RFC 7519),用于在各方之间安全地传输信息作为 JSON 对象。它主要由三部分组成,通过点号(.)分隔:

  1. Header(头部):包含令牌的类型(即JWT)和使用的签名算法(例如HMAC SHA256或RSA)。
  2. Payload(载荷):包含令牌的声明(claim),即实际传输的信息,例如用户ID、角色、权限等。
  3. Signature(签名):用于验证发送方是否有权创建令牌,确保信息未被篡改。

JWT的工作流程通常如下:

  • 生成令牌:在用户身份验证成功后,服务器根据用户信息和配置生成一个JWT,并将其发送给客户端。
  • 验证令牌:客户端收到JWT后,可以存储在本地并在未来的API请求中发送该令牌。
  • 使用令牌:服务端收到JWT后,会验证签名,解析载荷中的信息,并根据需要做出响应。

JWT的优点包括:

  • 无状态:服务器不需要在内存中存储会话信息,每个请求都包含足够的信息来验证用户。
  • 可扩展性:因为JWT可以包含任何JSON数据,因此非常适合于传输各种类型的信息。
  • 安全性:JWT可以使用密钥进行签名,确保发送方的身份和数据完整性。

JWT是如何将原始的JSON格式数据,转变为字符串的呢?

  • 其实在生成JWT令牌时,会对JSON格式的数据进行一次编码:进行base64编码

  • Base64:是一种基于64个可打印的字符来表示二进制数据的编码方式。既然能编码,那也就意味着也能解码。所使用的64个字符分别是A到Z、a到z、 0- 9,一个加号,一个斜杠,加起来就是64个字符。任何数据经过base64编码之后,最终就会通过这64个字符来表示。当然还有一个符号,那就是等号。等号它是一个补位的符号

  • 需要注意的是Base64是编码方式,而不是加密方式。

2. 生成和校验JWT令牌

简单介绍了JWT令牌以及JWT令牌的组成之后,接下来介绍基于Java代码如何生成和校验JWT令牌。

  • 要想使用JWT令牌,需要先引入JWT的依赖:
1
2
3
4
5
6
<!-- JWT依赖-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>x.x.x</version>
</dependency>
  • 生成和校验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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
package camellia;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.junit.jupiter.api.Test;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.FileWriter;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;


public class JwtTest {

private static String token;
// 密钥,用于签名和验证签名,必须保密
private static final String SECRET_KEY = "Camellia24211";

@Test
public void generateToken() {
// 创建头部
Map<String, Object> headerClaims = new HashMap<>();
headerClaims.put("typ", "JWT");
headerClaims.put("alg", "HS256");
// // 创建载荷。(一个Map对象,用于存放JWT的声明)
Map<String, Object> payloadClaims = new HashMap<>();
// 将ID添加到声明中
payloadClaims.put("id", 24211);
// 将姓名添加到声明中
payloadClaims.put("name", "Camellia");

// 使用Jwts.builder()创建JWT构建器
token = Jwts.builder()
// 设置JWT的头部
.setHeader(headerClaims)
// 设置JWT的声明(payload部分)
.setClaims(payloadClaims)
// 使用HS256算法和指定的密钥"camellia"对JWT进行签名
.signWith(SignatureAlgorithm.HS256, SECRET_KEY)
// 设置JWT的过期时间为当前时间加上24小时(单位为毫秒)
.setExpiration(new Date(System.currentTimeMillis() + 24 * 3600 * 1000))
// 压缩JWT以生成最终的紧凑的JWT字符串
.compact();

// 打印生成的JWT字符串
System.out.println(token);
// 将生成的JWT字符串写入文件
try (FileWriter fw = new FileWriter("src/main/resources/token.txt")) {
fw.write(token);
fw.flush();
} catch (Exception e) {
e.printStackTrace();
}
}

@Test
public void verifyToken() {
// 初始化token变量为空字符串
token = "";
// 从文件中读取JWT字符串
try (BufferedReader br = new BufferedReader(new FileReader("src/main/resources/token.txt"))) {
String line;
while ((line = br.readLine()) != null) {
token += line;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
System.out.println(token);
}

// 解析并验证JWT字符串
Claims claims = Jwts.parser()
.setSigningKey(SECRET_KEY)
.parseClaimsJws(token)
.getBody();

// 打印JWT的声明部分
System.out.println(claims);
}
}

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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
package camellia.utils;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.util.Date;
import java.util.Map;

public class JwtUtils {

// 签名密钥,用于对JWT进行签名和验证签名
private static String signKey = "camellia";
// 令牌过期时间,单位为毫秒,这里设置为12小时(43200000毫秒)
private static Long expire = 43200000L;

/**
* 生成JWT令牌
*
* @param claims JWT第二部分负载(payload)中存储的内容,通常是一个包含用户信息的Map
* @return 生成的JWT字符串
* 使用 JWT 的库(比如 io.jsonwebtoken)来创建 JWT,通常会默认添加这些必要的头部信息。
*/
public static String generateJwt(Map<String, Object> claims) {
// 使用Jwts.builder()创建JWT构建器
String jwt = Jwts.builder()
// 将传入的Map对象添加到JWT的声明中
.addClaims(claims)
// 使用HS256算法和指定的签名密钥对JWT进行签名
.signWith(SignatureAlgorithm.HS256, signKey)
// 设置JWT的过期时间为当前时间加上指定的过期时长
.setExpiration(new Date(System.currentTimeMillis() + expire))
// 压缩JWT以生成最终的紧凑的JWT字符串
.compact();
// 返回生成的JWT字符串
return jwt;
}

/**
* 解析JWT令牌
*
* @param jwt JWT令牌字符串
* @return JWT第二部分负载(payload)中存储的内容,通常是一个包含用户信息的Claims对象
*/
public static Claims parseJWT(String jwt) {
// 使用Jwts.parser()创建JWT解析器
Claims claims = Jwts.parser()
// 设置签名密钥,用于验证JWT的签名
.setSigningKey(signKey)
// 解析JWT字符串并获取其负载部分(payload)
.parseClaimsJws(jwt)
// 获取JWT的负载部分(payload)
.getBody();
// 返回负载部分(payload)
return claims;
}
}