我希望对新的REST API实现基于jwt的身份验证。但是由于到期时间是在令牌中设置的,那么是否可以自动延长它呢?我不希望用户每隔X分钟就需要登录一次,如果他们在这段时间内积极使用应用程序的话。这将是一个巨大的用户体验失败。
但是延长过期时间会创建一个新的令牌(旧的令牌在过期前仍然有效)。在每个请求后生成一个新的令牌对我来说听起来很傻。当多个令牌同时有效时,听起来像是一个安全问题。当然,我可以使用黑名单使旧的使用无效,但我需要存储令牌。JWT的好处之一是没有存储空间。
我发现Auth0是如何解决这个问题的。他们不仅使用JWT令牌,还使用refresh令牌:
https://auth0.com/docs/tokens/refresh-tokens
但是,要实现这一点(没有Auth0),我需要存储刷新令牌并维护它们的过期。那么真正的好处是什么呢?为什么不只有一个令牌(不是JWT)并将过期时间保存在服务器上呢?
还有其他选择吗?使用JWT不适合这种情况吗?
如果您正在使用AWS Amplify & Cognito,这将为您带来魔力:
Use Auth.currentSession() to get the current valid token or get new if the current has expired. Amplify will handle it
As a fallback, use some interval job to refresh tokens on demand every x minutes, maybe 10 min. This is required when you have a long-running process like uploading a very large video which will take more than an hour (maybe due to a slow network) then your token will expire during the upload and amplify will not update automatically for you. In this case, this strategy will work. Keep updating your tokens at some interval.
How to refresh on demand is not mentioned in docs so here it is.
import { Auth } from 'aws-amplify';
try {
const cognitoUser = await Auth.currentAuthenticatedUser();
const currentSession = await Auth.currentSession();
cognitoUser.refreshSession(currentSession.refreshToken, (err, session) => {
console.log('session', err, session);
const { idToken, refreshToken, accessToken } = session;
// do whatever you want to do now :)
});
} catch (e) {
console.log('Unable to refresh Token', e);
}
来源:https://github.com/aws-amplify/amplify-js/issues/2560
以下是撤销JWT访问令牌的步骤:
1) When you do login, send 2 tokens (Access token, Refresh token) in response to client .
2) Access token will have less expiry time and Refresh will have long expiry time .
3) Client (Front end) will store refresh token in his local storage and access token in cookies.
4) Client will use access token for calling apis. But when it expires, pick the refresh token from local storage and call auth server api to get the new token.
5) Your auth server will have an api exposed which will accept refresh token and checks for its validity and return a new access token.
6) Once refresh token is expired, User will be logged out.
如果你需要更多细节,请告诉我,我也可以分享代码(Java + Spring引导)。
在你自己处理认证的情况下(即不使用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令牌刷新逻辑还将从用户数据库检查用户的最后登录日期,并在此基础上拒绝/允许令牌刷新。
我通过在令牌数据中添加一个变量来解决这个问题:
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) => {})
如果上述方法失败,服务器将拒绝更新令牌。
JWT的想法很好,你把你需要的东西放进JWT,然后无状态化。
两个问题:
糟糕的JWT标准化。
JWT是不可能失效的,如果创建快速到期,它会迫使用户频繁登录。
1的解。使用自定义JSON:
{"userId": "12345", "role": "regular_user"}
使用对称(AES)算法加密它(它比使用非对称算法签名更快),并将其放入快速过期的cookie中。我仍然会称它为JWT,因为它是JSON,并且在Web应用程序中用作令牌。现在,服务器检查cookie是否存在,其值是否可以解密。
2的解。使用刷新令牌:
将userId作为12345,对其加密,并将其放入长期过期的cookie中。不需要在DB中为刷新令牌创建一个特殊字段。
现在,每次访问令牌(JWT) cookie过期时,服务器都会检查刷新令牌cookie,解密,获取值,并在DB中查找用户。如果找到用户,则生成一个新的访问令牌,否则(或者如果刷新令牌也过期)强制用户登录。
最简单的替代方法是使用刷新令牌作为访问令牌,即根本不使用JWT。
使用JWT的优点是,在它的过期时间内,服务器不会访问DB。即使我们在cookie中放入一个过期时间只有2分钟的访问令牌,对于像eBay这样繁忙的应用程序,它也会导致每秒避免数千DB的点击。