Поделиться через


Авторизация на основе политик в ASP.NET Core

В основе, авторизация на основе ролей и авторизация на основе утверждений используют требование, обработчик требований и предварительно настроенную политику. Эти стандартные блоки поддерживают выражение вычислений авторизации в коде. Результатом является более богатая, многократно использованная, проверяемая структура авторизации.

Политика авторизации включает одно или несколько требований. Зарегистрируйте его в рамках конфигурации службы авторизации в файле приложения Program.cs :

builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("AtLeast21", policy =>
        policy.Requirements.Add(new MinimumAgeRequirement(21)));
});

В предыдущем примере создается политика AtLeast21. Он имеет одно требование — минимальное возрастное значение, которое предоставляется в качестве параметра для требования.

IAuthorizationService

Основная служба, которая определяет успешность авторизации:IAuthorizationService

/// <summary>
/// Checks policy based permissions for a user
/// </summary>
public interface IAuthorizationService
{
    /// <summary>
    /// Checks if a user meets a specific set of requirements for the specified resource
    /// </summary>
    /// <param name="user">The user to evaluate the requirements against.</param>
    /// <param name="resource">
    /// An optional resource the policy should be checked with.
    /// If a resource is not required for policy evaluation you may pass null as the value
    /// </param>
    /// <param name="requirements">The requirements to evaluate.</param>
    /// <returns>
    /// A flag indicating whether authorization has succeeded.
    /// This value is <value>true</value> when the user fulfills the policy; 
    /// otherwise <value>false</value>.
    /// </returns>
    /// <remarks>
    /// Resource is an optional parameter and may be null. Please ensure that you check 
    /// it is not null before acting upon it.
    /// </remarks>
    Task<AuthorizationResult> AuthorizeAsync(ClaimsPrincipal user, object resource, 
                                     IEnumerable<IAuthorizationRequirement> requirements);

    /// <summary>
    /// Checks if a user meets a specific authorization policy
    /// </summary>
    /// <param name="user">The user to check the policy against.</param>
    /// <param name="resource">
    /// An optional resource the policy should be checked with.
    /// If a resource is not required for policy evaluation you may pass null as the value
    /// </param>
    /// <param name="policyName">The name of the policy to check against a specific 
    /// context.</param>
    /// <returns>
    /// A flag indicating whether authorization has succeeded.
    /// Returns a flag indicating whether the user, and optional resource has fulfilled 
    /// the policy.    
    /// <value>true</value> when the policy has been fulfilled; 
    /// otherwise <value>false</value>.
    /// </returns>
    /// <remarks>
    /// Resource is an optional parameter and may be null. Please ensure that you check
    /// it is not null before acting upon it.
    /// </remarks>
    Task<AuthorizationResult> AuthorizeAsync(
                                ClaimsPrincipal user, object resource, string policyName);
}

Приведенный выше код выделяет два метода службы IAuthorizationService.

IAuthorizationRequirement — это служба маркеров без методов и механизм отслеживания успешности авторизации.

Каждый IAuthorizationHandler отвечает за проверку соответствия требованиям:

/// <summary>
/// Classes implementing this interface are able to make a decision if authorization
/// is allowed.
/// </summary>
public interface IAuthorizationHandler
{
    /// <summary>
    /// Makes a decision if authorization is allowed.
    /// </summary>
    /// <param name="context">The authorization information.</param>
    Task HandleAsync(AuthorizationHandlerContext context);
}

Класс AuthorizationHandlerContext — это то, что обработчик использует для пометки соответствия требованиям:

 context.Succeed(requirement)

В следующем коде показана упрощенная (аннотированная с комментариями) реализация службы авторизации по умолчанию:

public async Task<AuthorizationResult> AuthorizeAsync(ClaimsPrincipal user, 
             object resource, IEnumerable<IAuthorizationRequirement> requirements)
{
    // Create a tracking context from the authorization inputs.
    var authContext = _contextFactory.CreateContext(requirements, user, resource);

    // By default this returns an IEnumerable<IAuthorizationHandler> from DI.
    var handlers = await _handlers.GetHandlersAsync(authContext);

    // Invoke all handlers.
    foreach (var handler in handlers)
    {
        await handler.HandleAsync(authContext);
    }

    // Check the context, by default success is when all requirements have been met.
    return _evaluator.Evaluate(authContext);
}

В следующем коде показана стандартная конфигурация службы авторизации:

// Add all of your handlers to DI.
builder.Services.AddSingleton<IAuthorizationHandler, MyHandler1>();
// MyHandler2, ...

builder.Services.AddSingleton<IAuthorizationHandler, MyHandlerN>();

// Configure your policies
builder.Services.AddAuthorization(options =>
      options.AddPolicy("Something",
      policy => policy.RequireClaim("Permission", "CanViewPage", "CanViewAnything")));

Используйте IAuthorizationService, [Authorize(Policy = "Something")]или RequireAuthorization("Something") для авторизации.

Применение политик к контроллерам MVC

Сведения о приложениях, использующих Razor Страницы, см. в разделе "Применить политики к Razor страницам ".

Примените политики к контроллерам с помощью атрибута [Authorize] с именем политики:

[Authorize(Policy = "AtLeast21")]
public class AtLeast21Controller : Controller
{
    public IActionResult Index() => View();
}

Если на уровне контроллера и действия применяются несколько политик, все политики должны передаваться перед предоставлением доступа:

[Authorize(Policy = "AtLeast21")]
public class AtLeast21Controller2 : Controller
{
    [Authorize(Policy = "IdentificationValidated")]
    public IActionResult Index() => View();
}

Применение политик к страницам Razor

Примените политики к Razor Страницам с помощью атрибута [Authorize] и укажите имя политики. Например:

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc.RazorPages;

namespace AuthorizationPoliciesSample.Pages;

[Authorize(Policy = "AtLeast21")]
public class AtLeast21Model : PageModel { }

Политики нельзя применять на Razor уровне обработчика страницы, их необходимо применить к странице.

Политику также можно применять к Razor страницам, используя конвенцию авторизации.

Применение политик к конечным точкам

Примените политики к конечным точкам с помощью RequireAuthorization именем политики. Например:

app.MapGet("/helloworld", () => "Hello World!")
    .RequireAuthorization("AtLeast21");

Требования

Требование авторизации — это коллекция параметров данных, которые политика может использовать для оценки текущего участника-пользователя. В нашей политике AtLeast21 требование является одним параметром — минимальным возрастом. Требование реализует пустой интерфейс маркера IAuthorizationRequirement. Параметризованный минимальный возраст может быть реализован следующим образом:

using Microsoft.AspNetCore.Authorization;

namespace AuthorizationPoliciesSample.Policies.Requirements;

public class MinimumAgeRequirement : IAuthorizationRequirement
{
    public MinimumAgeRequirement(int minimumAge) =>
        MinimumAge = minimumAge;

    public int MinimumAge { get; }
}

Если политика авторизации содержит несколько требований авторизации, все требования должны пройти, чтобы оценка политики прошла успешно. Другими словами, несколько требований авторизации, добавленных в одну политику авторизации, обрабатываются на основе AND .

Примечание.

Требование не требует данных или свойств.

Обработчики авторизации

Обработчик авторизации отвечает за оценку свойств требования. Обработчик авторизации оценивает требования к предоставленному AuthorizationHandlerContext , чтобы определить, разрешен ли доступ.

Требование может содержать несколько обработчиков. Обработчик может наследовать AuthorizationHandler<TRequirement>, где TRequirement является требованием, которое должно быть обработано. Кроме того, обработчик может реализовать IAuthorizationHandler непосредственно для обработки нескольких типов требований.

Используйте обработчик для одного условия

В следующем примере показана связь "один к одному", в которой обработчик минимального возраста обрабатывает одно требование:

using System.Security.Claims;
using AuthorizationPoliciesSample.Policies.Requirements;
using Microsoft.AspNetCore.Authorization;

namespace AuthorizationPoliciesSample.Policies.Handlers;

public class MinimumAgeHandler : AuthorizationHandler<MinimumAgeRequirement>
{
    protected override Task HandleRequirementAsync(
        AuthorizationHandlerContext context, MinimumAgeRequirement requirement)
    {
        var dateOfBirthClaim = context.User.FindFirst(
            c => c.Type == ClaimTypes.DateOfBirth && c.Issuer == "http://contoso.com");

        if (dateOfBirthClaim is null)
        {
            return Task.CompletedTask;
        }

        var dateOfBirth = Convert.ToDateTime(dateOfBirthClaim.Value);
        int calculatedAge = DateTime.Today.Year - dateOfBirth.Year;
        if (dateOfBirth > DateTime.Today.AddYears(-calculatedAge))
        {
            calculatedAge--;
        }

        if (calculatedAge >= requirement.MinimumAge)
        {
            context.Succeed(requirement);
        }

        return Task.CompletedTask;
    }
}

Предыдущий код определяет, имеет ли текущий субъект-пользователь дату утверждения о рождении, выданного известным и доверенным издателем. Авторизация не может выполняться, если отсутствует требование, тогда возвращается выполненная задача. При наличии утверждения вычисляется возраст пользователя. Если пользователь соответствует минимальному возрасту, определенному требованием, авторизация считается успешной. При успешной авторизации вызывается context.Succeed с выполненным требованием в качестве единственного параметра.

Использование обработчика для нескольких требований

В следующем примере показана связь "один ко многим", в которой обработчик разрешений может обрабатывать три различных типа требований:

using System.Security.Claims;
using AuthorizationPoliciesSample.Policies.Requirements;
using Microsoft.AspNetCore.Authorization;

namespace AuthorizationPoliciesSample.Policies.Handlers;

public class PermissionHandler : IAuthorizationHandler
{
    public Task HandleAsync(AuthorizationHandlerContext context)
    {
        var pendingRequirements = context.PendingRequirements.ToList();

        foreach (var requirement in pendingRequirements)
        {
            if (requirement is ReadPermission)
            {
                if (IsOwner(context.User, context.Resource)
                    || IsSponsor(context.User, context.Resource))
                {
                    context.Succeed(requirement);
                }
            }
            else if (requirement is EditPermission || requirement is DeletePermission)
            {
                if (IsOwner(context.User, context.Resource))
                {
                    context.Succeed(requirement);
                }
            }
        }

        return Task.CompletedTask;
    }

    private static bool IsOwner(ClaimsPrincipal user, object? resource)
    {
        // Code omitted for brevity
        return true;
    }

    private static bool IsSponsor(ClaimsPrincipal user, object? resource)
    {
        // Code omitted for brevity
        return true;
    }
}

Предыдущий код обходит PendingRequirements, что является свойством, содержащим требования, не отмеченные как выполненные. Для выполнения ReadPermission требования пользователь должен быть либо владельцем, либо спонсором, чтобы получить доступ к запрашиваемому ресурсу. EditPermission или DeletePermission Для требования они должны быть обладателем, чтобы получить доступ к запрошенному ресурсу.

Регистрация обработчика

Регистрируйте обработчики в коллекции служб во время настройки. Например:

builder.Services.AddSingleton<IAuthorizationHandler, MinimumAgeHandler>();

Предыдущий код регистрирует MinimumAgeHandler как одиночку. Обработчики можно зарегистрировать с помощью любого из встроенных сроков жизни службы.

Можно объединить как требование, так и обработчик в один класс, реализующий оба IAuthorizationRequirement и IAuthorizationHandler. Это объединение создает тесную связь между обработчиком и требованием и рекомендуется только для простых требований и обработчиков. Создание класса, который реализует оба интерфейса, устраняет необходимость регистрации обработчика в DI, благодаря встроенной возможности PassThroughAuthorizationHandler, позволяющей требованиям обрабатывать себя самостоятельно.

Ознакомьтесь с реализацией класса AssertionRequirement, где AssertionRequirement служит как требованием, так и обработчиком в полностью автономном классе.

Что должен возвращать обработчик?

Обратите внимание, что Handle метод в примере обработчика не возвращает значения. Как указывается статус успеха или неудачи?

  • Обработчик указывает на успешность вызова context.Succeed(IAuthorizationRequirement requirement), передав требование, которое было успешно проверено.

  • Обработчику обычно не нужно обрабатывать сбои, так как другие обработчики для того же требования могут завершиться успешно.

  • Чтобы гарантировать неудачу, даже если другие обработчики требований успешно выполняются, вызовите context.Fail.

Если обработчик вызывает context.Succeed или context.Failвсе остальные обработчики по-прежнему вызываются. Это позволяет требованиям создавать побочные эффекты, такие как ведение журнала, которое происходит, даже если другой обработчик успешно проверил или не выполнил требование. Если установлено значение false, свойство InvokeHandlersAfterFailure прерывает выполнение обработчиков при вызове context.Fail. InvokeHandlersAfterFailure по умолчанию становится true, в этом случае вызываются все обработчики.

Примечание.

Обработчики авторизации вызываются, даже если проверка подлинности завершается ошибкой. Кроме того, обработчики могут выполняться в любом порядке, поэтому не зависят от их вызова в определенном порядке.

Зачем мне может понадобиться несколько обработчиков для требования?

В случаях, когда вы хотите выполнить оценку на основе OR , реализуйте несколько обработчиков для одного требования. Например, у Майкрософт есть двери, которые открываются только с помощью карт ключей. Если вы покидаете карточку ключа дома, администратор печатает временную наклейку и открывает дверь для вас. В этом сценарии у вас будет одно требование , BuildingEntry, но несколько обработчиков, каждый из которых изучает одно требование.

BuildingEntryRequirement.cs

using Microsoft.AspNetCore.Authorization;

namespace AuthorizationPoliciesSample.Policies.Requirements;

public class BuildingEntryRequirement : IAuthorizationRequirement { }

BadgeEntryHandler.cs

using AuthorizationPoliciesSample.Policies.Requirements;
using Microsoft.AspNetCore.Authorization;

namespace AuthorizationPoliciesSample.Policies.Handlers;

public class BadgeEntryHandler : AuthorizationHandler<BuildingEntryRequirement>
{
    protected override Task HandleRequirementAsync(
        AuthorizationHandlerContext context, BuildingEntryRequirement requirement)
    {
        if (context.User.HasClaim(
            c => c.Type == "BadgeId" && c.Issuer == "https://microsoftsecurity"))
        {
            context.Succeed(requirement);
        }

        return Task.CompletedTask;
    }
}

TemporaryStickerHandler.cs

using AuthorizationPoliciesSample.Policies.Requirements;
using Microsoft.AspNetCore.Authorization;

namespace AuthorizationPoliciesSample.Policies.Handlers;

public class TemporaryStickerHandler : AuthorizationHandler<BuildingEntryRequirement>
{
    protected override Task HandleRequirementAsync(
        AuthorizationHandlerContext context, BuildingEntryRequirement requirement)
    {
        if (context.User.HasClaim(
            c => c.Type == "TemporaryBadgeId" && c.Issuer == "https://microsoftsecurity"))
        {
            // Code to check expiration date omitted for brevity.
            context.Succeed(requirement);
        }

        return Task.CompletedTask;
    }
}

Убедитесь, что оба обработчика зарегистрированы. Если один из обработчиков успешно срабатывает при оценке политики BuildingEntryRequirement, то оценка политики завершается успешно.

Для выполнения политики используйте функцию.

Могут возникнуть ситуации, в которых выполнение политики просто выразить в коде. Можно предоставить Func<AuthorizationHandlerContext, bool>, когда настраиваете политику с помощью конструктора RequireAssertion.

Например, предыдущий BadgeEntryHandler может быть перезаписан следующим образом:

builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("BadgeEntry", policy =>
        policy.RequireAssertion(context => context.User.HasClaim(c =>
            (c.Type == "BadgeId" || c.Type == "TemporaryBadgeId")
            && c.Issuer == "https://microsoftsecurity")));
});

Доступ к контексту запроса MVC в обработчиках

Метод HandleRequirementAsync имеет два параметра: AuthorizationHandlerContext и TRequirement, который обрабатывается. Платформы, такие как MVC или SignalR, могут добавить любой объект в свойство Resource на AuthorizationHandlerContext, чтобы передать дополнительные сведения.

При использовании маршрутизации конечных точек авторизация обычно обрабатывается посредником авторизации. В данном случае свойство Resource является экземпляром HttpContext. Контекст можно использовать для доступа к текущей конечной точке, которая может использоваться для проверки базового ресурса, к которому выполняется маршрутизация. Например:

if (context.Resource is HttpContext httpContext)
{
    var endpoint = httpContext.GetEndpoint();
    var actionDescriptor = endpoint.Metadata.GetMetadata<ControllerActionDescriptor>();
    ...
}

При традиционной маршрутизации или при авторизации посредством фильтра авторизации MVC, значение Resource представляется экземпляром AuthorizationFilterContext. Это свойство предоставляет доступ к HttpContext, RouteData, и ко всему остальному, предоставляемому MVC и Razor страницами.

Использование Resource свойства зависит от платформы. Использование информации в свойстве Resource ограничивает ваши политики авторизации конкретными фреймворками. Приведите свойство, используя ключевое слово is, а затем подтвердите, что приведение выполнено успешно, чтобы код не завершался сбоем в виде InvalidCastException при выполнении на других платформах.

// Requires the following import:
//     using Microsoft.AspNetCore.Mvc.Filters;
if (context.Resource is AuthorizationFilterContext mvcContext)
{
    // Examine MVC-specific things like routing data.
}

Глобально требуется проверка подлинности всех пользователей

Дополнительные сведения о том, как глобально требовать проверку подлинности всех пользователей, см. в разделе Требовать проверку подлинности пользователей.

Авторизация с помощью внешнего сервиса (пример)

Пример кода в AspNetCore.Docs.Samples показывает, как реализовать дополнительные требования к авторизации с помощью внешней службы авторизации. Пример Contoso.API проекта защищен с помощью Azure AD. Дополнительная проверка авторизации от проекта Contoso.Security.API возвращает полезную нагрузку, описывающую, может ли клиентское приложение Contoso.API использовать API GetWeather.

Настройка примера

  • Создайте регистрацию приложения в вашем тенанте Microsoft Entra ID.

  • Назначьте этому AppRole.

  • В разделе разрешений API добавьте AppRole в качестве разрешения и предоставьте согласие администратора. Обратите внимание, что в этой настройке регистрация приложения представляет как API, так и клиент, вызывающий API. При желании можно создать две регистрации приложений. Если вы используете эту настройку, убедитесь, что выполняете только разрешения API и добавляете AppRole в качестве шага разрешения только для клиента. Только регистрация клиентского приложения требует создания секрета клиента.

  • Настройте проект Contoso.API с помощью следующих параметров:

{
  "AzureAd": {
    "Instance": "https://login.microsoftonline.com/",
    "Domain": "<Tenant name from AAD properties>.onmicrosoft.com",
    "TenantId": "<Tenant Id from AAD properties>",
    "ClientId": "<Client Id from App Registration representing the API>"
  },
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*"
}
  • Настройте Contoso.Security.API, используя следующие параметры.
{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*",
  "AllowedClients": [
    "<Use the appropriate Client Id representing the Client calling the API>"
  ]
}
  • Откройте файл ContosoAPI.collection.json и настройте среду следующим образом:

    • ClientId: идентификатор клиента из регистрации приложения, представляющего клиента, вызывающего API.
    • clientSecret: секретный ключ клиента от регистрации приложения, которое представляет клиента для вызова API.
    • TenantId: идентификатор арендатора из свойств AAD
  • Извлеките команды из ContosoAPI.collection.json файла и используйте их для создания команд cURL для тестирования приложения.

  • Запустите решение и используйте cURL для вызова API. Вы можете добавить точки останова в Contoso.Security.API.SecurityPolicyController и наблюдать, как идентификатор клиента передается, который используется для утверждения того, разрешено ли получить погоду.

Дополнительные ресурсы

Под обложками, авторизация на основе ролей и авторизация на основе утверждений используют требование, обработчик требований и предварительно настроенную политику. Эти стандартные блоки поддерживают выражение вычислений авторизации в коде. Результатом является более богатая, многократно использованная, проверяемая структура авторизации.

Политика авторизации включает одно или несколько требований. Он зарегистрирован в рамках конфигурации службы авторизации в методе Startup.ConfigureServices :

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllersWithViews();
    services.AddRazorPages();
    services.AddAuthorization(options =>
    {
        options.AddPolicy("AtLeast21", policy =>
            policy.Requirements.Add(new MinimumAgeRequirement(21)));
    });
}

В предыдущем примере создается политика AtLeast21. Он имеет одно требование — минимальное возрастное значение, которое предоставляется в качестве параметра для требования.

IAuthorizationService

Основная служба, которая определяет успешность авторизации:IAuthorizationService

/// <summary>
/// Checks policy based permissions for a user
/// </summary>
public interface IAuthorizationService
{
    /// <summary>
    /// Checks if a user meets a specific set of requirements for the specified resource
    /// </summary>
    /// <param name="user">The user to evaluate the requirements against.</param>
    /// <param name="resource">
    /// An optional resource the policy should be checked with.
    /// If a resource is not required for policy evaluation you may pass null as the value
    /// </param>
    /// <param name="requirements">The requirements to evaluate.</param>
    /// <returns>
    /// A flag indicating whether authorization has succeeded.
    /// This value is <value>true</value> when the user fulfills the policy; 
    /// otherwise <value>false</value>.
    /// </returns>
    /// <remarks>
    /// Resource is an optional parameter and may be null. Please ensure that you check 
    /// it is not null before acting upon it.
    /// </remarks>
    Task<AuthorizationResult> AuthorizeAsync(ClaimsPrincipal user, object resource, 
                                     IEnumerable<IAuthorizationRequirement> requirements);

    /// <summary>
    /// Checks if a user meets a specific authorization policy
    /// </summary>
    /// <param name="user">The user to check the policy against.</param>
    /// <param name="resource">
    /// An optional resource the policy should be checked with.
    /// If a resource is not required for policy evaluation you may pass null as the value
    /// </param>
    /// <param name="policyName">The name of the policy to check against a specific 
    /// context.</param>
    /// <returns>
    /// A flag indicating whether authorization has succeeded.
    /// Returns a flag indicating whether the user, and optional resource has fulfilled 
    /// the policy.    
    /// <value>true</value> when the policy has been fulfilled; 
    /// otherwise <value>false</value>.
    /// </returns>
    /// <remarks>
    /// Resource is an optional parameter and may be null. Please ensure that you check
    /// it is not null before acting upon it.
    /// </remarks>
    Task<AuthorizationResult> AuthorizeAsync(
                                ClaimsPrincipal user, object resource, string policyName);
}

Приведенный выше код выделяет два метода службы IAuthorizationService.

IAuthorizationRequirement — это служба маркеров без методов и механизм отслеживания успешности авторизации.

Каждый IAuthorizationHandler отвечает за проверку соответствия требованиям:

/// <summary>
/// Classes implementing this interface are able to make a decision if authorization
/// is allowed.
/// </summary>
public interface IAuthorizationHandler
{
    /// <summary>
    /// Makes a decision if authorization is allowed.
    /// </summary>
    /// <param name="context">The authorization information.</param>
    Task HandleAsync(AuthorizationHandlerContext context);
}

Класс AuthorizationHandlerContext — это то, что обработчик использует для пометки соответствия требованиям:

 context.Succeed(requirement)

В следующем коде показана упрощенная (аннотированная с комментариями) реализация службы авторизации по умолчанию:

public async Task<AuthorizationResult> AuthorizeAsync(ClaimsPrincipal user, 
             object resource, IEnumerable<IAuthorizationRequirement> requirements)
{
    // Create a tracking context from the authorization inputs.
    var authContext = _contextFactory.CreateContext(requirements, user, resource);

    // By default this returns an IEnumerable<IAuthorizationHandlers> from DI.
    var handlers = await _handlers.GetHandlersAsync(authContext);

    // Invoke all handlers.
    foreach (var handler in handlers)
    {
        await handler.HandleAsync(authContext);
    }

    // Check the context, by default success is when all requirements have been met.
    return _evaluator.Evaluate(authContext);
}

В приведенном ниже коде показан типичный ConfigureServices.

public void ConfigureServices(IServiceCollection services)
{
    // Add all of your handlers to DI.
    services.AddSingleton<IAuthorizationHandler, MyHandler1>();
    // MyHandler2, ...

    services.AddSingleton<IAuthorizationHandler, MyHandlerN>();

    // Configure your policies
    services.AddAuthorization(options =>
          options.AddPolicy("Something",
          policy => policy.RequireClaim("Permission", "CanViewPage", "CanViewAnything")));


    services.AddControllersWithViews();
    services.AddRazorPages();
}

Используйте IAuthorizationService или [Authorize(Policy = "Something")] для авторизации.

Применение политик к контроллеру MVC

Если вы используете Razor страницы, см. статью Применение политик к Razor страницам в этом документе.

Политики применяются к контроллерам с помощью атрибута [Authorize], который указывает имя политики. Например:

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

[Authorize(Policy = "AtLeast21")]
public class AlcoholPurchaseController : Controller
{
    public IActionResult Index() => View();
}

Применение политик к страницам Razor

Политики применяются к Razor страницам с помощью атрибута [Authorize] с названием политики. Например:

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc.RazorPages;

[Authorize(Policy = "AtLeast21")]
public class AlcoholPurchaseModel : PageModel
{
}

Политики нельзя применять на Razor уровне обработчика страницы, их необходимо применить к странице.

Политики можно применять к Razor страницам с помощью конвенции авторизации.

Требования

Требование авторизации — это коллекция параметров данных, которые политика может использовать для оценки текущего участника-пользователя. В нашей политике AtLeast21 требование является одним параметром — минимальным возрастом. Требование реализует IAuthorizationRequirement, который представляет собой пустой интерфейс маркера. Параметризованный минимальный возраст может быть реализован следующим образом:

using Microsoft.AspNetCore.Authorization;

public class MinimumAgeRequirement : IAuthorizationRequirement
{
    public int MinimumAge { get; }

    public MinimumAgeRequirement(int minimumAge)
    {
        MinimumAge = minimumAge;
    }
}

Если политика авторизации содержит несколько требований авторизации, все требования должны пройти, чтобы оценка политики прошла успешно. Другими словами, несколько требований авторизации, добавленных в одну политику авторизации, обрабатываются на основе AND .

Примечание.

Требование не требует данных или свойств.

Обработчики авторизации

Обработчик авторизации отвечает за оценку свойств требования. Обработчик авторизации оценивает требования к предоставленному AuthorizationHandlerContext , чтобы определить, разрешен ли доступ.

Требование может содержать несколько обработчиков. Обработчик может наследовать AuthorizationHandler<TRequirement>, где TRequirement является требованием для обработки. Кроме того, обработчик может реализовать IAuthorizationHandler для обработки нескольких типов требований.

Используйте обработчик для одного требования

В следующем примере показана связь "один к одному", в которой обработчик минимального возраста использует одно требование:

using System;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using PoliciesAuthApp1.Services.Requirements;

public class MinimumAgeHandler : AuthorizationHandler<MinimumAgeRequirement>
{
    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context,
                                                   MinimumAgeRequirement requirement)
    {
        if (!context.User.HasClaim(c => c.Type == ClaimTypes.DateOfBirth &&
                                        c.Issuer == "http://contoso.com"))
        {
            //TODO: Use the following if targeting a version of
            //.NET Framework older than 4.6:
            //      return Task.FromResult(0);
            return Task.CompletedTask;
        }

        var dateOfBirth = Convert.ToDateTime(
            context.User.FindFirst(c => c.Type == ClaimTypes.DateOfBirth && 
                                        c.Issuer == "http://contoso.com").Value);

        int calculatedAge = DateTime.Today.Year - dateOfBirth.Year;
        if (dateOfBirth > DateTime.Today.AddYears(-calculatedAge))
        {
            calculatedAge--;
        }

        if (calculatedAge >= requirement.MinimumAge)
        {
            context.Succeed(requirement);
        }

        //TODO: Use the following if targeting a version of
        //.NET Framework older than 4.6:
        //      return Task.FromResult(0);
        return Task.CompletedTask;
    }
}

Предыдущий код определяет, имеет ли текущий пользователь утверждение о дате рождения, выданное известным и надежным издателем. Авторизация не может происходить, если заявка отсутствует, в этом случае возвращается выполненная задача. При наличии утверждения вычисляется возраст пользователя. Если пользователь соответствует минимальному возрасту, определенному требованием, авторизация считается успешной. При успешной авторизации вызывается context.Succeed с удовлетворённым требованием в качестве единственного параметра.

Использование обработчика для нескольких требований

В следующем примере показана связь "один ко многим", в которой обработчик разрешений может обрабатывать три различных типа требований:

using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using PoliciesAuthApp1.Services.Requirements;

public class PermissionHandler : IAuthorizationHandler
{
    public Task HandleAsync(AuthorizationHandlerContext context)
    {
        var pendingRequirements = context.PendingRequirements.ToList();

        foreach (var requirement in pendingRequirements)
        {
            if (requirement is ReadPermission)
            {
                if (IsOwner(context.User, context.Resource) ||
                    IsSponsor(context.User, context.Resource))
                {
                    context.Succeed(requirement);
                }
            }
            else if (requirement is EditPermission ||
                     requirement is DeletePermission)
            {
                if (IsOwner(context.User, context.Resource))
                {
                    context.Succeed(requirement);
                }
            }
        }

        //TODO: Use the following if targeting a version of
        //.NET Framework older than 4.6:
        //      return Task.FromResult(0);
        return Task.CompletedTask;
    }

    private bool IsOwner(ClaimsPrincipal user, object resource)
    {
        // Code omitted for brevity

        return true;
    }

    private bool IsSponsor(ClaimsPrincipal user, object resource)
    {
        // Code omitted for brevity

        return true;
    }
}

Предыдущий код проходит PendingRequirementsпо свойствам, содержащим требования, не помеченные как успешные. Для выполнения требования пользователь должен быть либо владельцем, либо спонсором, чтобы получить доступ к запрашиваемому ресурсу. Для требования EditPermission или DeletePermission пользователь должен быть владельцем, чтобы получить доступ к запрошенным ресурсам.

Регистрация обработчика

Обработчики регистрируются в коллекции служб во время настройки. Например:

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllersWithViews();
    services.AddRazorPages();
    services.AddAuthorization(options =>
    {
        options.AddPolicy("AtLeast21", policy =>
            policy.Requirements.Add(new MinimumAgeRequirement(21)));
    });

    services.AddSingleton<IAuthorizationHandler, MinimumAgeHandler>();
}

Предыдущий код регистрирует MinimumAgeHandler как одиночный экземпляр, вызывая services.AddSingleton<IAuthorizationHandler, MinimumAgeHandler>();. Обработчики можно зарегистрировать с помощью любого встроенного времени существования службы.

Можно объединить как требование, так и обработчик в одном классе, реализующего оба IAuthorizationRequirement и IAuthorizationHandler. Это объединение создает тесную связь между обработчиком и требованием и рекомендуется только для простых требований и обработчиков. Создание класса, реализующего оба интерфейса, удаляет необходимость регистрации обработчика в DI из-за встроенной функции PassThroughAuthorizationHandler , которая позволяет обрабатывать требования самостоятельно.

См. класс AssertionRequirement для хорошего примера, где AssertionRequirement одновременно является требованием и обработчиком в полностью автономном классе.

Какой результат должен возвращать обработчик?

Обратите внимание, что Handle метод в примере обработчика не возвращает значения. Как обозначается статус успеха или неудачи?

  • Обработчик указывает на успешность вызова context.Succeed(IAuthorizationRequirement requirement), передав требование, которое было успешно проверено.

  • Обработчику обычно не нужно обрабатывать сбои, так как другие обработчики для одного и того же требования могут справиться успешно.

  • Чтобы гарантировать сбой, даже если другие обработчики требований будут успешными, вызовите context.Fail.

Если обработчик вызывает context.Succeed или context.Failвсе остальные обработчики по-прежнему вызываются. Это позволяет требованиям создавать побочные эффекты, такие как ведение журнала, которое происходит, даже если другой обработчик успешно проверил или не выполнил требование. Если задано значение false, свойство InvokeHandlersAfterFailure останавливает выполнение обработчиков при вызове context.Fail. InvokeHandlersAfterFailure По умолчанию это true, в этом случае вызываются все обработчики.

Примечание.

Обработчики авторизации вызываются, даже если проверка подлинности завершается ошибкой.

Зачем мне понадобятся несколько обработчиков для одного требования?

В случаях, когда вы хотите выполнить оценку на основе OR , реализуйте несколько обработчиков для одного требования. Например, у Майкрософт есть двери, которые открываются только с помощью карт ключей. Если вы покидаете карточку ключа дома, администратор печатает временную наклейку и открывает дверь для вас. В этом сценарии у вас будет одно требование , BuildingEntry, но несколько обработчиков, каждый из которых изучает одно требование.

BuildingEntryRequirement.cs

using Microsoft.AspNetCore.Authorization;

public class BuildingEntryRequirement : IAuthorizationRequirement
{
}

BadgeEntryHandler.cs

using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using PoliciesAuthApp1.Services.Requirements;

public class BadgeEntryHandler : AuthorizationHandler<BuildingEntryRequirement>
{
    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context,
                                                   BuildingEntryRequirement requirement)
    {
        if (context.User.HasClaim(c => c.Type == "BadgeId" &&
                                       c.Issuer == "http://microsoftsecurity"))
        {
            context.Succeed(requirement);
        }

        //TODO: Use the following if targeting a version of
        //.NET Framework older than 4.6:
        //      return Task.FromResult(0);
        return Task.CompletedTask;
    }
}

TemporaryStickerHandler.cs

using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using PoliciesAuthApp1.Services.Requirements;

public class TemporaryStickerHandler : AuthorizationHandler<BuildingEntryRequirement>
{
    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, 
                                                   BuildingEntryRequirement requirement)
    {
        if (context.User.HasClaim(c => c.Type == "TemporaryBadgeId" &&
                                       c.Issuer == "https://microsoftsecurity"))
        {
            // We'd also check the expiration date on the sticker.
            context.Succeed(requirement);
        }

        //TODO: Use the following if targeting a version of
        //.NET Framework older than 4.6:
        //      return Task.FromResult(0);
        return Task.CompletedTask;
    }
}

Убедитесь, что оба обработчика зарегистрированы. Если хоть один обработчик успешно выполнит свою задачу при оценке BuildingEntryRequirement политики, оценка политики будет успешной.

Используйте функцию для реализации политики

Могут возникнуть ситуации, в которых выполнение политики просто выразить в коде. При настройке политики можно предоставить Func<AuthorizationHandlerContext, bool> с помощью RequireAssertion построителя политик.

Например, предыдущий BadgeEntryHandler может быть перезаписан следующим образом:

services.AddAuthorization(options =>
{
     options.AddPolicy("BadgeEntry", policy =>
        policy.RequireAssertion(context =>
            context.User.HasClaim(c =>
                (c.Type == "BadgeId" ||
                 c.Type == "TemporaryBadgeId") &&
                 c.Issuer == "https://microsoftsecurity")));
});

Доступ к контексту запроса MVC в обработчиках

Метод HandleRequirementAsync, который вы реализуете в обработчике авторизации, имеет два параметра: AuthorizationHandlerContext и TRequirement, которые вы обрабатываете. Фреймворки, такие как MVC или SignalR, могут свободно добавлять любой объект в свойство Resource на AuthorizationHandlerContext, чтобы передавать дополнительные сведения.

При использовании маршрутизации конечных точек авторизация обычно выполняется с помощью мидлвера авторизации. В этом случае свойство Resource является экземпляром HttpContext. Контекст можно использовать для доступа к текущей конечной точке, которая может использоваться для проверки базового ресурса, к которому выполняется маршрутизация. Например:

if (context.Resource is HttpContext httpContext)
{
    var endpoint = httpContext.GetEndpoint();
    var actionDescriptor = endpoint.Metadata.GetMetadata<ControllerActionDescriptor>();
    ...
}

При традиционной маршрутизации или когда авторизация происходит в рамках фильтра авторизации MVC, значением Resource является экземпляр AuthorizationFilterContext. Это свойство предоставляет доступ к HttpContext, RouteData, а также ко всему остальному, что предоставляется MVC и Razor Pages.

Использование Resource свойства зависит от платформы. Использование сведений в свойстве Resource ограничивает ваши политики авторизации определенными фреймворками. Приведите свойство Resource с помощью ключевого слова is, а затем убедитесь, что приведение выполнено успешно, чтобы ваш код не завершился сбоем при выполнении на других платформах InvalidCastException.

// Requires the following import:
//     using Microsoft.AspNetCore.Mvc.Filters;
if (context.Resource is AuthorizationFilterContext mvcContext)
{
    // Examine MVC-specific things like routing data.
}

Глобально требуется проверка подлинности всех пользователей

Дополнительные сведения о том, как глобально требовать проверку подлинности всех пользователей, см. в разделе Требовать проверку подлинности пользователей.