JWT 的简单使用

HYF Lv3

JSON Web Token(JWT)是一个轻量级的认证规范,这个规范允许我们使用JWT在用户和服务器之间传递安全可靠的信息。其本质是一个token,是一种紧凑的URL安全方法,用于在网络通信的双方之间传递。

1 JWT的组成

1.1 头部(Header)

JWT头是一个描述JWT元数据的JSON对象,通常如下所示:

1
2
3
4
{
"alg": "HS256",
"typ": "JWT"
}

alg:表示签名使用的算法,默认为HMAC SHA256

typ:表示令牌的类型,JWT令牌统一写为JWT

1.2 载荷(playload)

有效载荷,是JWT的主体内容部分,也是一个JSON对象,包含需要传递的数据。

有效载荷部分规定有如下七个默认字段供选择:

1
2
3
4
5
6
7
iss:发行人
exp:到期时间
sub:主题
aud:用户
nbf:在此之前不可用
iat:发布时间
jti:JWT ID用于标识该JWT

除以上默认字段外,还可以自定义私有字段。一般不建议存放敏感信息,因为base64是对称解密的,意味着该部分信息可以归类为明文信息

1.3 签证(signature)

签名实际上是一个加密的过程,是对上面两部分数据通过指定的算法生成哈希,以确保数据不会被篡改。

1
2
3
4
5
header (base64后的)

payload (base64后的)

secret

在计算出签名哈希后,JWT头,有效载荷和签名哈希的三个部分组合成一个字符串,每个部分用”.”分隔,就构成整个JWT对象。

2 使用JWT

JJWT是一个提供端到端的JWT创建和验证的Java库

Maven:

1
2
3
4
5
<dependency>
<groupld>io.jsonwebtoken</groupld>
<artifactld>jjwt</artifactld>
<version>0.9.1</version>
</dependency>

Maven依赖可以到MVNREPOSITORY网站查找:

https://mvnrepository.com/

3 Spring boot 整合 JWT

3.1 pom.xml文件添加依赖Maven JJWT 依赖

见本文 2 使用JWT

3.2 自定义注解

1
2
3
4
5
6
7
8
9
10
/**
* @author -侑枫
* @date 2023/7/31 13:16:21
* 需要登录才可以操作的注解
*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface ConfirmToken {
boolean required() default true;
}
1
2
3
4
5
6
7
8
9
10
11
/**
* @author -侑枫
* @date 2023/7/31 13:14:21
* 跳过验证注解
*/

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface PassToken {
boolean required() default true;
}

3.3 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
/**
* @author -侑枫
* @date 2023/7/31 13:33:27
*/
public class JwtUtil {
/**
* JWT过期时间1小时
*/
private static final Date DATE = new Date(System.currentTimeMillis()+ 3600*1000);

public static String getJwt(User user) {
Map<String,Object> claims = new HashMap<>(8);
// 自定义荷载
claims.put("userId",user.getUserId());
claims.put("userName",user.getUserName());
return Jwts.builder()
.setClaims(claims)
// 利用用户密码作为密钥生成签名算法
.signWith(SignatureAlgorithm.HS256, user.getPassword())
// 设置JWT过期时间
.setExpiration(DATE)
.compact();
}
}

写个main方法测试一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public static void main(String[] args) {
User user = new User();
user.setUserName("张三");
user.setPassword("123456");
user.setUserId("00101");
String jwt = getJwt(user);
System.out.println(jwt);
Claims body = Jwts.parser()
// 加密密钥
.setSigningKey(user.getPassword())
// 要解密的JWT
.parseClaimsJws(jwt)
// 获取荷载内容
.getBody();
System.out.println(body);
}

得到结果为;

1
2
eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyTmFtZSI6IuW8oOS4iSIsImV4cCI6MTY4MDUxNjkyMSwidXNlcklkIjoiMDAxMDEifQ.DsFFFKzkUChFcSDgjLYtRbKvH-svCiffzoT6J2nmM1E
{userName=张三, exp=1680516921, userId=00101}

使用
https://jwt.io/
解密获取荷载结果:

1
2
3
4
5
{
"userName": "张三",
"exp": 1680516921,
"userId": "00101"
}

exp为JWT有效时间,其余两项为自定义荷载

3.4 自定义拦截器

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
/**
* @author -侑枫
* @date 2023/7/31 13:47:52
*/
public class AuthenticationInterceptor implements HandlerInterceptor {

private static final String OPTIONS = "OPTIONS";

@Override
public boolean preHandle(HttpServletRequest request, @NotNull HttpServletResponse httpServletResponse, @NotNull Object object) {
//浏览器在发送正式的请求时会先发送options类型的请求试探
//放行该请求
if (OPTIONS.equalsIgnoreCase(request.getMethod())) {
return true;
}
//设置允许跨域访问
//jwt要设置跨域,不然拿不到请求头里的token
httpServletResponse.setHeader("Access-Control-Allow-Origin", "*");
httpServletResponse.setHeader("Access-Control-Allow-Methods", "*");
httpServletResponse.setHeader("Access-Control-Max-Age", "3600");
httpServletResponse.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Authorization,"
+ " Content-Type, Accept, Connection, User-Agent, Cookie,token");
// 从 http 请求头中取出 token
String token = request.getHeader("Authorization");
// 如果不是映射到方法直接通过
if (!(object instanceof HandlerMethod)) {
return true;
}
HandlerMethod handlerMethod = (HandlerMethod) object;
Method method = handlerMethod.getMethod();
//检查是否有passtoken注释,有则跳过认证
if (method.isAnnotationPresent(PassToken.class)) {
PassToken passToken = method.getAnnotation(PassToken.class);
if (passToken.required()) {
return true;
}
}
//检查有没有需要用户权限的注解
if (method.isAnnotationPresent(ConfirmToken.class)) {
ConfirmToken confirmToken = method.getAnnotation(ConfirmToken.class);
if (confirmToken.required()) {
// 执行认证
if (token == null) {
return false;
}
// 根据业务需求获取用户实体类,此操作略

// 获取 token 中的 user id 和 userName 验证
Claims body = Jwts.parser()
// 加密密钥
.setSigningKey(user.getPassword())
// 要解密的JWT
.parseClaimsJws(token)
// 获取荷载内容
.getBody();

if (body.get("userId") != user.getUserId() || body.get("userName") != user.getUserName()) {
return false;
}
System.out.println("JWT验证码完毕");
}
}
return true;
}

@Override
public void postHandle(@NotNull HttpServletRequest httpServletRequest, @NotNull HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {

}

@Override
public void afterCompletion(@NotNull HttpServletRequest httpServletRequest, @NotNull HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {

}
}

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
/**
* @author -侑枫
* @date 2023/7/31 13:58:43
*/
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {

/**
* 拦截所有请求,通过判断是否有注解 决定是否需要登录
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(authenticationInterceptor())
.addPathPatterns("/**");
}

/**
* 自己写的拦截器
*
* @return 拦截器
*/
@Bean
public AuthenticationInterceptor authenticationInterceptor() {
return new AuthenticationInterceptor();
}
}

最后附上本文所写源代码:JWT 的简单使用

  • 标题: JWT 的简单使用
  • 作者: HYF
  • 创建于 : 2023-07-31 20:46:32
  • 更新于 : 2024-07-27 21:21:53
  • 链接: https://youfeng.ink/jwt-b175fd7a8855/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。