
前言
关于JWT(Json Web Token)的含义就不多做介绍了,最常见的是和shiro、springSercurity等一起使用来进行鉴权操作,这次主要记录下jwt在SpringBoot中的用法,有时间再研究下和SpringSercurity一起使用。
开始
- IntelliJ IDEA 2018.1 x64
- jdk8
- SpringBoot 2.0.3
大致流程
1.首先在用户登录的时候生成token,返回给客户端。
2.客户端每次请求带上token,服务端在接收请求的时候取出token进行验证并且从token中获取当前用户信息。
3.根据用户信息(用户相应的权限)来作出判断是否允许该请求通过。
redis作用
由于JWT生成的token在设置过期时间后我们是无法手动控制的,所以在进行登出操作后还携带token的话也是可以访问请他请求的,所以为避免这种情况,我们可以:
1.在登陆的时候存到redis缓存里面,把username作key,并设置缓存过期时间。
2.通过token获取用户信息,用username去查询redis中的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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/com.auth0/java-jwt -->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.4.0</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>18.0</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.4</version>
</dependency>
配置文件application.yml
1 | spring: |
实体类UserBean
1 | /** |
这里 @Transient表示不作数据关系映射
Dao层UserJpa
1 | public interface UserJpa extends JpaRepository<UserBean,Long> { |
Service层
UserService1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21public interface UserService {
/**
* 登录
* @param user
* @return
*/
UserBean auth(UserBean user);
/**
* 根据token获取用户信息
* @param token
* @return
*/
UserBean getUserByToken(String token);
/**
* 退出登录
* @param token
*/
void invalidate(String token);
}
UserServiceImpl1
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
public class UserServiceImpl implements UserService {
private UserJpa userJpa;
private StringRedisTemplate redisTemplate;
public UserBean auth(UserBean user) {
if (StringUtils.isBlank(user.getUsername())){
throw new UserException(400,"用户名不能为空");
}
if (StringUtils.isBlank(user.getPassword())){
throw new UserException(400,"密码不能为空");
}
UserBean userBean = userJpa.findByUsername(user.getUsername());
if (userBean == null){
throw new UserException(400,"没有找到用户名为【"+user.getUsername()+"】的用户");
}
if (! user.getPassword().equals(userBean.getPassword())){
throw new UserException(400,"用户名或者密码错误");
}
// 登录生成token
onLogin(userBean);
return userBean;
}
public UserBean getUserByToken(String token) {
Map<String,String> map;
try {
map = JwtUtil.verifyToken(token);
}catch (Exception e){
throw new UserException(400,"用户未登录");
}
String username = map.get("username");
Long expire = redisTemplate.getExpire(username);
// 存入缓存的token已经过期
if (expire < 0L){
throw new UserException(400,"用户未登录");
}
// 刷新token
refreshToken(token,username);
UserBean userBean = userJpa.findByUsername(username);
if (userBean == null){
throw new UserException(400,"没有找到用户名为【"+username+"】的用户");
}
userBean.setToken(token);
return userBean;
}
public void invalidate(String token) {
Map<String, String> map = JwtUtil.verifyToken(token);
// 删除当前用户在redis中缓存的token
redisTemplate.delete(map.get("username"));
}
/**
* 登录并且生成token
* @param userBean
*/
private void onLogin(UserBean userBean) {
String token = JwtUtil.createToken(ImmutableMap.of("username", userBean.getUsername(),
"id", userBean.getId().toString()));
// 刷新token有效时间
refreshToken(token,userBean.getUsername());
userBean.setToken(token);
}
private void refreshToken(String token, String username) {
// 存入redis
redisTemplate.opsForValue().set(username,token);
// 设置过期时间
redisTemplate.expire(username,30,TimeUnit.MINUTES);
}
}
这里做了全局异常处理,所以直接抛出异常即可(不知道怎么设置全局异常处理?可以观看我之前写的SpringBoot全局异常处理)
注意:在登陆生成token的时候,不能放入敏感信息,如用户密码等,因为这个是可以通过base64破解的,jwt主要验证的是签名,只有签名部分是不可破解的。
工具类JwtUtil
1 | public class JwtUtil { |
验证
方便验证,使用了postman
1.输入http://localhost/jwt/getUserByToken,在未登录的时候访问根据token获取用户信息的接口:1
2
3
4{
"code": 400,
"msg": "用户未登录"
}
2.进行登录操作,生成token,访问http://localhost/jwt/auth,参数传入用户名和密码:1
2
3
4
5
6{
"id": 8,
"username": "mathias",
"password": "XXXX",
"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJtYXRoaWFzIiwiaWQiOiI4IiwiZXhwIjoxNTI5NjU4Mjk3LCJ1c2VybmFtZSI6Im1hdGhpYXMifQ.UNJ9pyTT7QKl1a2cXdFT4igioJ2UhhLxwFsxXdlXSAg"
}
3.再次执行第一步,加上参数token,值就是第二步返回的token值1
2
3
4
5
6{
"id": 8,
"username": "mathias",
"password": "XXXX",
"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJtYXRoaWFzIiwiaWQiOiI4IiwiZXhwIjoxNTI5NjU4Mjk3LCJ1c2VybmFtZSI6Im1hdGhpYXMifQ.UNJ9pyTT7QKl1a2cXdFT4igioJ2UhhLxwFsxXdlXSAg"
}
返回了用户信息,说明成功
4.测试下登出操作,输入http://localhost/jwt/logout,参数也是token,在执行第三步会发现信息已经是未登录状态了。
写在最后
这个只是一个简单的demo,在实际开发过程中我们的token一般会放在请求头Header里面或者是cookie中,毕竟我们不可能每请求一次就设置一下token参数。
以上纯属个人拙见,如果有纰漏、不足之处还请各位大佬指正,不胜感激…