我想为我们的新REST API实现基于JWT的身份验证 . 但是由于在令牌中设置了到期,是否可以自动延长它?我不希望用户在每X分钟后需要登录,如果他们在那段时间内积极使用该应用程序 . 这将是一个巨大的用户体验失败 .
但是延长过期会创建一个新令牌(旧令牌在到期之前仍然有效) . 每次请求后生成一个新令牌对我来说都是愚蠢的 . 当多个令牌同时有效时,听起来像是一个安全问题 . 当然,我可以使用黑名单使旧的旧列表无效,但我需要存储令牌 . JWT的一个好处就是没有存储空间 .
我发现Auth0是如何解决它的 . 它们不仅使用JWT令牌,还使用刷新令牌:https://docs.auth0.com/refresh-token
但同样,为了实现这一点(没有Auth0),我需要存储刷新令牌并保持其过期 . 那么真正的好处是什么?为什么不只有一个令牌(不是JWT)并在服务器上保持过期?
还有其他选择吗?使用JWT不适合这种情况吗?
9 回答
在您自己处理auth的情况下(即不使用Auth0之类的提供程序),以下内容可能有效:
发布JWT令牌,到期时间相对较短,比如15分钟 .
应用程序在任何需要令牌的事务(令牌包含到期日期)之前检查令牌到期日期 . 如果令牌已过期,那么它首先要求API为'refresh'令牌(这是对UX透明完成的) .
API获取令牌刷新请求,但首先检查用户数据库以查看是否已针对该用户配置文件设置了'reauth'标志(令牌可以包含用户ID) . 如果该标志存在,则拒绝令牌刷新,否则发出新令牌 .
重复 .
例如,当用户重置密码时,将设置数据库后端中的“reauth”标志 . 用户下次登录时会删除该标志 .
此外,假设您有一项政策,用户必须至少每72小时登录一次 . 在这种情况下,您的API令牌刷新逻辑还将检查用户从用户数据库的上次登录日期,并在此基础上拒绝/允许令牌刷新 .
这种方法怎么样:
对于每个客户端请求,服务器将令牌的expirationTime与(currentTime - lastAccessTime)进行比较
如果 expirationTime < (currentTime - lastAccessedTime) ,则将最后一个lastccessedTime更改为currentTime .
如果浏览器长时间不活动超过expirationTime,或者浏览器窗口关闭且 the expirationTime > (currentTime - lastAccessedTime) ,则服务器可以使令牌过期并要求用户再次登录 .
在这种情况下,我们不需要额外的终点来刷新令牌 . 非常感谢任何feedack .
我实际上是在PHP中使用Guzzle客户端为api创建了一个客户端库,但这个概念应该适用于其他平台 .
基本上,我会发行两个令牌,一个短的(5分钟)和一个长的,一周后到期 . 如果客户端库收到对某个请求的401响应,则使用中间件尝试刷新短令牌 . 然后它将再次尝试原始请求,如果能够刷新,则对用户透明地获得正确的响应 . 如果失败,它只会将401发送给用户 .
如果短令牌已过期,但仍然是可信的并且长令牌有效且可信,则它将使用长令牌进行身份验证的服务上的特殊 endpoints 刷新短令牌(这是唯一可用于此的令牌) . 然后它将使用短令牌获取新的长令牌,从而每次刷新短令牌时将其延长一周 .
这种方法还允许我们在最多5分钟内撤销访问权限,这对我们的使用是可以接受的,而不必存储令牌的黑名单 .
延迟编辑:重新阅读这几个月后,我应该指出你可以在刷新短令牌时撤销访问权限,因为它提供了更昂贵的调用的机会(例如,调用数据库来查看用户是否已被禁止),无需在每次拨打您的服务时付费 .
我在Auth0工作,我参与了刷新令牌功能的设计 .
这一切都取决于应用程序的类型,这是我们推荐的方法 .
Web应用程序
一个好的模式是在令牌到期之前刷新令牌 .
将令牌过期设置为一周,并在每次用户打开Web应用程序时每隔一小时刷新令牌 . 如果用户未打开应用程序超过一周,则必须再次登录,这是可接受的Web应用程序UX .
要刷新令牌,您的API需要一个新 endpoints ,该 endpoints 接收有效的,未过期的JWT,并使用新的过期字段返回相同的签名JWT . 然后,Web应用程序将令牌存储在某处 .
移动/本机应用程序
大多数本机应用程序只登录一次 .
我们的想法是刷新令牌永不过期,并且可以始终为有效的JWT进行交换 .
永不过期的令牌问题是 never 意味着永远不会 . 如果丢失手机怎么办?因此,它需要以某种方式由用户识别,并且应用程序需要提供撤销访问的方法 . 我们决定使用设备的名称,例如"maryo's iPad" . 然后,用户可以转到该应用程序并撤消对"maryo's iPad"的访问权限 .
另一种方法是撤消特定事件上的刷新令牌 . 一个有趣的事件是更改密码 .
我们认为JWT对这些用例没用,所以我们使用随机生成的字符串,然后将它存储在我们这边 .
在后端使用RESTful apis将应用程序移动到HTML5时,我正在修修补补 . 我想出的解决方案是:
成功登录后,会向客户端发出一个会话时间为30分钟(或通常的服务器端会话时间)的令牌 .
创建客户端计时器以调用服务以在令牌到期之前续订令牌 . 新令牌将替换将来调用中的现有令牌 .
如您所见,这减少了频繁的刷新令牌请求 . 如果用户在触发续订令牌呼叫之前关闭浏览器/应用程序,则先前令牌将及时到期,用户必须重新登录 .
更多可以实现复杂的策略以满足用户不活动(例如忽略打开的浏览器选项卡) . 在这种情况下,续订令牌调用应包括预期的到期时间,该时间不应超过定义的会话时间 . 应用程序必须相应地跟踪最后的用户交互 .
我不喜欢设置长期到期的想法,因此这种方法可能不适用于需要较少频繁验证的本机应用程序 .
jwt-autorefresh
如果您使用的是节点(React / Redux / Universal JS),则可以安装
npm i -S jwt-autorefresh
.此库根据用户计算的访问令牌到期之前的秒数(基于令牌中编码的exp声明)计划刷新JWT令牌 . 它有一个广泛的测试套件,并检查很多条件,以确保任何奇怪的活动伴随着有关您的环境配置错误的描述性消息 .
Full example implementation
disclaimer: I am the maintainer
我通过在令牌数据中添加一个变量来解决这个问题:
我将
expiresIn
选项设置为我想要的时间,然后再强制用户重新登录 . 我的设定为30分钟 . 这必须大于softexp
的值 .当我的客户端应用程序向服务器API(需要令牌,例如客户列表页面)发送请求时,服务器会根据其原始到期(
expiresIn
)值检查提交的令牌是否仍然有效 . 如果它无效,服务器将以特定于此错误的状态进行响应,例如 .INVALID_TOKEN
.如果令牌仍然基于
expiredIn
值有效,但已超过softexp
值,则服务器将以此错误的单独状态响应,例如 .EXPIRED_TOKEN
:在客户端,如果收到
EXPIRED_TOKEN
响应,则应通过向服务器发送续订请求自动续订令牌 . 这对用户是透明的,并自动处理客户端应用程序 .服务器中的续订方法必须检查令牌是否仍然有效:
如果上述方法失败,服务器将拒绝续订令牌 .
好问题 - 问题本身就有丰富的信息 .
文章Refresh Tokens: When to Use Them and How They Interact with JWTs为这种情况提供了一个好主意 . 一些要点是: -
刷新令牌包含获取新访问令牌所需的信息 .
刷新令牌也可以过期,但寿命相当长 .
刷新令牌通常受到严格的存储要求,以确保它们不会泄露 .
它们也可以被授权服务器列入黑名单 .
另请查看auth0/angular-jwt angularjs
对于Web API . 阅读Enable OAuth Refresh Tokens in AngularJS App using ASP .NET Web API 2, and Owin
在后端没有任何额外安全存储的情况下,使JWT无效的替代解决方案是在users表上实现新的
jwt_version
整数列 . 如果用户希望注销或使现有令牌失效,则只需递增jwt_version
字段 .生成新的JWT时,将
jwt_version
编码为JWT有效负载,如果新JWT应替换所有其他JWT,则可以选择性地增加该值 .验证JWT时,
jwt_version
字段与user_id
进行比较,只有匹配时才授予授权 .