Авторизация на основе ролей в ASP.NET Core 3.1 с удостоверением и ExternalLogin

Я новичок в .NET Core, и я пытаюсь настроить авторизацию на основе ролей в проекте .NET Core 3.1. Я считаю, что щелкал по всем обучающим материалам и темам, рассказывающим об этом в Интернете. Моя проблема в том, что, похоже, он очень легко работает с учебниками, но у меня это не работает. Согласно найденным мною руководствам, все, что мне нужно было сделать, это назначить роль пользователю в базе данных, а затем использовать [Authorize(Roles="roleName")] перед действием контроллера. Когда я это делаю, я всегда получаю ошибку 403 для пользователя с указанной ролью. Когда я использую userManager.GetRolesAsync(user), я вижу, что у пользователя есть роль. Когда я делаю запрос на это действие с помощью [Авторизовать], оно работает, когда пользователь вошел в систему, как и ожидалось.

Я проверил в режиме отладки 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")], я вижу, что текущий пользователь имеет роль Admin, но когда я добавляю его, я получаю ошибку 403.

Что я делаю неправильно? Мне кажется, что я где-то упускаю одну строчку или что-то в этом роде, потому что в найденных мною уроках все кажется таким простым.


person Rémy Huot    schedule 05.06.2020    source источник


Ответы (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, который проверяет, действительно ли пользователь является администратором.

Или менее формальный способ использования 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()
{
   ...
}
person Michael Shterenberg    schedule 05.06.2020
comment
Прежде всего, спасибо за ответ. Теперь, когда я знаю, что он использует это утверждение, как мне получить базу утверждений на основе содержимого моей базы данных? - person Rémy Huot; 05.06.2020
comment
У вас есть несколько способов сделать это: Самый стандартный - создать собственные AuthorizaionRequirement и AuthorizarionHandler и реализовать метод HandleRequirementAsync. Вы можете увидеть, как реализован RolesAuthorizationRequirement github.com/dotnet/aspnetcore/blob/ Затем вы определяете свою собственную политику и добавляете это требование. Ознакомьтесь с этим руководством geeklearning.io/ - person Michael Shterenberg; 05.06.2020
comment
В свой ответ добавлен образец кода с использованием более быстрого решения RequireAssertion - person Michael Shterenberg; 05.06.2020
comment
Спасибо, я попробую это и помечу как ответ, если это сработает, но как вы думаете, есть ли способ добавить заявление о роли при входе пользователя в систему таким образом, чтобы я мог использовать [Authorize(Roles = "Admin")]. Таким образом, мне не нужно создавать новую политику каждый раз, когда в моей базе данных появляется новая роль. Как и во время аутентификации, в базе данных есть проверка ролей пользователя, чтобы добавить их в заявку. - person Rémy Huot; 05.06.2020
comment
Хотя я уверен, что есть способ добиться этого, я бы не рекомендовал этого делать. 1. Вы в конечном итоге вызываете диспетчер пользователей для аутентификации, даже если в этом нет необходимости (поскольку во время аутентификации вы не знаете, запускаете ли вы контроллер администратора) 2. Определение политики для каждой логической роли - неплохая идея. Политика имеет тенденцию меняться со временем, например политика с именем Admin может иметь больше условий (например, вы можете решить, что [email protected] будет автоматически предоставлять права администратора). При использовании того же имени политики любое изменение будет автоматически применяться ко всем вашим конечным точкам / контроллерам. - person Michael Shterenberg; 05.06.2020
comment
Спасибо за ваш ответ. Это помогло мне понять механику. Я попытался использовать ваш подход, но пока не смог использовать userManager в ConfigureServices (...) файла Startup.cs. - person Rémy Huot; 05.06.2020

Наконец-то я нашел рабочее решение. Я попытался адаптировать код @MichaelShterenberg с помощью RequireAssertion, но мне не удалось заставить его работать, потому что мне пришлось запросить свою базу данных, и я не смог использовать UserManager с этим решением. В итоге я нашел решение, основанное на этой части его ответа:

Вероятно, вам следует создать свой собственный AuthorizationHandler, который проверяет, действительно ли пользователь является администратором.

Я следил за ответом в этой ветке: Внедрение зависимости от параметров авторизации Требование в DotNet Core

person Rémy Huot    schedule 08.06.2020