在ASP.NET Core 3.1中使用Identity和ExternalLogin进行基于角色的授权。

问题描述 投票:1回答:1

我是.NET Core的新手,我试图在一个.NET Core 3.1项目中设置基于角色的授权。我相信我点击了每一个在线谈论它的教程和线程。我的问题是,在教程上似乎很容易实现,但对我来说却无法实现。根据我找到的教程,我所要做的就是给数据库中的用户分配一个角色,然后使用 [Authorize(Roles="roleName")] 前的Controller's Action。当我这样做时,我总是得到一个403错误,因为用户有指定的角色。当我使用 userManager.GetRolesAsync(user)我看到该用户拥有该角色。当我用[Authorize]向这个动作发出请求时,当用户登录时,它就会像预期的那样工作。

我在调试模式下检查了当前用户的ClaimsPrincipal.Identity,结果发现 RoleClaimType = "role". 我检查了当前用户的权限,发现它没有一个类型为 "角色 "的权限。是这样的吗?[Authorize(Roles="...")] 工作?它看起来像一个索赔吗?如果是这样,我如何为用户的角色申请?在这个应用程序中,用户登录的唯一方法是用谷歌帐户。那么,如果他们是由谷歌登录管理,我应该如何添加一个索赔?

下面是我在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");
            }
    });
}

下面是一个Controller的Action例子

[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错误。

我到底做错了什么?我觉得我好像少了一行或者类似的东西,因为在我找到的教程中,这一切看起来都很简单。

c# asp.net identityserver4 asp.net-core-3.1 role-based-access-control
1个回答
2
投票

你的假设是正确的,当你指定了 [Authorize(Roles = "<role>")] 属性,ASP将创建一个 RolesAuthorizationRequirement 幕后。

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

在您的情况下,调用的是 this.HttpContext.User.IsInRole("Admin")

该方法 User.IsInRole 将调查一项名为 "http://schemas.microsoft.com/ws/2008/06/identity/claims/role" 并将其值与 "Admin "进行比较

ASP授权管道并没有连接到你的UserManager逻辑,基本的API只会观察和验证JWT令牌的要求。

你可能应该创建自己的AuthorizationHandler来检查用户是否真的是Admin。

或者使用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()
{
   ...
}

0
投票

我终于找到了一个可行的解决方案。我试着用RequireAssertion来改编@MichaelShterenberg的代码,但我不能让它工作,因为我必须查询我的数据库,而且我不能用这个解决方案来使用UserManager。最后,我根据他的答案中的这部分找到了一个解决方案。

你可能应该创建自己的AuthorizationHandler,检查用户是否真的是Admin。

我按照这个帖子的答案。Dependency Injection on AuthorizationOptions Requirement in DotNet Core

© www.soinside.com 2019 - 2024. All rights reserved.