【问题标题】:.NET Core API JwtBearerEvents.TokenValidated - race condition between multiple API requests.NET Core API JwtBearerEvents.TokenValidated - 多个 API 请求之间的竞争条件
【发布时间】:2020-08-14 00:01:31
【问题描述】:

我将覆盖 JwtBearerEvents.TokenValidated 以便将我的数据库中的用户与经过身份验证的用户(来自第 3 方身份验证服务 - 通过 JWT)进行匹配,然后填充一些自定义声明供我在我的 API 中使用(用户例如订阅级别)。

此代码从Startup.cs 触发,并为每个 API 请求调用。在TokenValidated 中,我尝试加载与令牌关联的用户,如果它们不存在,我会在数据库中创建一个用户。

但是,我的场景是客户端将对我的 API 发出多个请求,如果这是一个尚不存在的用户,API 会创建多个用户 - 每次并行调用 TokenValidated 时都会创建一个用户。

代码:

public class CustomJwtBearerEvents : JwtBearerEvents
{
    private IUsersRepository _usersRepository;

    public CustomJwtBearerEvents(IUsersRepository usersRepository)
    {
        this._usersRepository = usersRepository;
    }

    public override async Task TokenValidated(TokenValidatedContext context)
    {
        var subject = context.Principal.FindFirstValue("user_id");

        var dbUser = await _usersRepository.GetOrCreateUser(subject, context.Principal);

        // Populate custom claims
    }
}

我确定我在处理这个错误,但我不确定如何最好地解决这个问题 - 所以这是我的问题:

我应该如何将第 3 方身份验证用户与我的数据库中的用户进行匹配,同时允许并发 API 请求?

【问题讨论】:

    标签: .net .net-core oauth-2.0 jwt


    【解决方案1】:

    有几种方法可以解决这个问题。最直接的可能是在调用_usersRepository.GetOrCreateUser(subject, context.Principal); 的代码部分周围实现lock,这将确保一次只有一个线程可以访问该行代码,防止任何脏读和重复插入。

    该代码看起来像这样

    public class CustomJwtBearerEvents : JwtBearerEvents
    {
        private readonly object _key = new object();
        private IUsersRepository _usersRepository;
    
        public CustomJwtBearerEvents(IUsersRepository usersRepository)
        {
            this._usersRepository = usersRepository;
        }
    
        public override async Task TokenValidated(TokenValidatedContext context)
        {
            var subject = context.Principal.FindFirstValue("user_id");
    
            lock(_key)
            {
                var dbUser = await _usersRepository.GetOrCreateUser(subject, context.Principal);
            }
    
            // Populate custom claims
        }
    }
    

    正如您可能猜到的那样,这不会是最高性能的,实际上是让您的每个 API 调用等待所有其他调用完成。

    为了稍微优化一下,您可以考虑在您的IUsersRepository 中添加一个GetUser() 方法,该方法可以在代码的locked 部分之外使用。

    这将允许对已经存在的用户执行任何请求,而无需等待,并且仅在必须创建新用户时才锁定代码。

    public class CustomJwtBearerEvents : JwtBearerEvents
    {
        private readonly object _key = new object();
        private IUsersRepository _usersRepository;
    
        public CustomJwtBearerEvents(IUsersRepository usersRepository)
        {
            this._usersRepository = usersRepository;
        }
    
        public override async Task TokenValidated(TokenValidatedContext context)
        {
            var subject = context.Principal.FindFirstValue("user_id");
    
            var dbUser = await _usersRepository.GetUser(subject, context.Principal);
    
            if (dbUser is null) 
            {
                lock(_key)
                {
                    dbUser = await _usersRepository.GetOrCreateUser(subject, context.Principal);
                }
            }
    
            // Populate custom claims
        }
    }
    

    话虽如此,Microsoft.AspNet.Identity 框架在管理外部用户帐户方面做得非常好,如果您有兴趣查看的话。

    Here is a link to their docs on the subject.

    【讨论】:

      猜你喜欢
      • 2020-06-02
      • 2020-10-16
      • 2022-01-10
      • 2022-11-18
      • 1970-01-01
      • 2018-09-03
      • 2016-11-16
      • 1970-01-01
      • 2020-11-26
      相关资源
      最近更新 更多