我想对我们的新RESTAPI实现基于JWT的身份验证。但是由于在令牌中设置了过期,是否可以自动延长它?如果用户在这段时间内积极使用该应用程序,我不希望他们每隔X分钟登录一次。那将是一个巨大的用户体验失败。
但是延长到期时间会创建一个新的令牌(旧的令牌在到期之前仍然有效)。在每次请求之后生成一个新的令牌对我来说都是愚蠢的。当多个令牌同时有效时,听起来像是一个安全问题。当然,我可以使用黑名单使旧的旧的失效,但我需要存储令牌。JWT的一个好处是没有存储。
我发现Auth0是如何解决的。它们不仅使用JWT令牌,还使用刷新令牌:https://docs.auth0.com/refresh-token
但是,为了实现这个(没有auth0),我需要存储刷新令牌并保持它们的有效期。那么真正的好处是什么呢?为什么不只有一个令牌(而不是JWT)并在服务器上保持到期?
还有其他选择吗?使用JWT是否不适合这种情况?
- 实际上,对于许多有效的令牌,可能不会同时出现安全问题…实际上有效令牌的数量是无限的…那么,为什么要有一个刷新令牌呢?我将在每次请求后重新生成它们,这实际上不应该是一个问题。
- 对于SPA,请查看我的博客帖子:blog.wong2.me/2017/02/20/refresh-auth0-token-in-spa
- @Maryo我认为在任何给定的时间有(可能)成百上千的未使用的有效JWT会增加您的攻击足迹,这是一种安全风险。在我看来,JWT应该小心发行,因为它们是以某种方式使用城堡钥匙的访问令牌。
我在Auth0工作,参与了刷新令牌功能的设计。
这完全取决于应用程序的类型,这里是我们推荐的方法。
Web应用程序
一个好的模式是在令牌到期之前刷新它。
将令牌过期设置为一周,并在每次用户打开Web应用程序时以及每一小时刷新一次令牌。如果一个用户超过一周没有打开应用程序,他们将不得不再次登录,这是可接受的Web应用程序UX。
要刷新令牌,您的API需要一个新的端点,该端点接收一个有效的、未过期的JWT,并返回带有新到期字段的同一个已签名的JWT。然后Web应用程序将把令牌存储在某个地方。
移动/本机应用程序
大多数本机应用程序只登录一次。
其思想是刷新令牌永远不会过期,并且可以始终将其交换为有效的JWT。
令牌永不过期的问题是它永远意味着永不过期。如果你的手机丢了怎么办?因此,它需要以某种方式被用户识别,应用程序需要提供一种方法来撤销访问。我们决定使用该设备的名称,例如"Maryo的iPad"。然后用户可以进入应用程序并撤销对"Maryo'siPad"的访问。
另一种方法是撤销特定事件的刷新令牌。一个有趣的事件是更改密码。
我们相信JWT对于这些用例没有用处,所以我们使用随机生成的字符串,并将其存储在我们的一侧。
- 对于Web应用程序推荐的方法,如果令牌有效期为一周,我们是否不关心有人截取令牌,然后能够使用它这么长时间?免责声明:我不太清楚自己在说什么。
- @是的,拦截是一个问题,即使有cookie。你应该使用https。
- @Jos&233;f.Romaniello您能解释一下您所说的"我们认为JWT对这些用例没有用处,所以我们使用随机生成的字符串,并将其存储在我们的一边"是什么意思吗?哪些用例以及随机字符串的用途是什么?
- @devfox我的意思是刷新令牌不是JWT,而是一个不透明的令牌。
- 我使用一个角度截取器来实现每个HTTP请求的刷新。
- Web应用:如果您不喜欢每小时刷新一次,或者您想尝试其他解决方案,您可以让所有API请求通过一个服务器端代理,该代理以持久的方式存储用户的刷新令牌,并在注销用户之前截取401响应和交换令牌。请注意,这样刷新令牌就不会向客户机公开,用户将体验到与本机应用程序相同的身份验证。
- @jos&233;f.romaniello,当您讨论生成随机字符串并存储它时。你能详细解释一下吗?你的意思是你存储在移动设备上还是服务器上?如果他们获得了设备的名称,或者这个随机字符串(假设它在客户机上)不会让他们绕过任何被撤销的令牌,会发生什么?
- 另外,为了澄清……您所引用的刷新令牌……这是否意味着Web和移动设备都不会过期?
- @Jos&233;f.Romaniello在您的Web应用程序示例中,除了必须存储令牌外,其他一切都对我有意义。我认为JWT的优点是无状态认证——这意味着Web应用程序不必在签名时存储令牌。我认为服务器可以检查令牌的有效性,确保它在有效期内,然后发布一个更新的JWT令牌。你能详细说明一下吗?也许我还不太了解JWT。
- @乔斯&233;F.罗曼尼洛,我和洛坦有同样的问题。如果我们在数据库中存储令牌,它与传统的Web会话有何不同?
- @用户1870400很抱歉,我指的是浏览器中的客户端,比如本地存储或cookie。
- 两个问题/关注点:1-Web应用程序案例:为什么不能允许过期令牌刷新?假设我们设置短到期(1小时),并在令牌到期时对后端服务器进行续订调用,如您所说。2-在令牌中存储哈希(随机salt)密码是否存在安全问题?其想法是,如果存在,当请求更新时,后端服务器可以检查数据库中存储的密码,如果密码不匹配,则拒绝请求。这将涵盖移动/本地应用程序密码更改,允许将解决方案扩展到移动用例。
- @psaman您的评论意识到了被盗设备的问题,这与密码更改时的逻辑类似。我相信如果您已经在服务器端保留了刷新令牌,为了在需要刷新令牌时进行验证,不需要在令牌中包含哈希密码,因为您只需要在用户更改密码后删除所有以前的刷新令牌即可。此外,您还可以获得这样的好处:在后端系统中,用户可以基于刷新令牌检查所有打开的会话,并且可以使其失效。和Facebook一样,Gmail也是
- @le0diaz我应该澄清一下,我正试图找到一个解决方案,在这个解决方案中,我不在后端上持久保存刷新令牌,而仅仅依赖于客户端JWT,原因是我试图允许后端在不维护公共缓存(即完全分布式系统)的情况下水平扩展。
- 但是,如果有人拥有永不过期的刷新令牌呢?他会永远访问你的帐户吗?
- @诗篇一段时间后,你最后做了什么?因为同样的原因,我和你有同样的想法。通过在访问令牌中放置用户pw和作用域的哈希,可以在每次访问令牌过期时将此哈希与后端中的当前值进行比较。如果这些值没有更改,并且帐户没有被锁定(并且可能访问令牌不在黑名单中),则过期的访问令牌足以颁发新的访问令牌。这使得访问令牌中的到期时间成为一个"心跳",描述向后端数据库验证请求的频率。
- 对于Web应用,我不认为在令牌到期之前刷新它可以解决任何安全性问题,但会增加复杂性。与JWT令牌相比,它的工作方式与JWT令牌的工作方式相同。用一个不安全的令牌去获得另一个不安全的令牌,不要让我感觉到!虽然我使用auth0…
- @我仍然认为这是一个很好的解决办法。不过,我和那些不愿意在令牌中存储哈希密码的人谈过。我不知道为什么,因为拥有令牌意味着在其有效期内拥有密码(或者能够更改密码)。一个更安全的选择是在访问令牌的有效负载中有第二个随机生成的"密码"(类似于刷新令牌的想法),这就是在刷新时检查有效性的方法。例如,当用户密码更改或撤消访问时,您将更改此"密码"。
- @诗篇我同意。(在某些服务中,您不能只使用令牌来更改PW,但是PW更改要求用户再次输入PW。)我的意思是"salted and hashed PW"+scope+accountstatus的散列,只是为了避免在这些值更改时重新生成随机"刷新令牌"的错误。但是显式地重新生成随机刷新令牌是一个写类似的解决方案。
- @Jos&233;f.Romaniello您在Web应用程序示例中说,最好将令牌到期时间设置为一周-我根本不知道如何设置刷新令牌的到期时间-我认为它们将永远持续,除非它们被撤销。我有点困惑!谢谢!
- -1公开一个公开的API,盲目地重新签署任何令牌以延长其验证期是不好的。现在所有的令牌都有一个有效的无限期到期。签署令牌的行为应该包括对签署时在令牌中提出的每个声明进行适当的授权检查。
- 如何将令牌过期设置为一周,并在每次用户打开Web应用程序时以及每小时刷新一次令牌。使用节点?
- @psaman对于您,我认为您可以在需要生成任何恢复代码时执行我的操作:-在数据库中创建一个字符串字段;-生成一个uuidv4 uuid(随机);-从uuidv4中删除连字符("-");-base62较短的url/humanized代码的uuidv4;-bcrypt不带连字符的uuidv4的base62;-将bcrypt哈希存储在数据库中;。-将base62作为恢复电子邮件、刷新令牌等的代码发送;接收代码,使用bcrypt根据数据库中存储的哈希进行验证。自动,32个字符,URL友好,随机密码,用户不知道/carn更改。
- 是否可以用刷新的JWT更新iat("从epoch开始的秒数")值(您提到应该更新exp值,但iat值如何?)
- 根据我的经验,每当用户打开Web应用程序时刷新令牌是不好的做法。当使用应用程序的一个选项卡时,它是很好的,但是当您使用几个选项卡或几个浏览器时,它将是一个问题,并导致令牌冲突,并断开您使用的所有其他应用程序窗口。
- 每个人都说,在访问令牌使用刷新令牌过期后,应该获取新令牌。但是使用过期的令牌,我无法刷新令牌。是否确实可以使用过期令牌刷新令牌,或者我应该在令牌过期之前调用请求新令牌。
在您自己处理auth的情况下(即不要使用auth0这样的提供者),以下可能有效:
发行有效期相对较短的JWT代币,比如15分钟。
应用程序在任何需要令牌的事务(令牌包含到期日期)之前检查令牌到期日期。如果令牌已过期,那么它首先要求API"刷新"令牌(这是对UX透明的)。
API获取令牌刷新请求,但首先检查用户数据库,查看是否已针对该用户配置文件设置了"重新授权"标志(令牌可以包含用户ID)。如果标志存在,则拒绝令牌刷新,否则将发出新令牌。
重复。
例如,当用户重置了密码时,将设置数据库后端中的"重新授权"标志。当用户下次登录时,该标志将被删除。
另外,假设您有一个策略,即用户必须至少每72小时登录一次。在这种情况下,API令牌刷新逻辑还将检查用户数据库中的最后登录日期,并在此基础上拒绝/允许令牌刷新。
- 我觉得这不安全。如果我是一个攻击者,偷了您的令牌并将其发送到服务器,服务器将检查并看到该标志设置为真,这非常好,因为它会阻止刷新。我认为问题是,如果受害者更改了密码,标志将设置为false,现在攻击者可以使用原始令牌进行刷新。
- @用户2924127没有一个认证解决方案是完美的,并且总是存在权衡。如果攻击者能够"窃取您的令牌",那么您可能会有更大的问题需要担心。设置一个最大的令牌生存期将是上面的一个有用的调整。
- 您可以在令牌中包含for hash(bcrypt_password_hash),而不是在数据库中具有另一个字段reauth标志。然后在刷新令牌时,只需确认哈希(bcrypt_password_hash)是否等于令牌中的值。为了拒绝令牌刷新,必须更新密码哈希。
- @BAS,考虑到优化和性能,我认为密码哈希验证是多余的,并且具有更多的服务器含义。增加令牌的大小,以便签名确认/验证需要更多时间。服务器密码的其他哈希计算。使用额外的字段方法,您只需使用一个简单的布尔值在重新计算中进行验证。额外字段的db更新频率较低,但令牌刷新频率较高。您还可以为任何现有会话(移动、Web等)获得强制个人重新登录的可选服务。
- @le0diaz我没有建议密码散列,而是用一个快速散列函数散列一个字符串(可以修改,密码或任何应该触发注销的数据),比如md5。现在,做MD5(短_字符串)不是一个性能问题。如果你需要强制"个人重新登录",那么reauth在这里也不会有帮助。
- 我认为用户2924127的第一条评论实际上是错误的。更改密码后,帐户将被标记为需要重新验证,因此任何现有的过期令牌都将无效。
- 我喜欢这种方法,它是一种混合解决方案。
- @拉尔夫我认为相反,如果密码被更改,旧的过期令牌仍然有效。第一次命中需要重新验证,但随后连续的呼叫将再次通过。因此,现有的过期令牌仍然非常有效。就像上面提到的用户2924127。
- @如果你不想在哈希表中放入更多的触发器,这是很好的。可能是在1秒钟内将IP更改到世界的另一端时强制重新登录,或者类似的操作。然后,如果您更改计算哈希的算法,那么您的所有用户都需要在下一次调用时重新登录。一种解决方案是将散列存储在数据库中,并在每次需要更新访问令牌时再次检查散列。
- @用户2924127非常有效!不,只有当攻击者受到攻击时。如果您在多台设备上登录,最终用户的行为将非常奇怪。只有在密码更新后才能在第一台设备上重新登录…比如说,你在朋友的电脑上登录,或者在网吧、工作学校等的电脑上登录。你会期望密码更改来锁定那些电脑上的帐户。
- @这是真的。记住,每个令牌都有一个创建时间,因此一个解决方法就是简单地将密码重置时间与令牌创建时间进行比较——在最后一次密码重置之前创建的令牌不会被更新。
- @AndreasLundgren最终用户必须在每个设备上重新登录,除非通过使用当前密码的登录会话获取令牌。因此,密码更改将锁定所有计算机上的帐户。
- 所以每次你大概查询一些用户存储库/db来检查最后一次的密码更改…
另一种解决方案是在后端没有任何额外的安全存储的情况下使JWT无效,即在用户表上实现一个新的jwt_version整数列。如果用户希望注销或使现有令牌过期,则只需增加jwt_version字段。
在生成新的JWT时,将jwt_version编码到JWT有效载荷中,如果新的JWT应该替换所有其他的,可以选择提前增加值。
在验证JWT时,将jwt_version字段与user_id字段进行比较,并且仅在匹配时授予授权。
- 多个设备出现问题。基本上,如果您在一个设备上注销,它就会在任何地方注销。对吗?
- 嘿,根据您的需求,这可能不是一个"问题",但您是对的;这不支持每设备会话管理。
在将我们的应用程序转移到HTML5时,我在后端使用RESTfulAPI进行了一些修改。我提出的解决方案是:
成功登录后,向客户机颁发会话时间为30分钟(或任何通常的服务器端会话时间)的令牌。
创建客户端计时器,以便在令牌到期之前调用服务以续订令牌。新令牌将替换现有的将来调用。
如您所见,这减少了频繁的刷新令牌请求。如果用户在触发续订令牌调用之前关闭浏览器/应用程序,则以前的令牌将及时过期,用户必须重新登录。
可以实施更复杂的策略来满足用户不活动的需要(例如,忽略了打开的浏览器选项卡)。在这种情况下,续订令牌调用应包括预期的到期时间,该时间不应超过定义的会话时间。应用程序必须相应地跟踪最后一次用户交互。
我不喜欢设置长过期的想法,因此这种方法可能不能很好地用于需要较少频率的身份验证的本机应用程序。
- 如果计算机被挂起/休眠怎么办?计时器将一直计数到到期,但令牌实际上已经过期。在这种情况下计时器不工作
- @Alexparij你可以将其与固定时间进行比较,比如stackoverflow.com/a/35182296/1038456
- 允许客户端请求具有首选到期日期的新令牌对我来说有安全风险。
好问题——问题本身就包含了丰富的信息。
本文刷新令牌:何时使用它们以及它们如何与JWT交互为这个场景提供了一个好主意。有些要点是:
- 刷新令牌携带获取新访问所需的信息令牌。
- 刷新令牌也可以过期,但使用寿命相当长。
- 刷新令牌通常受到严格的存储要求确保没有泄漏。
- 它们也可以被授权服务器列入黑名单。
还可以看看Auth0/AngularJWT AngularJS
对于Web API。已阅读"使用ASP.NET Web API 2和OWIN在AngularJS应用程序中启用OAuth刷新令牌"
- 也许我读错了…但是标题以"刷新令牌…"开头的文章不包含任何关于刷新令牌的内容,除了您在这里提到的内容。
实际上,我在PHP中使用Guzzle客户机为API创建了一个客户机库,但是这个概念应该适用于其他平台。
基本上,我发行两个代币,一个短(5分钟)的,一个长的,一周后到期。如果客户端库接收到对某个请求的401响应,则使用中间件尝试对短令牌进行一次刷新。然后它将再次尝试原始请求,如果它能够刷新,将得到正确的响应,这对用户是透明的。如果失败,它将把401发送给用户。
如果短令牌已过期,但仍然是可信的,并且长令牌有效且可信,它将使用长令牌认证的服务上的特殊终结点刷新短令牌(这是它唯一可以使用的东西)。然后,它将使用短令牌获取新的长令牌,从而在每次刷新短令牌时将其延长一周。
这种方法还允许我们在最多5分钟内撤销访问,这对于我们的使用是可以接受的,而不必存储令牌的黑名单。
后期编辑:在我头脑中重新阅读了这几个月后,我应该指出,当刷新短令牌时,你可以取消访问,因为它提供了一个更昂贵的调用的机会(例如,调用数据库查看用户是否被禁止),而不必在每次调用你的服务时付费。
JWT自动刷新
如果您使用的是node(react/redux/universal js),则可以安装npm i -S jwt-autorefresh。
此库在访问令牌到期前(基于令牌中编码的exp声明)以用户计算的秒数调度JWT令牌的刷新。它有一个广泛的测试套件,并检查了相当多的条件,以确保任何奇怪的活动都伴随着有关您的环境配置错误的描述性消息。
全示例实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| 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一起工作?
- 是的,解码是客户端专用的解码,不应该知道这个秘密。机密用于在JWT令牌服务器端签名,以验证您的签名是否最初用于生成JWT,并且不应从客户端使用。JWT的神奇之处在于,它的有效负载可以在客户端进行解码,并且内部声明可以用于构建没有秘密的UI。jwt-autorefresh对其进行解码的唯一目的是提取exp声明,以便确定计划下一次刷新的距离。
- 哦,很高兴知道,有些事情没有意义,但现在确实如此。谢谢你的回答。
我通过在令牌数据中添加一个变量来解决这个问题:
1
| softexp - I set this to 5 mins (300 seconds) |
我将expiresIn选项设置为我想要的时间,然后用户将被迫再次登录。我的设定是30分钟。这必须大于softexp的值。
当我的客户端应用程序向服务器API发送请求(需要令牌的地方,如客户列表页)时,服务器根据其原始到期(expiresIn值)检查提交的令牌是否仍然有效。如果它无效,服务器将以特定于此错误的状态响应,例如INVALID_TOKEN。
如果基于expiredIn值的令牌仍然有效,但已经超过了softexp值,服务器将对此错误以单独的状态响应,例如EXPIRED_TOKEN:
1
| (Math.floor(Date.now() / 1000) > decoded.softexp) |
号
在客户端,如果它收到EXPIRED_TOKEN响应,它应该通过向服务器发送续订请求来自动续订令牌。这对用户是透明的,并自动处理客户端应用程序。
服务器中的续订方法必须检查令牌是否仍然有效:
1
| jwt.verify(token, secret, (err, decoded) => {}) |
如果上述方法失败,服务器将拒绝续订令牌。
这种方法怎么样:
- 对于每个客户机请求,服务器将令牌的过期时间与(currentTime-lastAccessTime)进行比较。
- 如果ExpirationTime<(CurrentTime-LastAccessedTime),它会将LastAccessedTime更改为CurrentTime。
- 如果浏览器处于非活动状态超过expirationtime或浏览器窗口关闭且expirationtime>(currentTime-lastaccessedtime),则服务器可以使令牌过期,并要求用户重新登录。
在这种情况下,刷新令牌不需要额外的端点。希望有任何反馈。
- 这是一个好的选择,在今天,它看起来很容易实现。
- 在这种情况下,您将上次访问的时间存储在哪里?您必须在后端和每个请求上执行此操作,因此它将成为一个不需要的状态解决方案。
以下是撤销JWT访问令牌的步骤:
1)登录时,发送2个令牌(访问令牌、刷新令牌)响应客户端。2)访问令牌的到期时间较短,刷新的到期时间较长。3)客户端(前端)将刷新令牌存储在本地存储器中,访问令牌存储在cookie中。4)客户端将使用访问令牌调用API。但到期后,从本地存储中选择刷新令牌并调用auth server api以获取新令牌。5)您的认证服务器将公开一个API,它将接受刷新令牌并检查其有效性,并返回一个新的访问令牌。6)一旦刷新令牌过期,用户将被注销。
请让我知道,如果你需要更多的细节,我可以共享代码(Java+Spring Bug)。