JWT的加密解密原理,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去访问!
————————————————
版权声明:本文为CSDN博主「知识分子_」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_45076180/article/details/107243172

  • 1
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
JWT(JSON Web Token)是一种用于身份验证的开放标准(RFC 7519)。它是基于JSON(JavaScript Object Notation)格式的数据结构,由三部分组成:头部(Header)、载荷(Payload)和签名(Signature)。 JWT的加密/解密过程可以采用对称加密或非对称加密。对称加密使用同一个密钥进行加密和解密,而非对称加密使用公钥加密,私钥解密。 以下是JWT使用对称加密和非对称加密的加密/解密过程: 1. 对称加密 加密过程: 1)将头部和载荷进行Base64编码,得到第一部分:Base64UrlEncode(header) + "." + Base64UrlEncode(payload)。 2)使用密钥对第一部分进行加密。 3)对加密后的结果进行Base64UrlEncode,得到第三部分:Base64UrlEncode(signature)。 解密过程: 1)将JWT字符串按照“.”分割成三部分。 2)使用密钥对第一部分进行解密。 3)对解密后的结果进行Base64解码,得到头部和载荷。 4)对解密后的结果和密钥进行签名验证。 2. 非对称加密 加密过程: 1)将头部和载荷进行Base64编码,得到第一部分:Base64UrlEncode(header) + "." + Base64UrlEncode(payload)。 2)使用私钥对第一部分进行签名。 3)对签名后的结果进行Base64UrlEncode,得到第三部分:Base64UrlEncode(signature)。 解密过程: 1)将JWT字符串按照“.”分割成三部分。 2)对第一部分和第三部分进行Base64解码,得到头部、载荷和签名。 3)使用公钥对签名进行验证。 4)验证通过后,得到解密后的头部和载荷。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值