文章目录
- 1. 两种token认证方式
- 2. jwt的token加密解密过程
- 3. token登出、改密后失效
- 4. token的自动续期、一定时间内无操作掉线
1. 两种token认证方式
传统的token认证
用户登录,服务端给前端返回token,并将token保存在服务端。
以后用户再来访问时,需要携带token,服务端获取token后再去数据库获取token做校验。
JWT的token认证
用户登录,服务端给用户返回一个token(服务端不保存)
以后用户再来访问时,需要携带token,服务端获取token做校验
两种认证方式对比:
jwt相对于传统的token认证,无需将token保存在服务端。
2. jwt的token加密解密过程
2.1 生成token
用户登录成功后,使用jwt创建一个token,并返回给用户,token格式如下
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 //第一段
.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ //第二段
.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c //第三段
注意:jwt生成的token是由三段字符串拼接而成,使用 . 连接起来
- ①:token的第一段字符串:由下面的json数据通过base64(可逆)加密算法得到。
{
"alg": "HS256", //第三段字符串的不可逆加密类型HS256
"typ": "JWT" //token类型JWT
}
- ② token的第二段字符串:是由下面的payload信息通过base64(可逆)加密算法得到
// payload信息 为自定义值,一般不放敏感信息
{
"sub": "1234567890", //用户id
"name": "John Doe", //用户名
"exp": 1516239022 //token过期时间
}
- ③:token的第三段字符串构成:
1.先将第一段和第二段的密文拼接起来
2.对拼接起来的密文字符串和自定义的盐进行 上边指定的HS256加密
3.对HS256加密后的密文再做base64加密
注意:第一、二部分可以通过Base64
解密得到,但第三部分不可以!
生成token代码如下
/**
* 生成token
* @return token
* @Param 对象map结构
*/
public static String generateToken(Map<String,Object> param){
Date date = new Date();
Date expireTime = new Date(date.getTime() + expire * 1000);
String token = Jwts.builder()
.setHeaderParam("typ", "JWT")
.setClaims(param) //数据
// .setSubject(userId + "")
.setIssuedAt(date)
.setExpiration(expireTime) //过期时间
.signWith(SignatureAlgorithm.HS256, secret) //秘钥
.compact();
return token;
}
2.2 验证(解密)token
当用户再来访问时,需要携带token,后端需要对token进行校验
- ①:获取token
- ②:对token进行切割成三部分
- ③:对第二段字符串进行base64解密,检测token是否超时?
- ④:对第一二段字符串拼接,再次进行HS256加密,得到密文字符串
- ⑤:对token的第三段HS256加密
解密验证代码:
/**
* 验证token
* @return token正确返回对象,token不正确返回null
*/
public static Claims getClaimByToken(String token){
try {
return Jwts.parser()
.setSigningKey(secret) //获取秘钥
.parseClaimsJws(token) //解析验证token
.getBody();
}catch (Exception e){
log.info("token验证失败",e);
return null;
}
}
ps : token一旦生成,在过期时间内永久有效,即使项目重启!想要失效token必须等待过期,或者重置盐值!
3. token登出、改密后失效
使用jwt时,一般修改密码或退出登录时,需要把正在使用的token做失效处理,防止别的客户端使用失效token访问信息。
-
方案一:在每次修改密码或者退出登录后,修改一下自定义的盐值。当进行下次访问时,会根据自定义盐值验证
token
,修改了自定义盐值,自然访问不通过。 -
方案二:利用数据库,存放一个修改或者登出的时间,在创建
token
时,标注上创建时间。如果这个创建时间小于修改或登出的时间,就表示它是修改或者登出之前的token
,为过期token
/**
* 生成jwt token
*/
public String generateToken(String userId,Boolean rememberMe) {
Date nowDate = new Date();
long expiration = rememberMe ? expireRemember : expire;
//过期时间
Date expireDate = new Date(nowDate.getTime() + expiration * 1000);
System.out.println("登录过期时间 >>>>"+DateUtils.formatDateTime(expireDate));
return Jwts
//创建一个JwtBuilder对象
.builder()
//设置头信息
.setHeaderParam("typ", "JWT")
//设置主题信息
.setSubject(userId)
.setIssuedAt(nowDate)//创建时间
.setExpiration(expireDate)//过期时间
//签名手段,参数1:算法,参数2:盐
.signWith(SignatureAlgorithm.HS256, secret)
//获取token
.compact();
}
/**
* token是否过期
* @return true:过期
* lastLoginDate 数据库记录的最后一次登出时间
* issueDate token 创建时间
*/
public boolean isTokenExpired(Date expiration,Date lastLoginDate,Date issueDate) {
//token创建时间小于数据库记录的最后一次登出时间 过期
if(lastLoginDate == null){
return expiration.before(new Date());
}else{
return issueDate.before(lastLoginDate);
}
}
拦截器的判断:
if(jwtUtils.isTokenExpired(claims.getExpiration(),user.getLoginDate(),claims.getIssuedAt())){
Result result = ResultGenerator.genFailResult(ResultCode.UNAUTHORIZED,"token失效,请重新登录");
SendMsgUtil.sendJsonMessage(response,result);
return false;
}
4. token的自动续期、一定时间内无操作掉线
场景:用户登陆后,token的过期时间为30分钟,如果在这30分钟内没有操作,则重新登录,如果30分钟内有操作,就给token自动续一个新的时间。避免用户正在操作时掉线重登!
实现①:在jwt生成token时先不设置过期时间,过期时间的操作放在redis中。
-
①:在登陆时,把用户信息(或者token)放进redis,并设置过期时间
-
②:如果30分钟内用户有操作,前端带着token来访问,过滤器解析token得到用户信息,去redis中验证用户信息,验证成功则在redis中增加过期时间,验证失败,返回token错误。实现了token时间的自动更新。
-
③:如果30分钟内用户无操作,redis中的用户信息已过期,此时再进行操作,token解析出的用户信息在redis中验证失败,则重新登录。实现了一定时间内无操作掉线!
实现②:使用access_token、refresh_token 解决
- 登录获取token(包括访问令牌
access_token
,刷新令牌refresh_token
),其中access_token设置过期时间为5分钟,refresh_token设置过期时间为30分钟。不能同时过期 - 前端保存
access_token
和refresh_token
,每次请求带着access_token
去访问服务器资源 - 服务器校验
access_token
有效性,通过解析access_token
看是否能解析出用户信息。如果用户信息为null
,说明token
无效,返回401
,让用户重新登录 - 服务器端校验
access_token
是否过期- 如果
access_token
没有过期,则token正常,继续执行业务逻辑 - 如果
access_token
过期,计算 过期后到当前的时间大小 是否在refresh_token
过期时间之内(是否大于30 - 5 - 5 = 20
分钟,为什么不是30 - 5 = 25
分钟呢?主要是想对正在请求的用户token做一个缓存,保证在最后五分钟内,新、老token都有效!防止正在进行的请求token突然失效!),- 如果大于
refresh_token
的过期时间,则表示用户长时间无操作,token真正过期了,返回401,让用户重新登录 - 如果小于
refresh_token
的过期时间,则继续让该access_token
访问业务,但返回给前端标识,提示token已过期,让前端带着refresh_token
去服务器获取新的access_token
,并保存在前端,后续使用新的access_token
去访问!
- 如果大于
- 如果