Jwt 令牌 token 使用策略

目录

一、单 token 认证

1、单 token 二次认证 

2、单 token 二次认证 【自动续期】

3、单 token 三次认证【自动续期】

核心步骤:

设计思路:

二、双 token 认证

1、双token二次认证(长token续期)

2、双token三次认证 (自动续期)

核心机制:

设计思路:

三、有状态token的转变

1、如何“转变”为有状态token

2、代码

四、进阶用法


一、单 token 认证

1、单 token 二次认证 

第一次认证:账号密码认证

第二次认证:Jwt 合法认证

Jwt 令牌 token 使用策略_第1张图片

优点:简单,资源消耗小

缺点:token时间设置长了,不安全,token时间设置短了,用户体验较差

2、单 token 二次认证 【自动续期】

第一次认证:账号密码认证

第二次认证:Jwt 合法认证

自动续期:每次权限认证成功后,都会返回一个新token

Jwt 令牌 token 使用策略_第2张图片

优点:相对简单,资源消耗相对较小,用户体验好

缺点:每次返回都需要生成一个新token,并且携带到响应体中,消耗资源。不安全

3、单 token 三次认证【自动续期】

第一次认证:账号密码认证

第二次认证:Jwt 合法认证

第三次认证:token有效状态

自动续期:每次通过三次认证成功后,都会返回一个新token

核心步骤:

将token按照某一规则进行转变,转变为一个有状态的token ,以此为redis中的key存入redis,当请求通过前两次认证后,将会将token转为有状态的token ,判断Redis中是否含有,校验成功,并在redis中删去此次请求中已使用过的 有状态令牌。 并生成新的token,在将token按照某一规则进行转变,转变为一个有状态的token,存入redis,返回资源时,将新token返回给前端。

设计思路:

以上操作的核心意义在token 令牌 赋予了只能一次性使用的特性,可以大大提升系统的安全性,弥补JWT token无法提前失效的弊端。即假如自动续签token的请求被黑客抓包,将其中的token重复使用,虽然能顺利通过第二种验证,但随即就会因为其所对应的“有状态token”已经在redis中被删而无法通过第三种验证。 同样由于一次性机制,即使令牌泄露并被黑客使用,也会被合法客户端及时发现——因为下一次自动续签时,合法客户端的令牌就会因为一次性机制失效了,继而要求用户重新登录,使得黑客手中的 token失效。也方便服务端根据登录、续签时的ip地址等记录判断该用户是否存在被盗号的风险,进一步进行账号冻结或提醒操作。

Jwt 令牌 token 使用策略_第3张图片

优点:用户体验好,大大提升系统的安全性

缺点:资源相对消耗较多

二、双 token 认证

1、双token二次认证(长token续期)

第一次认证:账号密码认证

第二次认证:Jwt 合法认证

refresh-token续期:401认证失败后,请求refresh-token续期请求

Jwt 令牌 token 使用策略_第4张图片

 

2、双token三次认证 (自动续期)

第一次认证:账号密码认证

第二次认证:Jwt 合法认证

第三次认证:有效token状态认证

自动续期:每次通过三次认证成功后,都会返回一个新长短token

核心机制:

首先在用户登陆阶段(流程概述的步骤1)做出改动,在生成了长短两个jwt token之外,还需生成一个有状态token,由长令牌按前面所说的规则“转变”而来。之后将长短令牌返回客户端,将有状态token作为键名存在redis中。留待后续的续签服务中使用。 之后在第三种验证时(流程概述的步骤5),只需将请求所携带的长令牌按照你设定的规则转变为为有状态token,再以此为键去redis中查询是否存在对应键即可。如果存在,则表示验证通过,其后的操作则与登录时一致,即生成长短令牌与有状态token,前俩令牌返回客户端,有状态令牌存redis,并在redis中删去此次请求中已使用过的 有状态令牌。

设计思路:

以上操作的核心意义在于给长令牌赋予了只能一次性使用的特性,可以大大提升系统的安全性,弥补JWT token无法提前失效的弊端。即假如自动续签token的请求被黑客抓包,将其中的长令牌重复使用,虽然能顺利通过第二种验证,但随即就会因为其所对应的“有状态token”已经在redis中被删而无法通过第三种验证。 同样由于一次性机制,即使长令牌泄露并被黑客使用,也会被合法客户端及时发现——因为下一次自动续签时,合法客户端的长令牌就会因为一次性机制失效了,继而要求用户重新登录,使得黑客手中的refresh token失效。也方便服务端根据登录、续签时的ip地址等记录判断该用户是否存在被盗号的风险,进一步进行账号冻结或提醒操作。 同时,纯JWT机制下,服务端系统原本无能为力的注销用户、拉黑名单等用户状态管理操作也得到了补全。注销时只需在redis中删去对应的有状态token,拉黑名单则在前者基础上进一步限制登陆即可,此时客户端发来的续签请求都会因为无法通过有状态token的验证而被拒。虽然客户端的短token还能在失效前继续可用,但已经利大于弊,采用JWT机制所失去的用户登录状态管理功能已经能够得到最大限度的挽回。

Jwt 令牌 token 使用策略_第5张图片

 

三、有状态token的转变

1、如何“转变”为有状态token

首先要介绍的是在第三种验证中提及的“转变”为有状态token。本质上是一个你自定义的规则,即要求你自定义一个方式,将格式为JWT的refresh_token转变为一个仅作为匹配一致性使用的、无意义的字符串,该转变只需要保证结果唯一,无需可逆。你可以对整个refresh_token进行一次md5计算(长度32),也可以直接把jwt token的签名部分截取使用(长度43)。开发阶段可以先使用后者,方便一眼看出对应关系。本文中把这个由长令牌转换来的字符串称为 “有状态token”,以区别于运用jwt规则验证的无状态token。

2、代码

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

public class MatchTokenGenerator {
    
    public static String generateMatchToken(String refreshToken) throws NoSuchAlgorithmException {
        // 获取 SHA256 哈希算法实例
        MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
        // 将 refresh_token 转换为字节数组
        byte[] refreshTokenBytes = refreshToken.getBytes();
        // 对字节数组进行哈希计算
        byte[] hashBytes = messageDigest.digest(refreshTokenBytes);
        // 将哈希值转换为十六进制字符串
        StringBuilder stringBuilder = new StringBuilder();
        for (byte b : hashBytes) {
            stringBuilder.append(String.format("%02x", b));
        }
        // 截取哈希值的前 16 个字符作为匹配字符串
        String matchToken = stringBuilder.substring(0, 16);
        return matchToken;
    }
}

在上述代码中,我们首先获取 SHA256 哈希算法实例,然后将 refresh_token 转换为字节数组,并对其进行哈希计算。接着,我们将哈希值转换为十六进制字符串,并使用 StringBuilder 拼接字符串。最后,我们截取哈希值的前 16 个字符作为匹配字符串并返回。

注意,在使用哈希算法时,需要注意选择合适的算法和哈希值长度,以保证哈希冲突的概率尽可能小。另外,由于哈希算法的不可逆性,一旦生成了匹配字符串,就无法从中还原出原始的 refresh_token

四、进阶用法

同一用户多设备的情形下登陆状态的管理 基于上述设计,再稍稍扩展一下,也能做好同一用户多设备的情形下登陆状态的管理:比如限制用户同时在线的设备数目、限制用户在同一类设备上仅能同时在线一个等等。

拿其中最为复杂的“限制用户在同一类设备上仅能同时在线一个”举例,在redis中以用户id为键,再维护一套hash(子map)对照表。表的filed为设备类型,用户登陆时由客户端传来,如果对应filed不存在则直接保存,以设备类型为filed,以有状态token的字符串为值;如果对应filed已存在,则先根据其中存的值,找到对应有状态token的键并删除,再将“设备类型–token”的field–value对存入。此时同类型旧设备上所存的refresh_token即会因为对应有状态token被删而失效,需要用户重新登录。 所以“限制用户同时在线的设备数目”也更简单了,把上面的设计的hash结构改成list结构,用lua脚本确保list元素上限,超过了则连同有状态token的key一并移除,以此让最旧的设备上的refresh token失效。 并且,如果客户端需要加个“管理我的设备”功能,也能一并实现了。用户可以自行移除自己其他设备上的自动登录状态。

你可能感兴趣的:(java,安全)