更改密码后如何使令牌失效

Dan*_* R. 8 c# jwt asp.net-core-webapi asp.net-core-2.0

我正在使用JWT令牌身份验证的API。我已经在其背后创建了一些逻辑,以使用验证码等更改用户密码。

一切正常,密码更改。但这很重要:即使用户密码已更改,并且在身份验证时我得到了新的JWT令牌,旧令牌仍然有效。

关于密码更改后如何刷新/使令牌失效的任何提示?

编辑:因为我听说您实际上不能使JWT令牌无效,所以我对如何做到这一点有所了解。我的想法是创建一个具有“ accessCode”之类的新用户列,并将该访问代码存储在令牌中。每当我更改密码时,我也会更改accessCode(类似于6位数的随机数),并且在进行API调用时会执行对该accessCode的检查(如果令牌中使用的访问代码与db中的访问代码不匹配->返回未授权)。

你们认为这是个好方法还是还有其他方法吗?

jan*_*anv 10

最简单的方法是:使用用户当前的密码哈希对 JWT 进行签名,以保证每个已发布令牌的一次性使用。这是因为密码哈希在成功重置密码后总是会发生变化。

同一个令牌不可能两次通过验证。签名检查总是会失败。我们发行的 JWT 成为一次性令牌。

来源- https://www.jbspeakr.cc/howto-single-use-jwt/

  • 我不确定为什么用户会做这一切。我们还为 JWT 令牌设置了过期时间,对于密码恢复来说,它应该在 1 小时到 1 天之间。因此,即使用户设法恢复到 pass1,在超过 JWT 令牌到期时间后,它也会失效 (2认同)

Kon*_*rad 8

撤销/失效的最简单方法可能只是删除客户端上的令牌,并祈祷没人会劫持并滥用令牌。

您使用“ accessCode”列的方法可以工作,但我会担心性能。

另一种可能是更好的方法是将某些数据库中的令牌列入黑名单。我认为Redis将是最好的选择,因为它支持超时,EXPIRE因此您可以将其设置为与JWT令牌中相同的值。当令牌过期时,它将自动删除。

您将需要快速的响应时间,因为您将必须检查每个需要授权的请求上的令牌是否仍然有效(不在黑名单或其他accessCode中),这意味着在每个请求上使用无效的令牌来调用数据库。


刷新令牌不是解决方案

有人建议使用寿命长的刷新令牌和寿命短的访问令牌。您可以将访问令牌设置为在10分钟后失效,并且在更改密码后,该令牌仍将在10分钟内有效,但是它将过期,并且您将必须使用刷新令牌来获取新的访问令牌。我个人对此表示怀疑,因为刷新令牌也可以被劫持:http : //appetere.com/post/how-to-renew-access-tokens,然后您将需要一种使它们无效的方法因此,最后,您无法避免将它们存储在某个位置。


使用StackExchange.Redis的ASP.NET Core实现

You're using ASP.NET Core so you will need to find a way how to add custom JWT validation logic to check if the token was invalidated or not. This can be done by extending default JwtSecurityTokenHandler and you should be able to call Redis from there.

In ConfigureServices add:

services.AddSingleton<IConnectionMultiplexer>(ConnectionMultiplexer.Connect("yourConnectionString"));
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(opt =>
    {
        opt.SecurityTokenValidators.Clear();
        // or just pass connection multiplexer directly, it's a singleton anyway...
        opt.SecurityTokenValidators.Add(new RevokableJwtSecurityTokenHandler(services.BuildServiceProvider()));
    });
Run Code Online (Sandbox Code Playgroud)

Create your own exception:

public class SecurityTokenRevokedException : SecurityTokenException
{
    public SecurityTokenRevokedException()
    {
    }

    public SecurityTokenRevokedException(string message) : base(message)
    {
    }

    public SecurityTokenRevokedException(string message, Exception innerException) : base(message, innerException)
    {
    }
}
Run Code Online (Sandbox Code Playgroud)

Extend the default handler:

public class RevokableJwtSecurityTokenHandler : JwtSecurityTokenHandler
{
    private readonly IConnectionMultiplexer _redis;

    public RevokableJwtSecurityTokenHandler(IServiceProvider serviceProvider)
    {
        _redis = serviceProvider.GetRequiredService<IConnectionMultiplexer>();
    }

    public override ClaimsPrincipal ValidateToken(string token, TokenValidationParameters validationParameters,
        out SecurityToken validatedToken)
    {
        // make sure everything is valid first to avoid unnecessary calls to DB
        // if it's not valid base.ValidateToken will throw an exception, we don't need to handle it because it's handled here: https://github.com/aspnet/Security/blob/beaa2b443d46ef8adaf5c2a89eb475e1893037c2/src/Microsoft.AspNetCore.Authentication.JwtBearer/JwtBearerHandler.cs#L107-L128
        // we have to throw our own exception if the token is revoked, it will cause validation to fail
        var claimsPrincipal = base.ValidateToken(token, validationParameters, out validatedToken); 
        var claim = claimsPrincipal.FindFirst(JwtRegisteredClaimNames.Jti);
        if (claim != null && claim.ValueType == ClaimValueTypes.String)
        {
            var db = _redis.GetDatabase();
            if (db.KeyExists(claim.Value)) // it's blacklisted! throw the exception
            {
                // there's a bunch of built-in token validation codes: https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/blob/7692d12e49a947f68a44cd3abc040d0c241376e6/src/Microsoft.IdentityModel.Tokens/LogMessages.cs
                // but none of them is suitable for this
                throw LogHelper.LogExceptionMessage(new SecurityTokenRevokedException(LogHelper.FormatInvariant("The token has been revoked, securitytoken: '{0}'.", validatedToken)));
            }
        }

        return claimsPrincipal;
    }
}
Run Code Online (Sandbox Code Playgroud)

Then on your password change or whatever set the key with jti of the token to invalidate it.

Limitation!: all methods in JwtSecurityTokenHandler are synchronous, this is bad if you want to have some IO-bound calls and ideally, you would use await db.KeyExistsAsync(claim.Value) there. The issue for this is tracked here: https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/issues/468 unfortunately no updates for this since 2016 :(

It's funny because the function where token is validated is async: https://github.com/aspnet/Security/blob/beaa2b443d46ef8adaf5c2a89eb475e1893037c2/src/Microsoft.AspNetCore.Authentication.JwtBearer/JwtBearerHandler.cs#L107-L128

A temporary workaround would be to extend JwtBearerHandler and replace the implementation of HandleAuthenticateAsync with override without calling the base so it would call your async version of validate. And then use this logic to add it.

The most recommended and actively maintained Redis clients for C#:

Might help you to choose one: Difference between StackExchange.Redis and ServiceStack.Redis

StackExchange.Redis has no limitations and is under the MIT license.

So I would go with the StackExchange's one


Ted*_*pac 5

以下方法汇集了之前提出的每种方法的优点

  1. password_id在表中创建列user
  2. 创建.password_iduser
  3. password_id每次user更改其 时都分配一个新的 UUID password
  4. password_id使用相应的签署授权 JWT user
  5. 如果需要更高的性能,只需将password_id值存储在 Redis 中即可。

这种方法的优点:

  • 如果用户更改密码,截至该时刻存在的所有 JWT 将自动永久失效。
  • 用户是否将其密码更改为旧密码并不重要。
  • 没有必要将 JWT 存储在服务器端。
  • 无需在 JWT 负载中添加任何额外数据。
  • 使用Redis的实现非常简单。