更改密码时如何使 OAuth 令牌无效?

Posted

技术标签:

【中文标题】更改密码时如何使 OAuth 令牌无效?【英文标题】:How to invalidate OAuth token when password is changed? 【发布时间】:2015-01-02 18:34:10 【问题描述】:

我们在带有SimpleAuthorizationServerProvider 的 Web Api 项目中使用 ASP.NET Identity,我们使用 OAuth-tokens 来授权来自客户端的每个请求。 (令牌具有和过期时间跨度,我们不使用刷新令牌。)

当用户更改密码时,我想使他们可能拥有的令牌无效,可能在其他设备上。有没有办法明确地做到这一点?我进行了实验,发现现有令牌在更改密码后可以正常工作,应该避免这种情况。

我考虑过将密码哈希或部分哈希放入 OAuth 令牌中作为声明,并在派生的 AuthorizeAttribute 过滤器的 OnAuthorization 方法中对其进行验证。 这是解决问题的正确方法吗?

【问题讨论】:

OAuth 的一部分不是独立于密码更改吗?为什么不在他们更改密码时从您的身份验证服务器中删除令牌,然后如果他们尝试在其他设备上使用它,它将无法工作。 “为什么不直接从身份验证服务器中删除令牌”:ASP.NET Identity 可以做到这一点吗?我认为令牌是自包含的,因此它们可以在任何 Web 服务器实例上使用,因此我们没有身份验证服务器。 如果这是真的,那么听起来我需要重新了解“ASP.NET 身份”已经有一段时间了。对不起。 【参考方案1】:

我的方法基于泰泽尔的建议。解决方案的要点如下。每次用户更改密码(以及注册时)时,都会生成一个新的 GUID,并将其保存在数据库的 User 表中。我将此 GUID 称为 密码戳,并将其存储在名为 LatestPasswordStamp 的属性中。

必须将此标记作为声明的一部分作为令牌的一部分发送给客户端。这可以通过OAuthAuthorizationServerProvider-implementation 的GrantResourceOwnerCredentials 方法中的以下代码来实现。

identity.AddClaim( new Claim( "PasswordTokenClaim", user.LatestPasswordStamp.ToString() ) );

这个标记将在每个请求中从客户端发送到服务器,并且验证该标记在数据库中没有被更改。如果是,则意味着用户更改了密码,可能来自另一台设备。验证是在我们的自定义授权过滤器中完成的。

public class AuthorizeAndCheckStampAttribute : AuthorizeAttribute

    public override void OnAuthorization( HttpActionContext actionContext )
    
        var claimsIdentity = actionContext.RequestContext.Principal.Identity as ClaimsIdentity;
        if( claimsIdentity == null )
        
            this.HandleUnauthorizedRequest( actionContext );
        

        // Check if the password has been changed. If it was, this token should be not accepted any more.
        // We generate a GUID stamp upon registration and every password change, and put it in every token issued.
        var passwordTokenClaim = claimsIdentity.Claims.FirstOrDefault( c => c.Type == "PasswordTokenClaim" );

        if( passwordTokenClaim == null )
        
            // There was no stamp in the token.
            this.HandleUnauthorizedRequest( actionContext );
        
        else
        
            MyContext ctx = (MyContext)System.Web.Mvc.DependencyResolver.Current.GetService( typeof( MyContext ) );

            var userName = claimsIdentity.Claims.First( c => c.Type == ClaimTypes.Name ).Value;

            if( ctx.Users.First( u => u.UserName == userName ).LatestPasswordStamp.ToString() != passwordTokenClaim.Value )
            
                // The stamp has been changed in the DB.
                this.HandleUnauthorizedRequest( actionContext );
            
        

        base.OnAuthorization( actionContext );
    

这样,如果客户端尝试使用在密码更改之前颁发的令牌授权自己,则会收到授权错误。

【讨论】:

不错的解决方案,OAuth 没有官方建议我们处理此类情况的方法吗?我的意思是,如果所有设备在更改密码后仍然可以访问,那么这似乎是整个身份验证方案中的一个严重漏洞。 是的,我认为这里的问题是 OAuth 本身对身份的来源一无所知,这是身份提供者的责任。例如这里的 ASP.NET Identity 和内置的 OAuth 处理程序是分开的,彼此不知道,这就是为什么我们必须手动让令牌处理程序知道密码已更改。 根据您的经验,我提出的以下解决方案是否可行? ***.com/questions/29534111/… 感谢您抽出宝贵时间发布适合您的解决方案的详细信息! 一个更简单的解决方案是根据密码上次更改日期(存储在数据库中)检查令牌发布日期。这样就不必生成、存储和传递 GUID。【参考方案2】:

我不建议将密码的哈希作为声明,并且我相信在更改密码时没有直接的方法使令牌无效。

但是,如果您可以通过从客户端应用程序发送到受保护的 API 端点的每个请求来访问数据库,那么您需要为授予资源所有者请求的每个令牌存储令牌标识符(可能是 Guid)。然后您将令牌标识符分配为此令牌的自定义声明,之后您需要通过查找令牌标识符和资源所有者的用户名来检查每个请求的此表。

更改密码后,您将删除此资源所有者(用户)的此令牌标识符记录,并且下次从客户端发送的令牌将被拒绝,因为此令牌标识符和资源所有者的记录已被删除。

【讨论】:

这很容易实现并且工作正常。谢谢! 很高兴它有帮助。如果你想知道为什么不分享你为实现它而创建的部分代码,我相信它对其他人有用:) @TaiseerJoudeh 在上面的问题中,假设场景是相同的,但是不是更改密码用户尝试登录三个不同的设备,我只希望该令牌应该在用户最近登录的用户和以前登录的用户上工作其他两个设备的两个令牌应该被撤销/删除。有什么办法可以做到吗? 该死的我不敢相信我正在读这个。 MS又做了一个粪

以上是关于更改密码时如何使 OAuth 令牌无效?的主要内容,如果未能解决你的问题,请参考以下文章

更改密码后 Parse 中的会话令牌无效

NestJS & Passport:更改用户密码时更改 JWT 令牌?

无法在 PHP 中刷新 OAuth2 令牌,授权无效

linux-修改密码提示passwd: 鉴定令牌操作错误

Laravel 更改密码重置特定令牌的令牌持续时间

Facebook Oauth 2.0 访问令牌会过期吗?