【JavaWeb】JWT

JWT

JWT(JSON Web Token)是一个开放的行业标准(RFC 7519),它定义了一种简洁的、自包含的协议格式,用于在通信双方传递 JSON 对象,传递的信息经过数字签名可以被验证和信任。JWT 可以使用 HMAC 算法或使用 RSA 的公钥/私钥对来签名,防止被篡改。

JWT 是一种规范(规则)。使用该规则可以生成包含用户信息的 Token 字符串(自包含令牌)。

JWT 由三部分组成:

image-20220129225023575
  • header:JWT 头部。声明需要使用什么算法来生成签名(密码),如 HMAC、SHA256 或 RSA
  • payload:有效负载。需要保存的用户数据
  • signature:签名哈希(防伪标志)。先对 headerpayload 经过 Base64Url 编码(非加密),然后使用 header 中指定的加密算法对编码结果进行加密,即可得到签名 signature

image-20220129225519072

https://www.bilibili.com/video/BV1ob4y1Y7Ep/?spm_id_from=333.788

JWT 结构

  1. header:JWT 头部。包括令牌的类型(即JWT)及使用的哈希加密算法(如 HMAC、SHA256 或RSA) 。例如:
1
2
3
4
{
"alg": "HS256",
"typ": "JWT"
}

将上边的内容使用 Base64Url 编码,即可得到 JWT 令牌的第一部分。

  1. payload:有效负载,内容也是一个 JSON 对象,它是存放有效信息的地方,它可以存放 JWT 提供的现成字段,比如:iss(签发者),exp(过期时间戳),sub(面向的用户)等,也可自定义字段。 此部分不建议存放敏感信息,因为此部分可以通过解码还原原始内容。例如:
1
2
3
4
5
{
"sub": "1234567890",
"name": "456",
"admin": true
}
  1. signature:签名哈希(防伪标志)。此部分用于验证 JWT,防止 JWT 内容被篡改。 这个部分使用 Base64Url 将前两部分进行编码,编码后使用点 . 连接组成字符串,最后使用 header 中声明签名算法进行签名。例如使用 HMAC 算法对前两段进行加密(需要指定密钥 secret 值,用于加签和验签):
1
2
HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload),
secret)

JWT 认证流程

  • 用户第一次访问服务器时,服务器就会根据用户名和密码生成一个 Token,并且服务器不需要保存该 Token,只需要保存生成该 Token 的密钥
  • 服务器把该 Token 发送给客户端,令其以 Cookie 或 Storage 的形式存储起来
  • 之后客户端再次访问服务器时,就会携带该 Token(请求头中)
  • 服务器使用密钥验证该 Token 是否合法,若合法则认证成功

配置 JWT 令牌服务

  1. 导入 Maven 依赖:
1
2
3
4
5
<!-- jwt json web token -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
</dependency>
  1. 配置工具类,用于生成 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
/**
* jwt(json web token)工具类,用于生成token字符串
*/
@Slf4j
public class JwtUtils {
// 设置token过期时间,单位ms
public static final long EXPIRE = 1000 * 60 * 60 * 24;
// jwt加密时使用的秘钥。需要保存到服务端,用于加签和验签
public static final String APP_SECRET = "ukc8BDbRigUDaY6pZFfWus2jZWLPHO";

/**
* 根据会员id和会员昵称生成token字符串
*
* @param id 会员id
* @param nickname 会员昵称
* @return
*/
public static String getJwtToken(String id, String nickname) {
String JwtToken = Jwts.builder()
.setHeaderParam("typ", "JWT")
.setHeaderParam("alg", "HS256") // 设置头信息
.setSubject("user")
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + EXPIRE))
.claim("id", id)
.claim("nickname", nickname) // 设置有效载荷
.signWith(SignatureAlgorithm.HS256, APP_SECRET)
.compact(); // 设置签名哈希
return JwtToken;
}

/**
* 判断token字符串是否存在且有效
*
* @param jwtToken
* @return
*/
public static boolean checkToken(String jwtToken) {
if (StringUtils.isEmpty(jwtToken)) return false;
try {
Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
} catch (Exception e) {
e.printStackTrace();
return false;
}
return true;
}

/**
* 判断token字符串是否存在且有效
*
* @param request
* @return
*/
public static boolean checkToken(HttpServletRequest request) {
try {
String jwtToken = request.getHeader("token");
if (StringUtils.isEmpty(jwtToken)) return false;
Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
} catch (Exception e) {
e.printStackTrace();
return false;
}
return true;
}

/**
* 获取会员id
*
* @param request
* @return
*/
public static String getMemberIdByJwtToken(HttpServletRequest request) {
log.info("会话id---" + request.getSession().getId());

String jwtToken = request.getHeader("token");
if (StringUtils.isEmpty(jwtToken)) return "";
Jws<Claims> claimsJws = Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
Claims claims = claimsJws.getBody();
return (String) claims.get("id");
}
}

之后在自己的服务中即可通过调用该工具类的方法实现加签验签等功能,不再需要保存用户信息到服务端了。