前言
原文地址:Springboot2.x 整合shiro JWT redis 前后端分离
如果你还对Shiro不太了解,请先看完上个教程:springboot2.x 整合shiro
本文整合shiro与JWT,其中数据库采用 用户->角色->权限 三层,JWT有token和refreshToken,其中refreshToken存储用的是redis,下面我们会一一介绍。
正文
流程
我们先说一遍用户请求的大概流程,其中涉及到了refreshToken的使用,如果不太懂的话可以看一下refreshToken的介绍(在本文下方会提到)。或者你在阅读下面过程中有不懂的,也可以直接跳转到下面的refreshToken介绍。
获取token:
- 用户第一次登陆,用户名密码请求登录接口
- 登录接口判断是否正确,正确发送Token,并创建refreshToken(包含发布token的时间)定时(过期时间)存储至redis
- 修改响应头 Authorization : token
携带 正常token 请求:
- 用户携带token请求 需要权限接口
- shiro拦截用户请求,验证token是否正确,是否未过期,验证redis中的refreshToken是否匹配传入的Token(两者发布时间是否一致)
- 上述验证通过后,根据token中的用户信息获取对应的角色信息和权限信息
- 权限信息验证是否与请求所需的权限相同(shiro完成),通过后进入接口返回数据,不通过返回报出权限异常
携带 过期token 请求:
- 用户携带过期的token请求(refreshToken确保未过期)需要权限的接口;
- shiro拦截用户请求,验证token是否正确,发现已经过期,抛出过期的异常
- 拦截器捕获异常,并查找redis中的refreshToken,对比两者是否匹配(两者的签发时间是否一致)
- 一致则重新生成新的token,修改响应头 Authorization : token,并修改refreshToken值(过期时间不会被修改)
- 再次验证通过后,根据token中的用户信息获取对应的角色信息和权限信息
- 权限信息验证是否与请求所需的权限相同(shiro完成),通过后进入接口返回数据,不通过返回报出权限异常
数据库设计
基于RBAC设计的基础权限模型,大家可以自行百度一下RBAC,目前也是非常主流的权限模型,很多权限设计都是由它进行变形:
具体设计:
Token 与 refreshToken
Token就不写了,自行百度吧…
refreshToken是OAuth2.0中颁布的,用于刷新过期token,减少用户登录次数的一个存储在服务器端的token。
这边放一个链接,这里不再细讲:OAuth2.0关于刷新token的问题
关于刚才流程中的Token问题:
1. 为什么每次验证token都需要与refreshToken匹配?
-
token与refreshToken验证一个是验证token的是不是最新的(签发时间对比),这样也就是可以单点登录。
-
可以通过删除refreshToken达到强制下线的效果。
2. 为什么利用refreshToken刷新token时不刷新refreshToken过期时间?
额…貌似两种思路。我感觉不刷新refreshToken会更好一点,一旦有人盗用了你的token,那么他每隔一段时间请求一下,就可以一直用下去。
所以refreshToken过期需要重新登陆,及时止损…
编写代码
我们不再讲解shiro的基础使用方法,如果你还不知道,需要再看一遍我上一篇的教程:springboot2.x 整合shiro。当然如果你是大佬,你可以点个赞然后按下calt+w了…
引入JWT
<!--jwt-->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.4.1</version>
</dependency>
基本的Jwt类
jwt基础类
/**
* @Author: goodtimp
* @Date: 2019/10/1 22:22
* @description : token
*/
public class JwtToken implements AuthenticationToken {
private String token;
public JwtToken(String token) {
this.token = token;
}
@Override
public Object getPrincipal(){
return token;
}
@Override
public Object getCredentials(){
return token;
}
}
jwt工具类
/**
* @Author: goodtimp
* @Date: 2019/10/1 22:44
* @description : jwt工具类
*/
public class JwtUtil {
/**
* 验证token是否正确
*
* @param token
* @return
*/
public static boolean verify(String token) {
try {
String secret = ; // 这个自己要去定义,用来加密token做验证的
Algorithm algorithm = Algorithm.HMAC256(secret); // 解密 验证正确性
JWTVerifier verifier = JWT.require(algorithm).build();
DecodedJWT jwt = verifier.verify(token);
return true;
} catch (UnsupportedEncodingException e) {
// 不报异常就是正确的
logger.error("JWTToken认证解密出现UnsupportedEncodingException异常:" + e.getMessage());
throw new TokenException("JWTToken认证解密出现UnsupportedEncodingException异常:" + e