我希望对新的REST API实现基于jwt的身份验证。但是由于到期时间是在令牌中设置的,那么是否可以自动延长它呢?我不希望用户每隔X分钟就需要登录一次,如果他们在这段时间内积极使用应用程序的话。这将是一个巨大的用户体验失败。

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

我发现Auth0是如何解决这个问题的。他们不仅使用JWT令牌,还使用refresh令牌: https://auth0.com/docs/tokens/refresh-tokens

但是,要实现这一点(没有Auth0),我需要存储刷新令牌并维护它们的过期。那么真正的好处是什么呢?为什么不只有一个令牌(不是JWT)并将过期时间保存在服务器上呢?

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


当前回答

今天,许多人选择使用jwt进行会话管理,却没有意识到他们为了简单而放弃了什么。我的回答详细阐述了问题的第二部分:

那么真正的好处是什么呢?为什么不只有一个令牌(不是JWT)并将过期时间保存在服务器上呢? 还有其他选择吗?使用JWT不适合这种情况吗?

jwt能够支持基本的会话管理,但有一些限制。由于是自描述令牌,它们在服务器端不需要任何状态。这使得他们很有吸引力。例如,如果服务没有持久层,它就不需要仅仅为了会话管理而引入持久层。

然而,无国籍也是他们缺点的主要原因。由于它们只发布一次,内容固定且到期,因此您无法使用典型的会话管理设置完成您想做的事情。

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

我写了一篇文章详细解释了这些缺点。需要明确的是,您可以通过添加更复杂的内容(滑动会话、刷新令牌等)来解决这些问题。

至于其他选项,如果您的客户端仅通过浏览器与您的服务交互,我强烈建议使用基于cookie的会话管理解决方案。我还列出了目前在web上广泛使用的认证方法。

其他回答

我知道这是一个老问题,但我同时使用会话和令牌身份验证。我的应用程序是微服务的组合,所以我需要使用基于令牌的身份验证,这样每个微服务都不需要访问集中的数据库进行身份验证。我向我的用户发出2个jwt(由不同的秘密签名):

A standard JWT, used to authenticate requests. This token expires after 15 minutes. A JWT that acts as a refresh token that is placed in a secure cookie. Only one endpoint (actually it is its own microservice) accepts this token, and it is the JWT refresh endpoint. It must be accompanied by a CSRF token in the post body to prevent CRSF on that endpoint. The JWT refresh endpoint stores a session in the database (the id of the session and the user are encoded into the refresh JWT). This allows the user, or an admin, to invalidate a refresh token as the token must both validate and match the session for that user.

这工作得很好,但比使用基于会话的认证和cookie和CSRF令牌要复杂得多。因此,如果你没有微服务,那么基于会话的认证可能是可行的方法。

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

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

Web应用程序

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

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

要刷新令牌,您的API需要一个新的端点,该端点接收有效的、未过期的JWT,并返回带有新过期字段的签名JWT。然后web应用程序将把令牌存储在某个地方。

移动/本机应用程序

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

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

永不过期的令牌的问题在于,它永远不会意味着永远不会。如果你丢了手机怎么办?因此,它需要被用户以某种方式识别,应用程序需要提供一种撤销访问的方法。我们决定使用设备的名称,例如。玛丽:iPad。然后用户可以进入应用程序,撤销对“maryo的iPad”的访问权限。

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

我们认为JWT对这些用例没有用处,所以我们使用一个随机生成的字符串,并将其存储在我们这边。

另一种使jwt失效的解决方案是在用户表上实现一个新的jwt_version整数列,而不需要在后端进行任何额外的安全存储。如果用户希望注销或过期现有令牌,他们只需增加jwt_version字段。

在生成一个新的JWT时,将jwt_version编码到JWT有效负载中,如果新的JWT应该替换所有其他JWT,则可以选择提前增加该值。

在验证JWT时,jwt_version字段将与user_id进行比较,只有匹配时才授予授权。

在你自己处理认证的情况下(即不使用Auth0这样的提供者),以下方法可能有效:

Issue JWT token with relatively short expiry, say 15min. Application checks token expiry date before any transaction requiring a token (token contains expiry date). If token has expired, then it first asks API to 'refresh' the token (this is done transparently to the UX). API gets token refresh request, but first checks user database to see if a 'reauth' flag has been set against that user profile (token can contain user id). If the flag is present, then the token refresh is denied, otherwise a new token is issued. Repeat.

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

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

这个方法怎么样:

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

在这种情况下,刷新令牌不需要额外的端点。 将感激任何反馈。