JWT(JSON Web令牌)自动延长有效期


509

我想对我们的新REST API实施基于JWT的身份验证。但是,由于在令牌中设置了有效期,是否可以自动延长有效期?我不希望用户在此期间正在使用该应用程序的情况下,每隔X分钟登录一次。那将是巨大的用户体验失败。

但是,延长有效期会创建一个新令牌(旧令牌在过期之前仍然有效)。在每个请求之后生成一个新令牌对我来说听起来很愚蠢。当多个令牌同时有效时,听起来像一个安全问题。当然,我可以使用黑名单来使旧的使用过的无效,但我需要存储令牌。JWT的好处之一是无需存储。

我发现Auth0如何解决它。它们不仅使用JWT令牌,而且还使用刷新令牌:https : //docs.auth0.com/refresh-token

但是同样,要实现此功能(不使用Auth0),我需要存储刷新令牌并保持其过期。那么,真正的好处是什么?为什么不只有一个令牌(而不是JWT)并在服务器上保留到期时间?

还有其他选择吗?使用JWT是否不适合这种情况?


1
实际上,一次使用多个有效令牌可能不存在安全问题...实际上有无限数量的有效令牌...那么,为什么要拥有刷新令牌呢?我将在每次请求后重新生成它们,这实际上应该不是问题。
2014年

1
对于SPA,请查看我的博客文章:blog.wong2.me/2017/02/20/refresh-auth0-token-in-spa
wong2

2
@maryo我认为在任何给定时间(潜在地)有成百上千的未使用的有效JWT会增加您的攻击范围,并且是安全风险。在我看来,应该谨慎地发布JWT,因为它们是带有令牌的城堡的访问令牌。
java-addict301

Answers:


589

我在Auth0工作,并参与了刷新令牌功能的设计。

这完全取决于应用程序的类型,这是我们推荐的方法。

网络应用

一个好的模式是在令牌过期之前刷新它。

将令牌到期时间设置为一周,并在用户每次打开Web应用程序时以及每小时一小时刷新令牌。如果用户超过一个星期没有打开应用程序,则他们将不得不再次登录,这是可以接受的Web应用程序UX。

要刷新令牌,您的API需要一个新的终结点,该终结点将接收有效的,未到期的JWT,并返回带有新到期字段的相同签名的JWT。然后,Web应用程序会将令牌存储在某处。

移动/本机应用

大多数本机应用程序仅登录一次。

这个想法是,刷新令牌永远不会过期,并且可以始终将其交换为有效的JWT。

永不过期的令牌的问题在于,永不意味着永不。如果丢失手机怎么办?因此,它需要以某种方式由用户识别,并且应用程序需要提供一种撤消访问的方法。我们决定使用设备的名称,例如“ maryo的iPad”。然后,用户可以转到该应用程序并撤消对“ maryo的iPad”的访问权限。

另一种方法是在特定事件上撤销刷新令牌。一个有趣的事件是更改密码。

我们认为JWT在这些用例中没有用,因此我们使用随机生成的字符串并将其存储在自己的一边。


42
对于Web应用程序推荐的方法,如果令牌有效期为一周,我们是否不关心有人拦截令牌然后能够使用这么长时间?免责声明:我不太了解我在说什么。
user12121234'1

30
@wbeange是的,即使使用cookie,拦截也是一个问题。您应该使用https。
何塞F. Romaniello

15
@JoséF.Romaniello在您的Web应用程序示例中,除了必须存储令牌之外,其他所有事情对我来说都是有意义的。我认为JWT的优点是无状态身份验证-意味着Web应用程序不必在签名时存储令牌。我认为服务器可以只检查令牌的有效性,确保它在有效期内,然后发出更新的JWT令牌。您能详细说明一下吗?也许我只是对JWT不够了解。
Lo-Tan)

7
两个问题/关注点:1- Web应用程序案例:为什么不允许过期的令牌进行刷新?假设您设置了一个短的到期时间(1小时),并在令牌过期时对后端服务器进行了重新调用。2-在令牌中存储散列(带有随机盐)的密码是否存在安全问题?想法是,如果存在,后端服务器可以在要求续订时检查数据库中存储的密码,并在密码不匹配时拒绝请求。这将涵盖Mobile / Native应用程序密码更改,从而允许将解决方案扩展到Mobile用例。
psamaan 2015年

7
-1公开一个盲目地重新签名任何令牌以延长其验证期限的公共API是不好的。现在,您所有的代币都有有效的无限期。签名令牌的行为应包括在签名时针对该令牌中的每个声明进行的相应身份验证检查。
菲尔(Phil)

69

如果您自己处理身份验证(即,不使用Auth0这样的提供程序),则可以使用以下方法:

  1. 发行JWT令牌的有效期相对较短,例如15分钟。
  2. 应用程序在任何需要令牌的交易之前检查令牌的到期日期(令牌包含到期日期)。如果令牌已过期,则它首先要求API“刷新”令牌(这对UX透明地进行)。
  3. API获取令牌刷新请求,但首先检查用户数据库以查看是否已针对该用户配置文件设置了“ reauth”标志(令牌可以包含用户ID)。如果存在该标志,则令牌刷新被拒绝,否则发出新的令牌。
  4. 重复。

例如,当用户重置密码时,将在数据库后端设置“ reauth”标志。当用户下次登录时,该标志将被删除。

另外,假设您有一项政策,即用户必须每72小时至少登录一次。在这种情况下,您的API令牌刷新逻辑还将从用户数据库中检查用户的上次登录日期,并在此基础上拒绝/允许令牌刷新。


7
我认为这不是安全的。如果我是攻击者并窃取了您的令牌并将其发送到服务器,则服务器将检查并查看标志设置为true,这非常好,因为它将阻止刷新。我认为问题是,如果受害者更改了密码,则该标志将设置为false,现在攻击者可以使用该原始令牌进行刷新。
user2924127

6
@ user2924127没有auth解决方案是完美的,并且总会有一些折衷。如果攻击者可以“窃取您的令牌”,那么您可能会担心更大的问题。设置最大令牌生存期将是对上述内容的有益调整。
IanB

27
您可以在令牌中包含hash(bcrypt_password_hash),而不是在数据库中包含另一个字段reauth标志。然后在刷新令牌时,只需确认hash(bcrypt_password_hash)是否等于令牌中的值即可。为了拒绝令牌刷新,只需要更新密码哈希即可。
2015年

4
@bas,考虑到优化和性能,我认为密码哈希验证将是多余的,并且对服务器有更多影响。增加令牌的大小,以便签名公司/验证花费更多时间。服务器的其他哈希计算以获取密码。使用额外的字段方法,您只需使用简单的布尔值在重新计算中进行验证即可。对于额外的字段,db更新的频率较低,但是令牌刷新的频率较高。您将获得可选的强制个人重新登录任何现有会话(移动,网络等)的服务。
le0diaz '16

6
我认为user2924127的第一个评论实际上是错误的。更改密码后,该帐户将被标记为需要重新认证,因此任何现有的过期令牌都将无效。
拉尔夫(Ralph)

15

在后端使用RESTful API将我们的应用程序迁移到HTML5时,我在做些修改。我想到的解决方案是:

  1. 成功登录后,将向客户端颁发令牌,令牌的会话时间为30分钟(或任何通常的服务器端会话时间)。
  2. 创建了客户端计时器,以调用服务以在令牌到期之前对其进行续订。新令牌将替换将来的调用中的现有令牌。

如您所见,这减少了频繁刷新令牌请求。如果用户在触发续订令牌调用之前关闭浏览器/应用程序,则先前的令牌将及时过期,用户将必须重新登录。

可以实施更复杂的策略来满足用户的不活动(例如忽略打开的浏览器选项卡)。在这种情况下,续签令牌调用应包括预期的到期时间,该时间不应超过定义的会话时间。该应用程序将必须相应地跟踪上一次用户交互。

我不喜欢设置长时间有效的想法,因此这种方法可能不适用于需要较少身份验证的本机应用程序。


1
如果计算机暂停/睡眠该怎么办。计时器将一直计数到到期,但令牌实际上已经过期。计时器在这种情况下不起作用
Alex Parij

@AlexParij您可以将其与固定时间进行比较,如下所示: stackoverflow.com/a/35182296/1038456
Aparajita

2
允许客户请求具有首选到期日期的新令牌对我来说就像是安全隐患。
java-addict301

14

使JWT无效且在后端没有任何其他安全存储的替代解决方案是jwt_version在users表上实现新的整数列。如果用户希望注销或使现有令牌过期,则只需增加该jwt_version字段即可。

生成新的JWT时,请将编码jwt_version为JWT有效负载,如果新的JWT应该替换所有其他JWT,则可以选择预先增加值。

验证JWT时,该jwt_version字段会与和进行比较,user_id并且仅当匹配时才授予授权。


1
这在多个设备上都有问题。本质上,如果您在一台设备上注销,则该设备到处注销。对?
山姆·沃什伯恩

4
嘿,根据您的要求,这可能不是一个“问题”,但是您是对的。这不支持每个设备的会话管理。
奥利·贝内特

这是否意味着jwt_version必须存储在服务器端,以使身份验证方案变得“类似于会话”并破坏JWT的基本目的?
ChetPrickles

8

好的问题-问题本身就有大量的信息。

文章“ 刷新令牌:何时使用它们以及它们如何与JWT交互”为这种情况提供了一个好主意。一些要点是:

  • 刷新令牌携带获取新访问令牌所需的信息。
  • 刷新令牌也可以过期,但寿命很长。
  • 刷新令牌通常要遵守严格的存储要求,以确保它们不会泄漏。
  • 授权服务器也可以将它们列入黑名单。

还看看auth0 / angular-jwt angularjs

对于Web API。使用ASP .NET Web API 2和Owin阅读在AngularJS App中启用OAuth刷新令牌


也许我读错了...但是标题以“ Refresh Tokens ...”开头的文章除了您在此处提到的内容外,不包含有关刷新令牌的内容。
Ievgen Martynov

8

我实际上是使用Guzzle客户端在PHP中实现此操作的,以为api创建客户端库,但该概念应适用于其他平台。

基本上,我发行两个令牌,一个短(5分钟),一个长令牌,该令牌在一周后到期。如果客户端库收到对某个请求的401响应,则使用中间件尝试对短令牌进行一次刷新。然后,它将再次尝试原始请求,并且如果能够刷新,则对用户透明地获得正确的响应。如果失败,它将仅将401发送给用户。

如果短令牌已过期,但仍然是真实的,而长令牌是有效且可靠的,它将使用长令牌认证的服务上的特殊端点刷新短令牌(这是唯一的用途)。然后它将使用短令牌获得新的长令牌,从而在每次刷新短令牌时将其延长一周。

这种方法还允许我们在最多5分钟内撤消访问权限,这对于我们的使用是可以接受的,而不必存储令牌的黑名单。

后期编辑:在重新读了几个月之后,我重新指出了这一点,我应该指出,您可以在刷新短令牌时撤消访问权限,因为它提供了进行更昂贵调用的机会(例如,调用数据库以查看用户是否已被禁止),而无需在每次致电您的服务时付费。


8

以下是撤销您的JWT访问令牌的步骤:

1)登录后,发送2个令牌(访问令牌,刷新令牌)作为对client的响应。
2)访问令牌的有效期将更少,而刷新将具有较长的到期时间。
3)客户端(前端)将在其本地存储中存储刷新令牌,并在cookie中访问令牌。
4)客户端将使用访问令牌来调用api。但是,当它过期时,请从本地存储中选择刷新令牌,然后调用auth服务器api以获取新令牌。
5)您的身份验证服务器将公开一个api,它将接受刷新令牌并检查其有效性并返回新的访问令牌。
6)刷新令牌过期后,用户将注销。

如果您需要更多详细信息,请让我知道,我也可以共享代码(Java + Spring引导)。


如果您在GitHub中有项目链接,能否请您分享?
Arun Kumar N


6

jwt-autorefresh

如果您使用的是节点(React / Redux / Universal JS),则可以安装npm i -S jwt-autorefresh

该库在访问令牌到期之前(根据令牌中编码的exp声明)在用户计算的秒数内安排JWT令牌的刷新。它具有广泛的测试套件,并检查相当多的条件,以确保任何奇怪的活动都伴随着有关环境配置错误的描述性消息。

完整的示例实现

import autorefresh from 'jwt-autorefresh'

/** Events in your app that are triggered when your user becomes authorized or deauthorized. */
import { onAuthorize, onDeauthorize } from './events'

/** Your refresh token mechanism, returning a promise that resolves to the new access tokenFunction (library does not care about your method of persisting tokens) */
const refresh = () => {
  const init =  { method: 'POST'
                , headers: { 'Content-Type': `application/x-www-form-urlencoded` }
                , body: `refresh_token=${localStorage.refresh_token}&grant_type=refresh_token`
                }
  return fetch('/oauth/token', init)
    .then(res => res.json())
    .then(({ token_type, access_token, expires_in, refresh_token }) => {
      localStorage.access_token = access_token
      localStorage.refresh_token = refresh_token
      return access_token
    })
}

/** You supply a leadSeconds number or function that generates a number of seconds that the refresh should occur prior to the access token expiring */
const leadSeconds = () => {
  /** Generate random additional seconds (up to 30 in this case) to append to the lead time to ensure multiple clients dont schedule simultaneous refresh */
  const jitter = Math.floor(Math.random() * 30)

  /** Schedule autorefresh to occur 60 to 90 seconds prior to token expiration */
  return 60 + jitter
}

let start = autorefresh({ refresh, leadSeconds })
let cancel = () => {}
onAuthorize(access_token => {
  cancel()
  cancel = start(access_token)
})

onDeauthorize(() => cancel())

免责声明:我是维护者


关于这个问题,我看到了它使用的解码功能。它是否假定可以在不使用秘密的情况下解码JWT?它与通过秘密签名的JWT一起使用吗?
吉安·佛朗哥·扎巴里诺

3
是的,解码是仅用于客户端的解码,因此不应知道其机密。该机密用于在服务器端对JWT令牌进行签名,以验证您的签名最初是用于生成JWT的,并且永远不要在客户端使用。JWT的神奇之处在于,它的有效负载可以在客户端解码,并且内部的声明可以用于构建您的UI,而无需任何秘密。jwt-autorefresh对其进行解码的唯一方法是提取exp声明,以便可以确定安排下一次刷新的距离。
cchamberlain

1
哦,很高兴知道,有些事情没有道理,但现在已经有意义了。感谢您的回答。
吉安·佛朗哥·扎巴里诺

4

我通过在令牌数据中添加变量来解决此问题:

softexp - I set this to 5 mins (300 seconds)

我将expiresIn选项设置为所需的时间,然后用户将被迫再次登录。我的设定为30分钟。该值必须大于的值softexp

当我的客户端应用向服务器API发送请求时(需要令牌,例如,客户列表页面),服务器会根据其原始过期(expiresIn)值检查提交的令牌是否仍然有效。如果无效,则服务器将以特定于此错误的状态响应,例如。INVALID_TOKEN

如果令牌基于expiredIn值仍然有效,但已超过该softexp值,则服务器将针对此错误以单独的状态响应,例如。EXPIRED_TOKEN

(Math.floor(Date.now() / 1000) > decoded.softexp)

在客户端,如果收到EXPIRED_TOKEN响应,它应该通过向服务器发送续订请求来自动续订令牌。这对用户是透明的,并会自动处理客户端应用程序。

服务器中的续订方法必须检查令牌是否仍然有效:

jwt.verify(token, secret, (err, decoded) => {})

如果上述方法失败,服务器将拒绝续订令牌。


这个策略看起来不错。但是我认为应该以“最大续订量”作为补充,因为(也许)用户sessión可以永远存活。
Juan Ignacio Barisich

1
您可以在令牌数据中设置hardExp变量,以设置最大日期以强制令牌过期,或者可以设置一个计数器,该计数器在每次令牌更新时都会减少,从而限制了令牌更新的总量。
詹姆斯A

1
没错 我认为这是“必须的”。
Juan Ignacio Barisich

2

这种方法怎么样:

  • 对于每个客户端请求,服务器都会将令牌的到期时间与(currentTime-lastAccessTime)进行比较
  • 如果expirationTime <(currentTime-lastAccessedTime),它将最后一个lastAccessedTime更改为currentTime。
  • 如果浏览器上的闲置时间超过expirationTime或关闭了浏览器窗口并且expirationTime>(currentTime-lastAccessedTime),则服务器可以使令牌到期并要求用户再次登录。

在这种情况下,我们不需要其他端点来刷新令牌。将不胜感激。


这是今天的好选择吗,实现起来似乎很容易。
b.ben

4
在这种情况下,您将lastAccessedTime存储在哪里?您必须在后端和每个请求上执行此操作,因此它成为不必要的有状态解决方案。
antgar9 2016年

2

如今,很多人选择做与JWTs会话管理没有意识到他们正在放弃了的缘故感觉简单。我的回答详细说明了问题的第二部分:

那么,真正的好处是什么?为什么不只有一个令牌(而不是JWT)并在服务器上保留到期时间?

还有其他选择吗?使用JWT是否不适合这种情况?

JWT能够支持基本的会话管理,但有一些限制。作为自描述令牌,它们在服务器端不需要任何状态。这使它们具有吸引力。例如,如果服务没有持久层,则不需要仅将其引入会话管理。

但是,无国籍也是其缺点的主要原因。由于它们仅以固定的内容和到期时间发布一次,因此您无法使用典型的会话管理设置来完成您想做的事情。

即,您不能按需使它们失效。这意味着您无法实施安全登出,因为没有办法使已经发出的令牌过期。同样,您也无法实现空闲超时。一种解决方案是保留黑名单,但这会引入状态。

我写了一篇文章,详细解释了这些缺点。明确地说,您可以通过增加更多的复杂性(滑动会话,刷新令牌等)来解决这些问题。

至于其他选项,如果您的客户仅通过浏览器与您的服务进行交互,我强烈建议您使用基于cookie的会话管理解决方案。我还汇编了当前在网络上广泛使用的列表身份验证方法

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.