ASP.NET Core 3.1 中基于角色的授权,带有 Identity 和 ExternalLogin

Posted

技术标签:

【中文标题】ASP.NET Core 3.1 中基于角色的授权,带有 Identity 和 ExternalLogin【英文标题】:Role based authorization in ASP.NET Core 3.1 with Identity and ExternalLogin 【发布时间】:2020-09-24 18:09:30 【问题描述】:

我是 .NET Core 的新手,我正在尝试在 .NET Core 3.1 项目中设置基于角色的授权。我相信我点击了每个在线讨论它的教程和线程。我的问题是它似乎很容易在教程上工作,但它对我不起作用。根据我找到的教程,我所要做的就是为数据库中的用户分配一个角色,然后在控制器的操作之前使用[Authorize(Roles="roleName")]。当我这样做时,对于具有指定角色的用户,我总是会收到 403 错误。当我使用userManager.GetRolesAsync(user) 时,我看到用户具有角色。当我使用 [Authorize] 向此操作发出请求时,它会在用户登录时按预期工作。

我在调试模式下检查了当前用户的 ClaimsPrincipal.Identity,我发现 RoleClaimType = "role"。我检查了当前用户的声明,发现它没有“角色”类型的声明。 [Authorize(Roles="...")] 是这样工作的吗?它看起来像索赔吗?如果是这样,我如何获得用户角色的声明?用户登录此应用程序的唯一方法是使用 Google 帐户。那么,如果它们由 Google 登录管理,我应该如何添加声明呢?

这是我在 Startup.cs 中的代码

public void ConfigureServices(IServiceCollection services)

    services.AddDbContext<ApplicationDbContext>(options =>
        options.UseNpgsql(Configuration.GetConnectionString("DefaultConnection")));

    services.AddDefaultIdentity<ApplicationUser>()
        .AddRoles<ApplicationRole>()
        .AddEntityFrameworkStores<ApplicationDbContext>();

    services.AddIdentityServer()
        .AddApiAuthorization<ApplicationUser, ApplicationDbContext>();

    services.AddAuthentication()
        .AddGoogle(options =>
        
            IConfigurationSection googleAuthNSection =
            Configuration.GetSection("Authentication:Google");

            options.ClientId = googleAuthNSection["ClientId"];
            options.ClientSecret = googleAuthNSection["ClientSecret"];
        )
        .AddIdentityServerJwt();

    services.AddControllersWithViews();
    services.AddRazorPages();
    services.AddSpaStaticFiles(configuration =>
    
        configuration.RootPath = "ClientApp/dist";
    );


public void Configure(IApplicationBuilder app, IWebHostEnvironment env)

    if (env.IsDevelopment())
    
        app.UseDeveloperExceptionPage();
        app.UseDatabaseErrorPage();
    
    else
    
        app.UseExceptionHandler("/Error");
        app.UseHsts();
    

    app.UseHttpsRedirection();
    app.UseStaticFiles();
    if (!env.IsDevelopment())
    
        app.UseSpaStaticFiles();
    
    app.UseRouting();
    app.UseIdentityServer();
    app.UseAuthorization();
    app.UseEndpoints(endpoints =>
    
        endpoints.MapControllerRoute(
            name: "default",
            pattern: "controller/action=Index/id?");
        endpoints.MapRazorPages();
    );

    app.UseSpa(spa =>
    
        spa.Options.SourcePath = "ClientApp";

            if (env.IsDevelopment())
            
                spa.UseAngularCliServer(npmScript: "start");
            
    );

这是一个控制器动作的例子

[Authorize(Roles = "Admin")]
[HttpGet("userinformations")]
public async Task<UserInformations> GetCurrentUserInformations()

    string strUserId = this.User.FindFirstValue(ClaimTypes.NameIdentifier);

    ApplicationUser user = await userManager.FindByIdAsync(strUserId);

    string[] roles = (await userManager.GetRolesAsync(user)).ToArray();

    UserInformations userInfo = new UserInformations()
    
        UserName = user.UserName,
        FirstName = user.FirstName,
        LastName = user.LastName,
        Email = user.Email,
        Organization = user.idDefaultOrganisation.HasValue ? user.DefaultOrganization.OrganizationName : "",
        Claims = this.User.Claims.Select(c => $"c.Type : c.Value").ToArray(),
        Roles = roles
    ;

    return userInfo;

当我在没有 [Authorize(Roles = "Admin")] 的情况下向此 Action 发出请求时,我可以看到当前用户具有角色 Admin,但是当我添加它时,我收到 403 错误。

我做错了什么?我觉得我在某处遗漏了一行或类似的东西,因为在我找到的教程中这一切似乎都很简单。

【问题讨论】:

【参考方案1】:

我终于找到了一个可行的解决方案。 我尝试使用 RequireAssertion 调整 @MichaelShterenberg 的代码,但我无法让它工作,因为我必须查询我的数据库并且我无法将 UserManager 与此解决方案一起使用。 我最终根据他的这部分答案找到了解决方案:

您可能应该创建自己的 AuthorizationHandler 来检查用户是否确实是管理员

我关注了这个帖子的答案:Dependency Injection on AuthorizationOptions Requirement in DotNet Core

【讨论】:

【参考方案2】:

您的假设是正确的,当您指定[Authorize(Roles = "&lt;role&gt;")] 属性时,ASP 将在后台创建一个RolesAuthorizationRequirement

然后授权处理程序将调用this.HttpContext.User.IsInRole(&lt;role&gt;) 来评估策略。

在你的情况下,电话是this.HttpContext.User.IsInRole("Admin")

User.IsInRole 方法将查看名为 "http://schemas.microsoft.com/ws/2008/06/identity/claims/role" 的声明并将其值与“Admin”进行比较

ASP 授权管道未与您的 UserManager 逻辑挂钩,基本 API 只会观察和验证 JWT 令牌声明。

您可能应该创建自己的 AuthorizationHandler 来检查用户是否确实是管理员

或者使用 RequireAssertion 的不太正式的方式:

services.AddAuthorization(options => options.AddPolicy("Admininstrators", builder =>

    builder.RequireAssertion(async context =>
    
        string strUserId = context.User.FindFirstValue(ClaimTypes.NameIdentifier);
        var user = await userManager.FindByIdAsync(strUserId);
        string[] roles = (await userManager.GetRolesAsync(user)).ToArray();
        return roles.Contains("Admin");
    ;
);

[Authorize("Admininstrators")]
[HttpGet("userinformations")]
public async Task<UserInformations> GetCurrentUserInformations()

   ...

【讨论】:

首先感谢您的回答。既然我知道它使用了该声明,那么我如何根据我的数据库的内容来获得声明? 您有几种方法可以做到这一点:最标准的是创建自己的 AuthorizaionRequirement 和 AuthorizarionHandler 并实现 HandleRequirementAsync 方法。您可以查看RolesAuthorizationRequirement 是如何实现的github.com/dotnet/aspnetcore/blob/… 然后您定义自己的策略并添加此要求查看本指南geeklearning.io/… 使用更快的RequireAssertion 解决方案在我的答案中添加了一个代码示例 谢谢,如果可行,我会尝试并标记为答案,但是您是否认为有一种方法可以在用户登录时添加“角色”声明,我可以使用@ 987654333@。这样,每当我的数据库中有新角色时,我就不需要制定新策略。就像在身份验证期间一样,在数据库中检查用户的角色以将它们添加到声明中 虽然我确信有办法做到这一点,但我建议不要这样做。 1. 您最终在身份验证时调用用户管理器,即使您可能不需要(因为在身份验证期间您不知道是否触发了管理控制器) 2. 为每个逻辑角色定义策略并不是一个坏主意。政策往往会随着时间而改变,例如名为“Admin”的策略可能有更多条件(例如,您可以决定 jon.doe@testuser.com 自动授予管理员权限)。通过使用相同的策略名称,任何更改都将自动应用于您的所有端点/控制器

以上是关于ASP.NET Core 3.1 中基于角色的授权,带有 Identity 和 ExternalLogin的主要内容,如果未能解决你的问题,请参考以下文章

Asp.Net Core Web API 5.0 和 Angular 中基于自定义角色的授权

使用 Azure AD 的 Asp.net core mvc 基于角色的授权

Asp.Net Core 中IdentityServer4 实战之角色授权详解

当基于角色的授权失败时,asp.net core 2.0 应用程序崩溃

IdentityServer4 基于角色的 Web API 授权与 ASP.NET Core 身份

在 ASP.NET Core 中的每个操作之前查询数据库以获得角色授权