JWT 实现自动刷新登陆token

JWT  的全称为Json Web Token,简而言之json类型的web服务身份认证令牌(个人理解哟,勿喷)。

  适合做前后端分离身份认证,集群服务身份认证,系统群单点登录等。

1、学习这个技术之前先来了解一下这个技术有哪些优点吧。(参考:https://blog.csdn.net/qq_34037264/article/details/108273333)

 1 1、支持跨域访问: Cookie是不允许垮域访问的,这一点对Token机制是不存在的,前提是传输的用户认证信息通过HTTP头传输.
 2 
 3 2、无状态(也称:服务端可扩展行):Token机制在服务端不需要存储session信息,因为Token 自身包含了所有登录用户的信息,只需要在客户端的cookie或本地介质存储状态信息.
 4 
 5 4、更适用CDN: 可以通过内容分发网络请求你服务端的所有资料(如:javascript,HTML,图片等),而你的服务端只要提供API即可.(居于前面两点得出这个更适用于CDN内容分发网络)
 6 
 7 5、去耦: 不需要绑定到一个特定的身份验证方案。Token可以在任何地方生成,只要在你的API被调用的时候,你可以进行Token生成调用即可.(这个似乎也在继续说前面第一点和第二点的好处。。。)
 8 
 9 6、更适用于移动应用: 当你的客户端是一个原生平台(iOS, Android,Windows 8等)时,Cookie是不被支持的(你需要通过Cookie容器进行处理),这时采用Token认证机制就会简单得多。
10 
11 7、CSRF:因为不再依赖于Cookie,所以你就不需要考虑对CSRF(跨站请求伪造)的防范。(如果token是用cookie保存,CSRF还是需要考虑,一般建议使用1、在HTTP请求中以参数的形式加入一个服务器端产生的token。或者2.放入http请求头中也就是一次性给所有该类请求加上csrftoken这个HTTP头属性,并把token值放入其中)
12 ps:后面会推出一些常见的网络安全的处理
13 
14 8、性能: 一次网络往返时间(通过数据库查询session信息)总比做一次HMACSHA256计算 的Token验证和解析要费时得多.
15 
16 9、不需要为登录页面做特殊处理: 如果你使用Protractor 做功能测试的时候,不再需要为登录页面做特殊处理.
17 
18 10、基于标准化:你的API可以采用标准化的 JSON Web Token (JWT). 这个标准已经存在多个后端库(.NET, Ruby, Java,Python, PHP)和多家公司的支持(如:Firebase,Google, Microsoft).

 

2、基础性知识和demo搭建参考问题1所提的博客就可以了。话不多说进入主题,学习如下内容需要对JWT有简单的了解。

3、在使用JWT的时候,你会发现token的生命周期不能够进行续命。此处有不少小伙伴可能不理解什么意思。

我举个栗子,用户A进入一个系统,获取到一个token,这个token默认有效期是30分钟,但是由于是业务系统,几乎工作时间都在使用这个系统。那么等到30分钟token到期的时候,用户还是需要重新登录。

期望:用户不断操作给token续命,如果token还未失效,只要用户处于活跃状态就一直给token顺延到期时间,即当前时间向后推迟30分钟,如果token已经失效,则重新登录。

现状:JWT由于无法更新失效时间(主要原因token的产生和失效时间有关系),当用户登录系统没到30分钟就需要重新登录一次,无论用户是否活跃。

4、针对这个问题,网友也各抒己见。

  1) 用redis存储jwt(这个可以实现,但是有违jwt的初衷)

       2) 利用refreshToken来刷新JWT (这个方案个人觉得意义不大)

5、下面我们直接开始撸代码。

1、添加JWT依赖。

        <dependency>
            <groupId>com.auth0</groupId>
            <artifactId>java-jwt</artifactId>
            <version>3.10.3</version>
        </dependency>

2、创建JWT测试代码类。

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTCreator;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.AlgorithmMismatchException;
import com.auth0.jwt.exceptions.SignatureVerificationException;
import com.auth0.jwt.exceptions.TokenExpiredException;
import com.auth0.jwt.interfaces.DecodedJWT;
import net.sf.json.JSONObject;
import org.apache.commons.codec.binary.Base64;

import java.io.UnsupportedEncodingException;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;


/**
 * @author LH
 * @version V1.0
 * @Package com.purang.test
 * @date 2021/3/25 16:59
 * @description: 修改描述
 */
public class JwtUtils {
    //SING签名的设置,不能对外暴露(可以提取到配置文件)
    public static final String SING = "token!J1JK3JH^&g%f*f@f*(f!)fs*#s*$H3J4DK43";
    //产生token的有效时长,单位分钟(可以提取到配置文件)
    public static final int TOKEN_EFFECTIVE_TIME_MINUTE = 1;
    //回话连接的有效时长,单位分钟(可以提取到配置文件)
    public static final int SCOKET_EFFECTIVE_TIME_MINUTE = 140;

    public static void main(String[] args) throws UnsupportedEncodingException {
            /*
            //1、模拟第一次登录获取token
            Map<String,String> tokenMap = new HashMap<>();
tokenMap.put("userName","张三zhangsan1");
tokenMap.put("passWord","张三zhangsan1pass");
String token = getToken(tokenMap);*/
//================================================ //2、模拟用户操作系统校验token,有效则直接获取所需信息,直接访问,其他异常做相应处理 String token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJwYXNzV29yZCI6IuW8oOS4iXpoYW5nc2FuMXBhc3MiLCJ1c2VyTmFtZSI6IuW8oOS4iXpoYW5nc2FuMSIsImV4cCI6MTYxNjcyMjI0MH0.T6lm9kQjzUpJlio-MT2arAhAciFMzpdHyVQtpdkFjjA"; JSONObject tokenPayloadJson = null; try { //根据前台请求头中获取token的Base64编码值 String[] tokenSplit = token.split("\\."); //解析载荷信息,用于用户信息的续命 tokenPayloadJson = decodeBase64(tokenSplit[1]); System.out.println("旧token=="+token); System.out.println("旧tokenStr=="+tokenPayloadJson.toString()); //校验token合法性 verify(token); System.out.println("有效签名,验证通过"); } catch (SignatureVerificationException e) { e.printStackTrace(); System.out.println("无效签名"); } catch (TokenExpiredException e) { e.printStackTrace(); //只有在token过期的时候才可以进行token重生策略的调用 System.out.println("token已过期!"); //因JWT只精确到秒,所以此处获取的时间戳你会发现少了后面三位(也就是毫秒) Long token_time = tokenPayloadJson.getLong("exp"); Date date = new Date(); //token已经超期多少时间 System.out.println("token已经超期的时间="+(date.getTime()/1000 - token_time)/60+"分钟"); //token创建时间点到现在的时间 int time = (int) ((date.getTime()/1000 - token_time)/60 - TOKEN_EFFECTIVE_TIME_MINUTE); System.out.println("token创建时间点到现在的时间="+time+"分钟"); if(time < SCOKET_EFFECTIVE_TIME_MINUTE){ System.out.println(">>>>>>>>>>>>>>>>>>token可以重新产生"); Map<String,String> map = new HashMap<>(); tokenPayloadJson.forEach((k, v) -> { if(!"exp".equals((String)k)) map.put((String)k, (String)v); }); map.put("newToken","new"+Math.random()); String newToken = getToken(map); System.out.println("新token=="+newToken); System.out.println("新tokenStr=="+decodeBase64(getTokenInfo(newToken).getPayload())); } } catch (AlgorithmMismatchException e) { e.printStackTrace(); System.out.println("token算法不一致!"); } catch (Exception e) { e.printStackTrace(); System.out.println("token无效!!!"); } } /** * @Description 生成token header.payload.sing * @Author LH * @Date 2021-03-26 10:48:52 * @Param Map 集合存入payload信息 * @Return token * @Exception */ public static String getToken(Map<String, String> map) { //设置令牌的过期时间 Calendar instance = Calendar.getInstance(); //设置失效时间 instance.add(Calendar.MINUTE, TOKEN_EFFECTIVE_TIME_MINUTE); //创建JWT builder JWTCreator.Builder builder = JWT.create(); //payload map.forEach((k, v) -> { builder.withClaim(k, v); }); System.out.println("时间的times"+instance.getTime().getTime()); String token = builder.withExpiresAt(instance.getTime()) //指定令牌过期时间 .sign(Algorithm.HMAC256(SING)); return token; } /** * @Description 验证令牌是否合法 * @Author LH * @Date 2021-03-26 10:57:04 */ public static void verify(String token) { JWT.require(Algorithm.HMAC256(SING)).build().verify(token); } /** * @Author LH * @Date 2021-03-26 11:13:52 * @Return DecodedJWT JWT信息获取 * @Description 获取JWT的信息 */ public static DecodedJWT getTokenInfo(String token) { DecodedJWT verify = JWT.require(Algorithm.HMAC256(SING)).build().verify(token); return verify; } /** * @Author LH * @Date 2021-03-26 11:13:52 * @Return 解析TOKEN后的JSONObject内容 * @Description 根据Base64获取解析内容 */ public static JSONObject decodeBase64(String tokenPayload) throws UnsupportedEncodingException { byte[] bytes = Base64.decodeBase64(tokenPayload); String decode = new String(bytes, "UTF-8"); return JSONObject.fromObject(decode); } }

3、新建token,运行结果如下

 

打开如下代码注释:
            //1、模拟第一次登录获取token
            Map<String,String> tokenMap = new HashMap<>();
tokenMap.put("userName","张三zhangsan1");
tokenMap.put("passWord","张三zhangsan1pass");
String token = getToken(tokenMap);
========================================================================

时间的times1616731158511 旧token
==eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJwYXNzV29yZCI6IuW8oOS4iXpoYW5nc2FuMXBhc3MiLCJ1c2VyTmFtZSI6IuW8oOS4iXpoYW5nc2FuMSIsImV4cCI6MTYxNjczMTE1OH0.tf7dT_kHFAto-AJ8BzCYI8YY8pmmgY8javepTNwGZHY 旧tokenStr=={"passWord":"张三zhangsan1pass","userName":"张三zhangsan1","exp":1616731158} 有效签名,验证通过

4、校验token有效期,进行动态token续命。

旧token==eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJwYXNzV29yZCI6IuW8oOS4iXpoYW5nc2FuMXBhc3MiLCJ1c2VyTmFtZSI6IuW8oOS4iXpoYW5nc2FuMSIsImV4cCI6MTYxNjczMTE1OH0.tf7dT_kHFAto-AJ8BzCYI8YY8pmmgY8javepTNwGZHY
旧tokenStr=={"passWord":"张三zhangsan1pass","userName":"张三zhangsan1","exp":1616731158}
com.auth0.jwt.exceptions.TokenExpiredException: The Token has expired on Fri Mar 26 11:59:18 CST 2021.
    at com.auth0.jwt.JWTVerifier.assertDateIsFuture(JWTVerifier.java:379)
    at com.auth0.jwt.JWTVerifier.assertValidDateClaim(JWTVerifier.java:370)
    at com.auth0.jwt.JWTVerifier.verifyClaims(JWTVerifier.java:295)
    at com.auth0.jwt.JWTVerifier.verify(JWTVerifier.java:278)
    at com.auth0.jwt.JWTVerifier.verify(JWTVerifier.java:261)
    at com.purang.track.testCase.JwtUtils.verify(JwtUtils.java:131)
    at com.purang.track.testCase.JwtUtils.main(JwtUtils.java:55)
token已过期!
token已经超期的时间=88分钟
token创建时间点到现在的时间=87分钟
>>>>>>>>>>>>>>>>>>token可以重新产生
时间的times1616736521241
新token==eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJwYXNzV29yZCI6IuW8oOS4iXpoYW5nc2FuMXBhc3MiLCJuZXdUb2tlbiI6Im5ldzAuMjEzNTkzMjYwNTMxNDY3IiwidXNlck5hbWUiOiLlvKDkuIl6aGFuZ3NhbjEiLCJleHAiOjE2MTY3MzY1MjF9.r3HaEzSRwgY4UlBFNtVX39vUAC0_drIZhWL4j4tZOVc
新tokenStr=={"passWord":"张三zhangsan1pass","newToken":"new0.213593260531467","userName":"张三zhangsan1","exp":1616736521}

5、有了新的token就可以进行前台返回了,后面请求就会拿新token进行验证了。

posted @ 2021-03-26 13:30  曾经的路  阅读(5240)  评论(1编辑  收藏  举报