spring-security和jwt如何生成token

背景:在开始一个项目时,目前生成token的方法主要有两种,一种是spring-security+jwt组合,另一种则是shiro+jwt组合,本文主要阐述spring-security+jwt组合生成token。对这两种框架不熟悉的同学,有兴趣可以点击去官网学习下,不熟悉的也没关系,在生成token的过程中涉及到的知识点,我会一一解答,不熟悉的也不影响阅读本文(建议独自去学习下)。

开始

废话不多,大家都知道,要使用框架,那必不可少的,首先就是要引入相关依赖啊

1
2
3
4
5
6
7
8
9
10
11
12
13
14
js复制代码//pom.xml
<dependencies>
...
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
<version>2.5.3</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
</dependencies>

除此之外,我们还需要在application.yml文件中,给jwt配置一下信息:

1
2
3
4
5
6
7
8
9
js复制代码jwt:
# JWT存储请求头
tokenHeader: Authorization
# JWT加密使用的密钥
secret: juejin-secret
# JWT超期限时间(60*60*24)
expiration: 604800
# JWT负载中拿到开头
tokenHead: Bearer

不管你是老司机,还是正在进阶的同学,我还是在这里啰嗦解析一下其中的字段。tokenHeader是存储的一个请求头,在前端后端接口数据交互中一般都会有,以及tokenHead一般都会存在于请求信息里,secret是jwt加密和解密要使用到的一个密钥,expiration是token的一个失效时间设定,我这里设置是24小时失效。

接下来我们可以去实现一个spring-security+jwt组合生成token的一个工具类JwtTokenUtil,这个类一般都放在包的config下,下面先把代码展示出来:

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
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
js复制代码/java/com.hhk,server.config.security(包路径)

package com.hhk.server.config.security;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;

@Component
public class JwtTokenUtil {
private static final String CLAIM_KEY_USERNAME="sub";
private static final String CLAIM_KEY_CREATED="created";
@Value("${jwt.secret}")
private String secret;
@Value("${jwt.expiration}")
private Long expiration;

/**
* 根据用户信息生成token
*/
public String generateToken(UserDetails userDetails){
Map<String,Object> claims=new HashMap<>();
claims.put(CLAIM_KEY_USERNAME,userDetails.getUsername());
claims.put(CLAIM_KEY_CREATED,new Date());
return generateToken(claims);
}

// 根据荷载JWT生成token
public String generateToken(Map<String,Object> claims){
// Jwts去生成token
return Jwts.builder()
.setClaims(claims)
.setExpiration(generateExpirationDate())
.signWith(SignatureAlgorithm.HS256,secret)
.compact();
}

// 生成token失效时间
private Date generateExpirationDate() {
return new Date(System.currentTimeMillis()+expiration*1000);
}

// 从token中获取登录名
public String getUserNameByToken(String token){
String userName;
try{
Claims claims=getClaimsFromToken(token);
userName=claims.getSubject();
}catch (Exception e){
userName=null;
}
return userName;
}

// 从token中获取荷载
private Claims getClaimsFromToken(String token) {
Claims claims=null;
try {
claims=Jwts.parser()
.setSigningKey(secret)
.parseClaimsJws(token)
.getBody();
}catch (Exception e){
e.printStackTrace();
}
return claims;
}
// 验证token是否有效
public boolean validateToken(String token,UserDetails userDetails){
String username=getUserNameByToken(token);
return username.equals(userDetails.getUsername())&&!isTokenExpired(token);
}

// 验证token是否失效
private boolean isTokenExpired(String token) {
Date expireDate=getExpiredDateFromToken(token);
return expireDate.before(new Date());
}

// 从token中获取失效时间
private Date getExpiredDateFromToken(String token) {
Claims claims=getClaimsFromToken(token);
return claims.getExpiration();
}
// 判断token是否可以被刷新
public boolean canRefresh(String token){
return !isTokenExpired(token);
}

// 刷新token
public String refreshToken(String token){
Claims claims=getClaimsFromToken(token);
claims.put(CLAIM_KEY_CREATED,new Date());
return generateToken(claims);
}
}

下面主要是解析一下,上面整个JwtTokenUtil的源码,自己能阅读的话,不用看下面的源码,下面主要是以方法为单位进行拆分讲解:

1
2
3
4
5
6
7
js复制代码//在类的开始,我们需要定义两个静态字符串,以及获取我们前面在Application.yml中定义好的jwt参数,这里是通过@Value(${""})的方式获取Application.yml文件中的配置信息了
private static final String CLAIM_KEY_USERNAME="sub"; //定义静态用户名(key)CLAIM_KEY_USERNAME
private static final String CLAIM_KEY_CREATED="created";//定义静态生成token的时间
@Value("${jwt.secret}") //获取密钥
private String secret;
@Value("${jwt.expiration}") //获取设定的失效时间
private Long expiration;

到这里在工具类JwtTokenUtil需要使用的静态资源就准备好了,下面我们要开始创建生成token的方法了:

1
2
3
4
5
6
7
8
9
js复制代码/**
* 根据用户信息生成token
*/
public String generateToken(UserDetails userDetails){
Map<String,Object> claims=new HashMap<>(); //定义一个荷载,用于存储用户ing以及token的生成时间
claims.put(CLAIM_KEY_USERNAME,userDetails.getUsername()); //通过。put方法存储CLAIM_KEY_USERNAME和CLAIM_KEY_CREATED的值
claims.put(CLAIM_KEY_CREATED,new Date());
return generateToken(claims); //generateToken的方法在下面会编写其源码,主要是根据荷载生成token的
}

承上启下,下面是generateToken(claims):

1
2
3
4
5
6
7
8
js复制代码//    根据荷载JWT生成token
public String generateToken(Map<String,Object> claims){
return Jwts.builder() //Jwts去生成token
.setClaims(claims) //存储带有用户信息的荷载
.setExpiration(generateExpirationDate()) //存储失效时间
.signWith(SignatureAlgorithm.HS256,secret) //使用算法,和密钥
.compact();
}

generateToken(claims)中使用的generateExpirationDate(),贴一下代码:

1
2
3
4
5
js复制代码//    生成token失效时间
private Date generateExpirationDate() {
//System.currentTimeMillis()+expiration*1000===当前时间+失效时间
return new Date(System.currentTimeMillis()+expiration*1000);
}

到这里其实已经实现了生成token了,但是,既然作为工具类,那在项目中,有时候要校验token,就是通过token中的用户名去跟数据库中的用户进行对比,这时就要获取token中的用户名了,也就是源码中的getUserNameByToken(String token)

1
2
3
4
5
6
7
8
9
10
11
js复制代码从token中获取登录名
public String getUserNameByToken(String token){
String userName;
try{ //有时候会存在用户名不存在的情况,所以要对这种异常进行捕获
Claims claims=getClaimsFromToken(token); //因为生成token时,用户名是存储在token中的荷载,所以getClaimsFromToken(token)获取荷载
userName=claims.getSubject(); //claims的getSubject()获取用户名
}catch (Exception e){
userName=null;
}
return userName;
}

getUserNameByToken(String token)中使用的getClaimsFromToken(token)是要从token中获取荷载:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
js复制代码//    从token中获取荷载
private Claims getClaimsFromToken(String token) {
Claims claims=null;
try {
//通过密钥和token从jwts中获取荷载,因为荷载有可能为空,所以需要捕获一下异常,防止程序崩溃
claims=Jwts.parser()
.setSigningKey(secret)
.parseClaimsJws(token)
.getBody();
}catch (Exception e){
e.printStackTrace();
}
return claims;
}

在日常开发中,或者访问网页中时,我们经常会出现需要重新登录的情况,这是,就需要重新登录,以获取新的token继续访问网页,这个过程,需要验证token是否有效、是否可以被刷新。具体方法如下:

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
js复制代码//    验证token是否有效UserDetails是spring-security框架里的一个类
public boolean validateToken(String token,UserDetails userDetails){
String username=getUserNameByToken(token); //从token中获取用户名
return username.equals(userDetails.getUsername())&&!isTokenExpired(token);
}

// 验证token是否失效
private boolean isTokenExpired(String token) {
Date expireDate=getExpiredDateFromToken(token); //从token中获取失效时间
return expireDate.before(new Date());
}

// 从token中获取失效时间
private Date getExpiredDateFromToken(String token) {
Claims claims=getClaimsFromToken(token); //从token中获取荷载,荷载中存储了失效时间
return claims.getExpiration();
}
// 判断token是否可以被刷新
public boolean canRefresh(String token){
return !isTokenExpired(token);
}

// 刷新token
public String refreshToken(String token){
Claims claims=getClaimsFromToken(token);
claims.put(CLAIM_KEY_CREATED,new Date());
return generateToken(claims);
}

到这里,spring-security+jwt生成token,以及获取token中的信息的工具类就写完了,感谢阅读!❤️❤️❤️

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

0%