【问题标题】:Overriding OnTokenValidated JwtBearerEvents with Custom function .NET Core 2使用自定义函数 .NET Core 2 覆盖 OnTokenValidated JwtBearerEvents
【发布时间】:2018-11-18 06:27:26
【问题描述】:

在我的 API 项目中,我使用 JwtBearer 处理身份验证(用户使用 Azure 登录)。调用 API 时,将使用定义的 Azure 实例验证令牌,这一切正常。

当一个令牌被成功验证时,登录的用户将被插入到我们自己的数据库中,并具有适当的角色。现在的处理方式如下:

// Add authentication (Azure AD)
services
    .AddAuthentication(sharedOptions =>
    {
        sharedOptions.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
        sharedOptions.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; 
        sharedOptions.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; 
    })
    .AddJwtBearer(options =>
    {
        options.Audience = this.Configuration["AzureAd:ClientId"];
        options.Authority = $"{this.Configuration["AzureAd:Instance"]}{this.Configuration["AzureAd:TenantId"]}";

        options.Events = new JwtBearerEvents()
        {
            OnTokenValidated = context =>
            {
                // Check if the user has an OID claim
                if (!context.Principal.HasClaim(c => c.Type == "http://schemas.microsoft.com/identity/claims/objectidentifier"))
                {
                    context.Fail($"The claim 'oid' is not present in the token.");
                }

                ClaimsPrincipal userPrincipal = context.Principal;
                
                // Check is user exists, if not then insert the user in our own database
                CheckUser cu = new CheckUser(
                    context.HttpContext.RequestServices.GetRequiredService<DBContext>(),
                    context.HttpContext.RequestServices.GetRequiredService<UserManager<ApplicationUser>>(),
                    userPrincipal);

                cu.CreateUser();

                return Task.CompletedTask;
            },
        };
    });

这工作正常,但它不是最漂亮/正确的方法。我会说我应该使用依赖注入/覆盖OnTokenValidated 事件并在那里集成'CheckUser' 逻辑,这样startup 类就会保持整洁。

遗憾的是,我缺乏关于 DI 的知识,我不完全确定正确处理此问题的最佳方法是什么。因此,我环顾四周,找到了一个准确描述我的问题的帖子:

Problems handling OnTokenValidated with a delegate assigned in startup.cs

读完这篇文章后,我尝试用自己的逻辑对其进行一些修改,结果如下:

在启动中:

services.AddScoped<UserValidation>();

services
    .AddAuthentication(sharedOptions =>
    {
        sharedOptions.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
        sharedOptions.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
        sharedOptions.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; 
    })
    .AddJwtBearer(options =>
    {
        options.Audience = this.Configuration["AzureAd:ClientId"];
        options.Authority = $"{this.Configuration["AzureAd:Instance"]}{this.Configuration["AzureAd:TenantId"]}";

        options.EventsType = typeof(UserValidation);
    });

自定义 JwtBearerEvents 类:

public class UserValidation : JwtBearerEvents
{
    private string UserID { get; set; }

    private string UserEmail { get; set; }

    private string UserName { get; set; }

    public override async Task TokenValidated(TokenValidatedContext context)
    {
        try
        {
            TRSContext context2 = context.HttpContext.RequestServices.GetRequiredService<TRSContext>();
            UserManager<ApplicationUser> userManager = context.HttpContext.RequestServices.GetRequiredService<UserManager<ApplicationUser>>();

            ClaimsPrincipal userPrincipal = context.Principal;

            this.UserID = userPrincipal.Claims.First(c => c.Type == "http://schemas.microsoft.com/identity/claims/objectidentifier").Value;

            if (userPrincipal.HasClaim(c => c.Type == "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress"))
            {
                this.UserEmail = userPrincipal.Claims.First(c => c.Type == "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress").Value;
            }

            if (userPrincipal.HasClaim(c => c.Type == "name"))
            {
                this.UserName = userPrincipal.Claims.First(c => c.Type == "name").Value;
            }

            var checkUser = userManager.FindByIdAsync(this.UserID).Result;
            if (checkUser == null)
            {
                checkUser = new ApplicationUser
                {
                    Id = this.UserID,
                    Email = this.UserEmail,
                    UserName = this.UserEmail,
                };

                var result = userManager.CreateAsync(checkUser).Result;

                // Assign Roles
                if (result.Succeeded)
                {
                    return;  
                }
                else
                {
                    throw new Exception(result.Errors.First().Description);
                }
            }
        }
        catch (Exception)
        {
            throw;
        }
    }
}

但是由于某种原因,这不起作用。没有错误,UserValidation 从未被命中(尝试设置调试点但从未命中)并且它不会插入新用户(在使用旧代码时会这样做)。

任何人都知道我在这里做错了什么,或者可能有一些更好的想法来处理这个问题?

【问题讨论】:

  • 您是否遇到任何错误?这到底是什么问题?
  • 我的错,忘了把它放在主帖中。我没有收到任何错误,自定义 JWTBearerEvents 类从未被命中(试图在它的开头设置一个调试点,但它从未命中)。我使用我的帐户(数据库中不存在)使用 Azure 登录,因此它应该插入我,但没有任何反应。我已经编辑了我遇到的问题的主要帖子。
  • 我注意到您没有在等待 TokenValidated() 方法中的任何内容,而是将其标记为 async
  • ... 愚蠢的我。不小心把异步留在了我的任务中..在删除它并给它一个适当的回报后它可以工作..非常感谢!
  • 只是一个简短的旁注:不要从方法中删除 async 关键字,而是从实现中删除各种 .Result 调用,而是等待那些。否则您的代码可能会遭遇意外死锁。

标签: asp.net-core jwt bearer-token


【解决方案1】:

我建议您在启动时进行基本的令牌验证(如权威和受众),如您所展示的。我建议您使用基于策略的验证来进行特定的索赔验证。见Policy-based authorization in ASP.NET Core

结果将是更简单、更易于维护的代码。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-11-03
    • 1970-01-01
    • 1970-01-01
    • 2018-06-25
    • 2011-09-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多