我希望对新的REST API实现基于jwt的身份验证。但是由于到期时间是在令牌中设置的,那么是否可以自动延长它呢?我不希望用户每隔X分钟就需要登录一次,如果他们在这段时间内积极使用应用程序的话。这将是一个巨大的用户体验失败。
但是延长过期时间会创建一个新的令牌(旧的令牌在过期前仍然有效)。在每个请求后生成一个新的令牌对我来说听起来很傻。当多个令牌同时有效时,听起来像是一个安全问题。当然,我可以使用黑名单使旧的使用无效,但我需要存储令牌。JWT的好处之一是没有存储空间。
我发现Auth0是如何解决这个问题的。他们不仅使用JWT令牌,还使用refresh令牌:
https://auth0.com/docs/tokens/refresh-tokens
但是,要实现这一点(没有Auth0),我需要存储刷新令牌并维护它们的过期。那么真正的好处是什么呢?为什么不只有一个令牌(不是JWT)并将过期时间保存在服务器上呢?
还有其他选择吗?使用JWT不适合这种情况吗?
jwt-autorefresh
如果你正在使用node (React / Redux / Universal JS),你可以安装npm i -S jwt-autorefresh。
这个库计划在访问令牌到期之前的用户计算的秒数刷新JWT令牌(基于令牌中编码的exp声明)。它有一个广泛的测试套件,并检查相当多的条件,以确保任何奇怪的活动都伴随着关于来自您的环境的错误配置的描述性消息。
完整的示例实现
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-autorefresh
如果你正在使用node (React / Redux / Universal JS),你可以安装npm i -S jwt-autorefresh。
这个库计划在访问令牌到期之前的用户计算的秒数刷新JWT令牌(基于令牌中编码的exp声明)。它有一个广泛的测试套件,并检查相当多的条件,以确保任何奇怪的活动都伴随着关于来自您的环境的错误配置的描述性消息。
完整的示例实现
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())
免责声明:我是维护者
当我在后端将我们的应用程序移动到带有RESTful api的HTML5时,我正在进行修补。我想到的解决办法是:
成功登录后,客户端将获得一个会话时间为30分钟(或通常的服务器端会话时间)的令牌。
创建一个客户端计时器来调用服务,以便在令牌到期之前更新令牌。新的令牌将在未来的调用中取代现有的令牌。
如您所见,这减少了频繁的刷新令牌请求。如果用户在更新令牌调用触发之前关闭浏览器/应用程序,之前的令牌将及时过期,用户将不得不重新登录。
可以采用更复杂的策略来应对用户不活跃(例如忽略打开的浏览器选项卡)。在这种情况下,更新令牌调用应该包括预期的到期时间,该时间不应该超过所定义的会话时间。应用程序必须相应地跟踪最后一次用户交互。
我不喜欢设置较长的过期时间,因此这种方法可能不适用于需要较少频繁身份验证的本机应用程序。
我通过在令牌数据中添加一个变量来解决这个问题:
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) => {})
如果上述方法失败,服务器将拒绝更新令牌。