关于学习Token、JWT、Cookie等验证授权方式的总结

目录

一、为什么Cookie无法防止CSRF攻击,而Token可以?

二、为什么无论采用Cookie-session的方式,还是Token(JWT)的方式,在一个浏览器里,同一个网站只能保证一个用户处于登录状态?

三、理解黑马点评中的双重拦截器

四、什么是ThreadLocal?底层是如何实现的

4.1 ThreadLocal为什么可以优化鉴权逻辑?

 4.2 ThreadLocal内存泄漏的原因

4.2.1 那为什么不将key设置为强引用?

4.2.2 那么为什么 key 要设计为弱引用 ?

4.3 如何正确使用ThreadLocal?

五、JWT身份认证常见问题及解决方法

5.1 JWT概念

5.2 如何防止JWT被篡改?

5.3 如何加强JWT的安全性?

5.4 JWT身份认证常见问题及解决方案

5.4.1 JWT续签问题

5.4.2 JWT实现强制某用户只能在一种客户端登录怎么实现?


最近接触了许许多多的项目,对这些概念有些许混淆,趁着这次温书假,对其进行巩固一下。

在此之前,我们先讲讲传统的关于Cookie-session方案:

很多时候我们都是通过 SessionID 来实现特定的用户,SessionID 一般会选择存放在 Redis 中。举个例子:

  1. 用户成功登陆系统,然后返回给客户端具有 SessionID 的 Cookie。
  2. 当用户向后端发起请求的时候会把SessionID 带上,这样后端就知道你的身份状态了。

关于这种认证方式更详细的过程如下:

  1. 用户向服务器发送用户名、密码、验证码用于登陆系统。
  2. 服务器验证通过后,服务器为用户创建一个 Session,并将 Session信息存储起来。
  3. 服务器向用户返回一个 SessionID ,写入用户的 Cookie。
  4. 当用户保持登录状态时,Cookie将与每个后续请求一起被发送出去。
  5. 服务器可以将存储在 Cookie上的 SessionID 与存储在内存中或者数据库中的 Session 信息进行比较,以验证用户的身份,返回给用户客户端响应信息的时候会附带用户当前的状态。

使用 Session 的时候需要注意下面几个点:

  • 依赖 Session 的关键业务一定要确保客户端开启了 Cookie。(当然,没用开启可以使用URL重写技术进行传递SessionID)
  • 注意 Session 的过期时间。

一、为什么Cookie无法防止CSRF攻击,而Token可以?

CSRF(Cross Site Request Forgery) 一般被翻译为 跨站请求伪造 。那么什么是 跨站请求伪造 呢?说简单点,就是用你的身份去发送一些对你不友好的请求。举个简单的例子:

小壮登录了某网上银行,他来到了网上银行的帖子区,看到一个帖子下面有一个链接写着“科学理财,年盈利率过万”,小壮好奇的点开了这个链接,结果发现自己的账户少了 10000 元。这是这么回事呢?原来黑客在链接中藏了一个请求,这个请求直接利用小壮的身份给银行发送了一个转账请求,也就是通过你的 Cookie 向银行发出请求。

<a src=http://www.mybank.com/Transfer?bankId=11&money=10000>科学理财,年盈利率过万</>

上面也提到过,进行 Session 认证的时候,我们一般使用 Cookie 来存储 SessionID ,当我们登陆后后端生成一个 SessionID 放在 Cookie 中返回给客户端,服务端通过 Redis 或者其他存储工具记录保存着这个 SessionID ,客户端登录以后每次请求都会带上这个 SessionID ,服务端通过这个 SessionID 来标示你这个人。如果别人通过 Cookie 拿到了 SessionID 后就可以代替你的身份访问系统了。

Session认证中 Cookie 中的 SessionID 是由浏览器发送到服务端的,借助这个特性,攻击者就可以通过让用户误点攻击链接,达到攻击效果。

但是,我们使用 Token 的话就不会存在这个问题,在我们登录成功获得 Token 之后,一般会选择存放在localStorage(浏览器本地存储)中。然后我们在前端通过某些方式会给每个发到后端的请求加上这个Token ,这样就不会出现 CSRF 漏洞的问题。因为,即使你点击了非法链接发送了请求到服务端,这个非法请求是不会携带 Token 的,所以这个请求将是非法的。

需要注意的是:不论是 Cookie 还是 Token 都无法避免 跨站脚本攻击(Cross Site Scripting)XSS

跨站脚本攻击(Cross Site Scripting)缩写为 CSS 但这会与层叠样式表(Cascading Style Sheets,CSS)的缩写混淆。因此,有人将跨站脚本攻击缩写为 XSS。

XSS 中攻击者会用各种方式将恶意代码注入到其他用户的页面中。就可以通过脚本盗用信息比如 Cookie 。

二、为什么无论采用Cookie-session的方式,还是Token(JWT)的方式,在一个浏览器里,同一个网站只能保证一个用户处于登录状态?

其实这里主要是前端的设置,因为一般前端才向后端发送请求的时候,基本是拿去浏览器所存储的JSESSIONID和Token,如果硬要保证,一个浏览器同一个网站可以保持多个登录为登录状态的话,设计较为麻烦。

其实像黑马点评里面的登录校验逻辑,其实也是类似于JWT一样,感觉JWT只是一种较为正规的Token,多封装了一些签名,负载等。

三、理解黑马点评中的双重拦截器

在初始方案中,他确实可以使用对应路径的拦截,同时刷新登录token令牌的存活时间,但是现在这个拦截器他只是拦截需要被拦截的路径,假设当前用户访问了一些不需要拦截的路径,那么这个拦截器就不会生效,所以此时令牌刷新的动作实际上就不会执行,所以这个方案是存在问题的:

优化方案

既然之前的拦截器无法对不需要拦截的路径生效,那么我们可以添加一个拦截器,在第一个拦截器中拦截所有的路径,把第二个拦截器做的事情放入到第一个拦截器中,同时刷新令牌,因为第一个拦截器有了threadLocal的数据,所以此时第二个拦截器只需要判断拦截器中的user对象是否存在即可,完成整体刷新功能。

四、什么是ThreadLocal?底层是如何实现的

其实简单来讲,ThreadLocal就是线程本地变量,每个线程都拥有一份该变量的独立副本,即使是在多线程环境下,每个线程只能修改和访问自己的那份副本,从而避免了线程安全问题,实现了线程间的隔离。

ThreadLocal底层是使用ThreadLocalMap 实现的,这点从JDK的源码可以看出,核心源码如下:

从ThreadLocal的Set方法可以看出,ThreadLocal是存储在ThreadLocalMap中的,咱们继续看ThreadLocalMap的源码实现:

从上面源码可以看出,ThreadLocalMap中存放的是Entry,而Entry中的key就是ThreadLocal,而Value则是要存储的值,所以我们得出ThreadLocal的实现如下所示:

4.1 ThreadLocal为什么可以优化鉴权逻辑?

我们先来看这幅图:当我们访问这个程序的时候,所有的请求,其实是需要先通过拦截器的,但是在拦截器之后呢?那些Contorller层是怎么知道当前用户信息的呢?

这时候我们就可以将其存到ThreadLocal中了。

可能有人会说,这不是将用户信息存入Sesssion中吗?我每次都去Session中取不可以吗?

  • 这是因为使用ThreadLocal可以简化代码,使得当前线程可以随时的获取当前用户的信息,而不必每次都通过‘HttpServletRequest’来获取。
  • 并且可以避免的频繁访问Session,提高性能。

使用ThreadLocal优化也是较为常见的操作,上图中的情况可以通过Session获取,但是在分布式架构中,可能使用的是Token/JWT结合Redis进行存储用户信息,用户信息都放在Redis中,如果每次获取用户信息都需要去查询Redis,那么就太浪费性能了。所以使用ThreadLocal成为一种常见的方式。

可能有人说,那为什么网上有些文章说ThreadLocal可以避免线程安全问题?

其实这是从另一个维度来区分的,这个结论有个前提条件,是相比于普通的变量,使用ThreadLocal可以有效的避免线程安全问题。

下面我们来看这个例子:(以下原文为:为什么要使用 ThreadLocal 进行登录时处理用户信息?而非普通变量?_threadlocalcontext 在登录的时候-CSDN博客)

假如有两个用户 A 和 B,他们分别进行登录,并且他们的每次请求都会带有自己的 token,在请求到达 controller 之前(preHandle() 中),每次都会被会被拦截器进行拦截,提取出当前 token 中的用户信息(比如 userId),认证通过以后在 service 中就可以通过 Contenxt 类获取提取出来的用户信息:

前提

  • Context 类中存储用户的 ID,有一个静态的变量或者对象叫做 USER_ID

(1)使用普通变量,例如 String

Context 类使用 String 变量 (static String USER_ID) 进行存储,这时候:

  • 用户 A 登录,带着自己的 token,到达后端拦截器,token 验证通过后,用户信息被提取到 Context 中的 String 变量中。
  • 用户 B 登录,带着自己的 token,到达后端拦截器,token 验证通过后,用户信息被提取到 Context 中的 String 变量中。(这里 String 变量之前存储的是 A 的信息,但是由于 B 登录以后,又将 String 的值设置为了 B 的 token 中提取出来的用户信息。)
  • 用户 A 调用新增的 api,这时候调用新增 api 的这个请求,也附带了 A 的 token 信息,所以重复第一步。
  • 用户 B 调用新增的 api,这时候调用新增 api 的这个请求,也附带了 B 的 token 信息,所以重复第二步。

虽然存储用户信息都是在一个 String 中,但是好像并没有发现什么问题。(往下看)

(2)使用ThreadLocal类进行存储

Context 类使用 ThreadLocal 类型的对象 (ThreadLocal USER_ID = new …) 进行存储,这时候:

  • 用户 A 登录,带着自己的 token,到达后端拦截器,token 验证通过后,接下来应该提取用户信息到 Context 中了,这时候,用户 A 当前登录是在一个线程 ThreadA 中,那么看到 Context 中定义的 USER_ID 是 ThreadLocal 类型的,(简单讲)这时候他会以当前线程为 key = ThreadA,以 A.token 为 value 创建一个新的只属于当前 ThreadA 的对象 USER_ID。
  • 用户 B 登录,带着自己的 token,到达后端拦截器,token 验证通过后,接下来应该提取用户信息到 Context 中了,这时候,用户 B 当前登录是在一个线程 ThreadB 中,那么看到 Context 中定义的 USER_ID 是 ThreadLocal 类型的,(简单讲)这时候他会以当前线程为 key= ThreadB,以 B.token 为 value 创建一个新的只属于当前 ThreadB 的对象 USER_ID。

总结

第一种方式看似运行时和第二种没有区别,但是在高并发的时候,由于 USER_ID 的值的设置和 USER_ID 的值的获取是两次操作,那么很显然设置和获取不是一个原子性的操作,这时候肯定会发生并发问题,即:A 刚设置了值,还没有等到 A 取值,B 就将这个 String 类型的 USER_ID 设置成了自己的信息。这时候 A 再进行取值,取到的就是 B 的值。
第二种方式的话,很显然就解决了这个问题,因为他们都是操作的自己线程内的 USRE_ID,各个线程之间互不影响,所以这个时候,完全不会混乱。

注意

  • 首先说一个名词 OOM,即 Out Of Memory,内存泄露、内存溢出。
  • ThreadLocal 中的 key 是弱引用,value 是强引用。
  • 弱引用,自动垃圾回收。
  • 强引用,线程销毁时,才会被回收。
  • 一个线程可能有时候很久都不会被销毁,但是这时候只有弱引用的 key 会被回收,value 由于是强引用,由于线程还存在,他就会存在,但是 value 已经没有用了,这个时候就造成了浪费。
  • 为了避免浪费内存,继而发生内存溢出,我们需要使用 remove() 方法,进行手动清除 ThreadLocal 对象。

 4.2 ThreadLocal内存泄漏的原因

原文链接:史上最全ThreadLocal 详解(二)_史上最全threadlocal 详解(二)-CSDN博客 

 Entry将ThreadLocal作为Key,值作为value保存,它继承自WeakReference,注意构造函数里的第一行代码super(k),这意味着ThreadLocal对象是一个「弱引用」。

主要是由于两个原因:

1. 没有手动删除这个Entry

2. CurrentThread即当前线程仍然运行

  • 第一点很好理解,只要在使用完下 ThreadLocal ,调用其 remove 方法删除对应的 Entry ,就能避免内存泄漏。
  • 第二点稍微复杂一点,由于ThreadLocalMap 是 Thread 的一个属性,被当前线程所引用,所以ThreadLocalMap的生命周期跟 Thread 一样长。如果threadlocal变量被回收,那么当前线程的threadlocal 变量副本指向的就是key=null, 也即entry(null,value),那这个entry对应的value永远无法访问到。实际私用ThreadLocal场景都是采用线程池,而线程池中的线程都是复用的,这样就可能导致非常多的entry(null,value)出现,从而导致内存泄露。

综上, ThreadLocal 内存泄漏的根源是:

    由于ThreadLocalMap 的生命周期跟 Thread 一样长,对于重复利用的线程来说,如果没有手动删除(remove()方法)对应 key 就会导致entry(null,value)的对象越来越多,从而导致内存泄漏。

4.2.1 那为什么不将key设置为强引用?

1.key如果为强引用

其实很简单,如果key被设计成强引用,且没有手动remove(),那么key会和value一样伴随着线程的整个生命周期。

如下图,如果栈上的引用没有了,堆上的两个强引用还存在,没有手动的remove,那么着两个强引用就一直没办法进行垃圾回收,这种设计仍然存在内存泄漏问题。

4.2.2 那么为什么 key 要设计为弱引用 ?

事实上,在 ThreadLocalMap 中的set/getEntry 方法中,会对 key 为 null(也即是 ThreadLocal 为 null )进行判断,如果为 null 的话,那么会把 value 置为 null 的.这就意味着使用threadLocal , CurrentThread 依然运行的前提下.就算忘记调用 remove 方法,弱引用比强引用可以多一层保障。——这便是ThreadLocalMap的自我清理机制。

但是即使如此,还说存在一些情况无法覆盖: 

  • 如果没有频繁访问ThreadLocal 的get,set 或 remove 方法,自我清理机制不会被触发,垃圾回收器不会回收那些强引用的值对象。

4.3 如何正确使用ThreadLocal?

  1.  将ThreadLocal变量定义成private static的,这样的话ThreadLocal的生命周期就更长,由于一直存在ThreadLocal的强引用,所以ThreadLocal也就不会被回收,也就能保证任何时候都能根据ThreadLocal的弱引用访问到Entry的value值,然后remove它,防止内存泄露
  2.  每次使用完ThreadLocal,都调用它的remove()方法,清除数据。

关于第一点,可能有人会有点疑惑,这里我们展开介绍:

ThreadLocal的目的是为每个线程提供一个独立的变量副本,如果‘ThreadLocal’变量是‘private static’的,则它在类级别上是共享的,但每个线程对它的访问是独立的,这意味着每个线程在访问ThreadLocal变量时,实际上访问的是自己独有的变量副本,这种设计符合‘ThreadLocal’的初衷,即为每个线程提供独立的、隔离的变量副本。

五、JWT身份认证常见问题及解决方法

5.1 JWT概念

JWT (JSON Web Token) 是目前最流行的跨域认证解决方案,是一种基于 Token 的认证授权机制。 从 JWT 的全称可以看出,JWT 本身也是 Token,一种规范化之后的 JSON 结构的 Token。

JWT 自身包含了身份验证所需要的所有信息,因此,我们的服务器不需要存储 Session 信息。这显然增加了系统的可用性和伸缩性,大大减轻了服务端的压力。

可以看出,JWT 更符合设计 RESTful API 时的「Stateless(无状态)」原则

并且, 使用 JWT 认证可以有效避免 CSRF 攻击,因为 JWT 一般是存在在 localStorage 中,使用 JWT 进行身份验证的过程中是不会涉及到 Cookie 的。

JWT的构成

JWT 本质上就是一组字串,通过(.)切分成三个为 Base64 编码的部分: 

  • Header : 描述 JWT 的元数据,定义了生成签名的算法以及 Token 的类型。
  • Payload : 用来存放实际需要传递的数据
  • Signature(签名):服务器通过 Payload、Header 和一个密钥(Secret)使用 Header 里面指定的签名算法(默认是 HMAC SHA256)生成。

JWT 通常是这样的:xxxxxxx.yyyyyy.zzzzzz

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

你可以在 jwt.ioopen in new window 这个网站上对其 JWT 进行解码,解码之后得到的就是 Header、Payload、Signature 这三部分。

Header 和 Payload 都是 JSON 格式的数据,Signature 由 Payload、Header 和 Secret(密钥)通过特定的计算公式和加密算法得到。

Header 通常由两部分组成:

  • typ(Type):令牌类型,也就是 JWT。
  • alg(Algorithm):签名算法,比如 HS256。
{
  "alg": "HS256",
  "typ": "JWT"
}

JSON 形式的 Header 被转换成 Base64 编码,成为 JWT 的第一部分。

Payload 也是 JSON 格式数据,其中包含了 Claims(声明,包含 JWT 的相关信息)。

Claims 分为三种类型:

  • Registered Claims(注册声明):预定义的一些声明,建议使用,但不是强制性的。
  • Public Claims(公有声明):JWT 签发方可以自定义的声明,但是为了避免冲突,应该在 IANA JSON Web Token Registryopen in new window 中定义它们。
  • Private Claims(私有声明):JWT 签发方因为项目需要而自定义的声明,更符合实际项目场景使用。

下面是一些常见的注册声明:

  • iss(issuer):JWT 签发方。
  • iat(issued at time):JWT 签发时间。
  • sub(subject):JWT 主题。
  • aud(audience):JWT 接收方。
  • exp(expiration time):JWT 的过期时间。
  • nbf(not before time):JWT 生效时间,早于该定义的时间的 JWT 不能被接受处理。
  • jti(JWT ID):JWT 唯一标识。
{
  "uid": "ff1212f5-d8d1-4496-bf41-d2dda73de19a",
  "sub": "1234567890",
  "name": "John Doe",
  "exp": 15323232,
  "iat": 1516239022,
  "scope": ["admin", "user"]
}

Payload 部分默认是不加密的,一定不要将隐私信息存放在 Payload 当中!!!

JSON 形式的 Payload 被转换成 Base64 编码,成为 JWT 的第二部分。

Signature 部分是对前两部分的签名,作用是防止 JWT(主要是 payload) 被篡改。

这个签名的生成需要用到:

  • Header + Payload。
  • 存放在服务端的密钥(一定不要泄露出去)。
  • 签名算法。

签名的计算公式如下:

HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  secret)

算出签名以后,把 Header、Payload、Signature 三个部分拼成一个字符串,每个部分之间用"点"(.)分隔,这个字符串就是 JWT 。

5.2 如何防止JWT被篡改?

有了签名之后,即使 JWT 被泄露或者截获,黑客也没办法同时篡改 Signature、Header、Payload。

这是为什么呢?因为服务端拿到 JWT 之后,会解析出其中包含的 Header、Payload 以及 Signature 。服务端会根据 Header、Payload、密钥再次生成一个 Signature。拿新生成的 Signature 和 JWT 中的 Signature 作对比,如果一样就说明 Header 和 Payload 没有被修改。

不过,如果服务端的秘钥也被泄露的话,黑客就可以同时篡改 Signature、Header、Payload 了。黑客直接修改了 Header 和 Payload 之后,再重新生成一个 Signature 就可以了。

密钥一定保管好,一定不要泄露出去。JWT 安全的核心在于签名,签名安全的核心在密钥。

5.3 如何加强JWT的安全性?

  1. 使用安全系数高的加密算法。
  2. 使用成熟的开源库,没必要造轮子。
  3. JWT 存放在 localStorage 中而不是 Cookie 中,避免 CSRF 风险。
  4. 一定不要将隐私信息存放在 Payload 当中。
  5. 密钥一定保管好,一定不要泄露出去。JWT 安全的核心在于签名,签名安全的核心在密钥。
  6. Payload 要加入 exp (JWT 的过期时间),永久有效的 JWT 不合理。并且,JWT 的过期时间不易过长。
  7. ...........

5.4 JWT身份认证常见问题及解决方案

注销登录等场景下JWT还有效

与之类似的具体相关场景有:

  • 退出登录;
  • 修改密码;
  • 服务端修改了某个用户具有的权限或者角色;
  • 用户的帐户被封禁/删除;
  • 用户被服务端强制注销;
  • 用户被踢下线;
  • .........

这个问题不存在于 Session 认证方式中,因为在 Session 认证方式中,遇到这种情况的话服务端删除对应的 Session 记录即可。但是,使用 JWT 认证的方式就不好解决了。我们也说过了,JWT 一旦派发出去,如果后端不增加其他逻辑的话,它在失效之前都是有效的。

那我们如何解决这个问题呢?查阅了很多资料,我简单总结了下面 3 种方案:

1、将 JWT 存入数据库

将有效的 JWT 存入数据库中,更建议使用内存数据库比如 Redis。如果需要让某个 JWT 失效就直接从 Redis 中删除这个 JWT 即可。但是,这样会导致每次使用 JWT 都要先从 Redis 中查询 JWT 是否存在的步骤,而且违背了 JWT 的无状态原则。

2、黑名单机制

和上面的方式类似,使用内存数据库比如 Redis 维护一个黑名单,如果想让某个 JWT 失效的话就直接将这个 JWT 加入到 黑名单 即可。然后,每次使用 JWT 进行请求的话都会先判断这个 JWT 是否存在于黑名单中。

前两种方案的核心在于将有效的 JWT 存储起来或者将指定的 JWT 拉入黑名单。

虽然这两种方案都违背了 JWT 的无状态原则,但是一般实际项目中我们通常还是会使用这两种方案。

3、保持令牌的有效期限短并经常轮换

很简单的一种方式。但是,会导致用户登录状态不会被持久记录,而且需要用户经常登录。

另外,对于修改密码后 JWT 还有效问题的解决还是比较容易的。说一种我觉得比较好的方式:使用用户的密码的哈希值对 JWT 进行签名。因此,如果密码更改,则任何先前的令牌将自动无法验证。

5.4.1 JWT续签问题

JWT 有效期一般都建议设置的不太长,那么 JWT 过期后如何认证,如何实现动态刷新 JWT,避免用户经常需要重新登录?

我们先来看看在 Session 认证中一般的做法:假如 Session 的有效期 30 分钟,如果 30 分钟内用户有访问,就把 Session 有效期延长 30 分钟。

JWT 认证的话,我们应该如何解决续签问题呢?查阅了很多资料,我简单总结了下面 4 种方案:

1、类似于 Session 认证中的做法(不推荐)

这种方案满足于大部分场景。假设服务端给的 JWT 有效期设置为 30 分钟,服务端每次进行校验时,如果发现 JWT 的有效期马上快过期了,服务端就重新生成 JWT 给客户端。客户端每次请求都检查新旧 JWT,如果不一致,则更新本地的 JWT。这种做法的问题是仅仅在快过期的时候请求才会更新 JWT ,对客户端不是很友好。

2、每次请求都返回新 JWT(不推荐)

这种方案的的思路很简单,但是,开销会比较大,尤其是在服务端要存储维护 JWT 的情况下。

3、JWT 有效期设置到半夜(不推荐)

这种方案是一种折衷的方案,保证了大部分用户白天可以正常登录,适用于对安全性要求不高的系统

4、用户登录返回两个 JWT(推荐)

借鉴OAuth2.0的方法,第一个是 accessJWT ,它的过期时间 JWT 本身的过期时间比如半个小时,另外一个是 refreshJWT 它的过期时间更长一点比如为 1 天。refreshJWT 只用来获取 accessJWT,不容易被泄露。

客户端登录后,将 accessJWT 和 refreshJWT 保存在本地,每次访问将 accessJWT 传给服务端。服务端校验 accessJWT 的有效性,如果过期的话,就将 refreshJWT 传给服务端。如果有效,服务端就生成新的 accessJWT 给客户端。否则,客户端就重新登录即可。

这种方案的不足是:

  • 需要客户端来配合;
  • 用户注销的时候需要同时保证两个 JWT 都无效;
  • 重新请求获取 JWT 的过程中会有短暂 JWT 不可用的情况(可以通过在客户端设置定时器,当 accessJWT 快过期的时候,提前去通过 refreshJWT 获取新的 accessJWT);
  • 存在安全问题,只要拿到了未过期的 refreshJWT 就一直可以获取到 accessJWT。不过,由于 refreshJWT 只用来获取 accessJWT,不容易被泄露。

对于第四点,这里展开进行分析,对于续期问题,可能有人会说,使用单个JWT+redis+自动续期这种方案来实现,但是   单token+redis+自动续期的缺点:

单token设置短期的话,虽然一直操作可以通过拦截器重置token过期时间让它续期,但是如果隔一会儿不操作不续期,超过过期时间就过期了,那么用户需要重新登录,体验不好。

单token设置长期的话,就会有被盗用的风险。

如果是自动续期还是同一个token,那么token过期时间延长变成长期token那么还是会有盗用的风险。

如果是自动续期的时候刷新token,那么是拦截器是否需要返回新的token给前端,重新发起请求?(长期的自动续期有盗用风险,短期的自动续期如果隔会儿不操作还是会有重新登录的问题)

那么有没有一种实现方式既能活跃用户长时间登录,token还能安全不被盗用呢?

引入refresh token,就解决了【access token设置时间比较长,容易泄露造成安全问题,设置时间比较短,又需要频繁让用户授权】的矛盾。

使用双token无感刷新:

双token:一个短期token,一个长期token。

短期token为访问token。

长期token为refreshToken。当短期token过期,前端发起请求刷新token接口入参为refreshToken,用来获取新的短期token,同时获取新的refreshToken返回给前端。(刷新token和refreshToken都是为了防止两种token永不过期并且一直重复会被盗用)

刷新token接口中处理:校验refreshToken是否存在redis中,如果存在则刷新token和refreshToken返回前端(短期token刷新token和过期时间。长期token刷新token,是否刷新过期时间(续期)呢?)。前端用新的token访问。

如果不存在则返回未登录,让前端转去登录。

关于refreshToken是否续期的问题:

如果不续期,那么最终refreshToken会过期,可能导致用户正在操作的时候,强制重新登录。

如果续期,用户经常活跃的话,accessToken和refreshToken都会进行续期,用户可以一直在线。不会被强制重新登录。

当用户长时间不活跃的话,即refreshToken长时间不刷新过期时间,下次访问就会在refreshToken过期时,强制重新登录。

总结:

越频繁传输的越有风险,对于这种有风险的,要么避免频繁传输,如果不能避免就减少有效时间。

  • access_token频繁传输的风险通过缩短有效时间来解决。
  • access_token有效期短可能导致用户体验不佳,通过refresh_token解决。
  • refresh_token本身使用频率低,所以有效期长点也可以。

5.4.2 JWT实现强制某用户只能在一种客户端登录怎么实现?

案例描述:

如果一个用户在手机A中登录了,然后又在手机B中登录,怎么做到B登录后让A过期?

这个其实蛮好实现的,我简单总结了下面 3 种方案:

  1. 可以在JWT中的payload中记录ID,在服务端给客户端签发token的时候,后端记录该JWT的ID和JWT本身,将其看作一个Map,客户端发送请求时候,服务端验证是否存在该记录,有记录的话,将其删除即可(手机A在之后的访问中就会发现JWT失效,退出登录),然后再根据新的id,签发给客户端新的JWT即可。
  2. 签名的时候加上时间戳或者其他随机数,一旦登录就更新签名,之前的签名就失效了,这样就不会出现多个设备在线了。
  3. 或者登录时候通过记录设备信息、IP地址等......

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/715137.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

讨论C++模板

讨论C模板 函数重载和泛型编程模板分类函数模板语法原理函数模板的实例化隐式实例化显示实例化 匹配原则 类模板语法类模板的实例化 C支持了函数重载&#xff0c;通过函数名相同&#xff0c;参数列表不同来构成函数重载&#xff0c;以达到方便程序员调用。但还是没有改变代码大…

Python基础教程(二十一):多线程

&#x1f49d;&#x1f49d;&#x1f49d;首先&#xff0c;欢迎各位来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里不仅可以有所收获&#xff0c;同时也能感受到一份轻松欢乐的氛围&#xff0c;祝你生活愉快&#xff01; &#x1f49d;&#x1f49…

C++ 56 之 菱形继承

#include <iostream> #include <string> using namespace std;// 动物类: 虚基类 class Animal{ public:int m_age; };// 羊类 class Sheep : virtual public Animal{ // virtual虚继承 };// 驼类 class Tuo : virtual public Animal{ // virtual虚继承 指向同一块…

【计算机毕业设计】基于Springboot的车辆管理系统【源码+lw+部署文档】

包含论文源码的压缩包较大&#xff0c;请私信或者加我的绿色小软件获取 免责声明&#xff1a;资料部分来源于合法的互联网渠道收集和整理&#xff0c;部分自己学习积累成果&#xff0c;供大家学习参考与交流。收取的费用仅用于收集和整理资料耗费时间的酬劳。 本人尊重原创作者…

016基于SSM+Jsp的医院远程诊断系统

开发语言&#xff1a;Java框架&#xff1a;ssm技术&#xff1a;JSPJDK版本&#xff1a;JDK1.8服务器&#xff1a;tomcat7数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09;数据库工具&#xff1a;Navicat11开发软件&#xff1a;eclipse/myeclipse/ideaMaven包…

排序1---插入排序

目录 插入排序的基本思路&#xff1a; 插入排序的代码实现&#xff1a; 代码&#xff1a; 代码解读&#xff1a; 插入排序的时间、空间复杂度&#xff1a; 插入排序的基本思路&#xff1a; 插入排序是一个比较简单的排序。 我们插入排序就是我们先假设前面的一段区间有序…

子域名爆破工具

本章将记录3个子域名爆破工具subfinder、OneForAll、ksubdomain的安装&#xff0c;项目地址分别是&#xff1a; https://github.com/projectdiscovery/subfinder https://github.com/shmilylty/OneForAll https://github.com/knownsec/ksubdomain 1. subfinder 我是在Windows…

深度分析2024年中国 AI 产业商业化实践案例

京东云言犀 提供客户全渠道全生命周期的营服销一体化智能服务 京东云言犀依托于全栈自研的人工智能技术&#xff0c;基于京东集团广泛实体业务、庞大而又复杂的产业生态&#xff0c;从内部真实、复杂的海量业务场景实践中推出千亿级参数的言犀大模型&#xff0c;打造全新的智…

计算机网络 —— 网络层(CIDR)

计算机网络 —— 网络层&#xff08;CIDR&#xff09; CIDR的提出背景什么是CIDR基本概念划分示例应用优势 举个例子路由聚合常用数字 我们今天来看IPv4地址划分的另一种方法 —— CIDR。 CIDR的提出背景 CIDR&#xff08;无类域间路由&#xff0c;Classless Inter-Domain Ro…

ROS机器人小车建模仿真与SLAM

文章目录 一、URDF二、创建小车模型1.创建功能包2.导入依赖3.创建urdf,launch文件&#xff1a;4.可视化 三、添加雷达1.xacro文件2.集成和修改launch3.添加摄像头和雷达 三.GAZEBO仿真四、orbslam2kitti1.下载2.安装编译ORB_SLAM23.运行Kitee数据集 一、URDF ​ URDF&#xff…

工程设计问题-步进锥滑轮问题

该问题的主要目标是用5个变量使4阶锥皮带轮的重量最小&#xff0c;其中4个变量是皮带轮每个台阶的直径&#xff0c;最后一个变量是滑轮的宽度。该问题包含11个非线性约束&#xff0c;以保证传动功率必须为0.75马力。 Abhishek Kumar, Guohua Wu, Mostafa Z. Ali, Rammohan Mall…

【C++】实现学生管理系统(完整版)

&#x1f495;&#x1f495;&#x1f495;大家好&#xff0c;这是作业侠系列之C实现学生管理系统&#xff0c;还是那句话&#xff0c;大家不想cv或者cv了跑不起来,三连后都可以来找我要源码&#xff0c;私信或评论留下你的邮箱即可。有任何问题有可以私聊我&#xff0c;大家觉得…

Nginx + Tomcat 负载均衡、动静分离

前言 Tomcat简介 最初是由Sun的软件构架师詹姆斯邓肯戴维森开发 安装Tomcat后&#xff0c;安装路径下面的目录和文件&#xff0c;是使用或者配置Tomcat的重要文件 Nginx 应用 Nginx是一款非常优秀的HTTP服务器软件 &#xff08;1&#xff09;支持高达50 000个并发连接数的响应…

workhome 2024.06.16 math-6

数学分析语句断句&#xff0c;分析&#xff0c;画画做图&#xff0c;逻辑&#xff0c;解析&#xff0c;计算过程&#xff0c;严谨&#xff0c;我们程序出错多数是因为不够严谨&#xff0c;少了漏了可能出现的情况。 1&#xff09; https://download.csdn.net/download/spencer_…

Aspose将doc,ppt转成pdf

1.需要引入的jar包 链接: https://pan.baidu.com/s/1t3wqq7KrHi50K9KX3-Eb9A?pwdu4se 提取码: u4se <dependency><groupId>com.aspose</groupId><artifactId>aspose-words-jdk16</artifactId><version>15.8.0</version><scop…

xss+csrf项目实例

项目背景&#xff1a; 如下&#xff1a;我们是在一个类似文章管理系统的网站上面发现的该漏洞。我们将其运行在本地的phpstudy集成环境上面。 源码地址下载链接&#xff1a;https://pan.baidu.com/s/1MpnSAq7a_oOcGh4XgPE-2w 提取码&#xff1a;4444 考察内容&#xff1a; …

C. Rooks Defenders(树状数组)

You have a square chessboard of size nnnn. Rows are numbered from top to bottom with numbers from 11 to nn, and columns — from left to right with numbers from 11 to nn. So, each cell is denoted with pair of integers (x,y)(x,y) (1≤x,y≤n1≤x,y≤n), where …

质疑标普,理解标普,加入标普

上周我在文章里提到过&#xff0c;标普信息科技LOF(161128)出现套利机会。每天申购卖出&#xff0c;到现在一个账户56*6336润。 得益于美股七巨头轮流领涨&#xff0c;161128依旧坚挺&#xff0c;每天溢价都是10%&#xff0c;成交量1个多亿&#xff0c;场内新增份额才400万份&…

大模型生成的常见Top-k、Top-p、Temperature参数

参考&#xff1a; https://zhuanlan.zhihu.com/p/669661536 topK&#xff0c;topP https://www.douyin.com/video/7380126984573127945 主要是softmax产生的词表每个词的概率分布后&#xff0c; topK&#xff0c;比如K3&#xff0c;表示采样概率最大的前3个&#xff0c;其他全…

第一篇——怎样堵住我们人生错误的源头

目录 一、背景介绍二、思路&方案三、过程1.思维导图2.文章中经典的句子理解3.学习之后对于投资市场的理解4.通过这篇文章结合我知道的东西我能想到什么&#xff1f; 四、总结五、升华 一、背景介绍 再次开始了孙子兵法的学习&#xff0c;之前听完就让我醍醐灌顶&#xff0…