如何在更改密码后使令牌无效

How to invalidate tokens after password change(如何在更改密码后使令牌无效)
本文介绍了如何在更改密码后使令牌无效的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在开发一个使用JWT令牌身份验证的API。我在其背后创建了一些逻辑来使用验证码&;更改用户密码。

一切正常,密码更改。但这里有个问题: 即使用户密码已更改并且我在身份验证时获得新的JWT令牌.旧令牌仍然有效。

有关如何在密码更改后刷新/使令牌无效的提示?

编辑:自从我听说您实际上不能使JWT令牌无效后,我对如何做有了一个想法。 我的想法是创建一个类似于"accessCode"的新用户列,并将该访问代码存储在令牌中。每当我更改密码时,我也会更改accessCode(类似于6位随机数),并在执行API调用时实现对该accessCode的检查(如果令牌中使用的访问码与db->返回的unsured中的不匹配)。

你们认为这是个好办法,还是有其他办法?

推荐答案

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

您使用"accessCode"列的方法可以工作,但我担心性能问题。

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

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


刷新令牌不是解决方案

有些人建议使用长期刷新令牌和短期访问令牌。您可以将访问令牌设置为(比方说)在10分钟后过期,当密码更改时,令牌仍将在10分钟内有效,但随后将过期,您将不得不使用刷新令牌来获取新的访问令牌。就我个人而言,我对此有点怀疑,因为刷新令牌也可能被劫持:http://appetere.com/post/how-to-renew-access-tokens,然后您也需要一种方法来使它们无效,所以最终,您无法避免将它们存储在某个地方。


使用StackExchange.Redis实现ASP.NET核心

您使用的是ASP.NET Core,因此需要找到一种方法来添加自定义JWT验证逻辑,以检查令牌是否无效。这可以由extending default JwtSecurityTokenHandler完成,您应该可以从那里调用Redis。

在ConfigureServices中添加:

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()));
    });

创建您自己的例外:

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

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

    public SecurityTokenRevokedException(string message, Exception innerException) : base(message, innerException)
    {
    }
}

扩展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;
    }
}

然后在更改密码时或其他任何情况下,使用令牌的JTI设置密钥以使其无效。

限制!:JwtSecurityTokenHandler中的所有方法都是同步的,如果您希望有一些IO绑定的调用,这是不好的,理想情况下,您应该在那里使用await db.KeyExistsAsync(claim.Value)。跟踪此问题的原因如下:https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/issues/468很遗憾,自2016年来没有此问题的更新:(

这很有趣,因为验证令牌的函数是异步的:https://github.com/aspnet/Security/blob/beaa2b443d46ef8adaf5c2a89eb475e1893037c2/src/Microsoft.AspNetCore.Authentication.JwtBearer/JwtBearerHandler.cs#L107-L128

临时解决方法是扩展JwtBearerHandler并将HandleAuthenticateAsync的实现替换为override,而不调用基础,这样它就会调用您的异步版本的验证。然后使用this logic添加。

最推荐和最积极维护的C#Redis客户端:

  • StackExchange.Redis (also used on stackoverflow)(Using StackExchange.Redis in a ASP.NET Core Controller)
  • ServiceStack.Redis(有限制的商业广告)

可能会帮助您选择一个:Difference between StackExchange.Redis and ServiceStack.Redis

StackExchange.Redis没有限制,受MIT许可。

所以我会选择StackExchange的

这篇关于如何在更改密码后使令牌无效的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持编程学习网!

本站部分内容来源互联网,如果有图片或者内容侵犯您的权益请联系我们删除!

相关文档推荐

DispatcherQueue null when trying to update Ui property in ViewModel(尝试更新ViewModel中的Ui属性时DispatcherQueue为空)
Drawing over all windows on multiple monitors(在多个监视器上绘制所有窗口)
Programmatically show the desktop(以编程方式显示桌面)
c# Generic Setlt;Tgt; implementation to access objects by type(按类型访问对象的C#泛型集实现)
InvalidOperationException When using Context Injection in ASP.Net Core(在ASP.NET核心中使用上下文注入时发生InvalidOperationException)
LINQ many-to-many relationship, how to write a correct WHERE clause?(LINQ多对多关系,如何写一个正确的WHERE子句?)