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

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

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

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

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


当前回答

我知道这是一个老问题,但我同时使用会话和令牌身份验证。我的应用程序是微服务的组合,所以我需要使用基于令牌的身份验证,这样每个微服务都不需要访问集中的数据库进行身份验证。我向我的用户发出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令牌要复杂得多。因此,如果你没有微服务,那么基于会话的认证可能是可行的方法。

其他回答

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的点击。

参考-刷新过期的JWT示例

另一种替代方法是,一旦JWT过期,用户/系统将调用 另一个url suppose /refreshtoken。与此请求一起通过的还有过期的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())

免责声明:我是维护者

services.Configure (Configuration.GetSection(“ApplicationSettings”));

        services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2); 

        services.AddDbContext<AuthenticationContext>(options =>
        options.UseSqlServer(Configuration.GetConnectionString("IdentityConnection")));

        services.AddDefaultIdentity<ApplicationUser>()
            .AddEntityFrameworkStores<AuthenticationContext>();

        services.Configure<IdentityOptions>(options =>
        {
            options.Password.RequireDigit = false;
            options.Password.RequireNonAlphanumeric = false;
            options.Password.RequireLowercase = false;
            options.Password.RequireUppercase = false;
            options.Password.RequiredLength = 4;
        }
        );

        services.AddCors();

        //Jwt Authentication

        var key = Encoding.UTF8.GetBytes(Configuration["ApplicationSettings:JWT_Secret"].ToString());

        services.AddAuthentication(x =>
        {
            x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
            x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
            x.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
        }).AddJwtBearer(x=> {
            x.RequireHttpsMetadata = false;
            x.SaveToken = false;
            x.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters
            {
                ValidateIssuerSigningKey = true,
                IssuerSigningKey = new SymmetricSecurityKey(key),
                ValidateIssuer = false,
                ValidateAudience = false,
                ClockSkew = TimeSpan.Zero
            };
        });
    }

在你自己处理认证的情况下(即不使用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令牌刷新逻辑还将从用户数据库检查用户的最后登录日期,并在此基础上拒绝/允许令牌刷新。