Авторизация на основе политик в 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 Pages с помощью атрибута [Authorize] с именем политики. Например:

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

namespace AuthorizationPoliciesSample.Pages;

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

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

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

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

Применение политик к конечным точкам с 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 , реализуйте несколько обработчиков для одного требования. Например, у Майкрософт есть двери, которые открываются только с помощью карт ключей. Если вы покидаете карточку homeключа, администратор печатает временную наклейку и открывает дверь для вас. В этом сценарии у вас будет одно требование , 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 для выполнения политики

Могут возникнуть ситуации, в которых выполнение политики просто выразить в коде. Можно указать 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 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.
}

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

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

Авторизация с помощью примера внешней службы

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

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

  • Создайте регистрацию приложения в клиенте идентификатора 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 Pages с помощью атрибута [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по свойствам, содержащим требования, не помеченные как успешные. ReadPermission Для требования пользователь должен быть владельцем или спонсором для доступа к запрошенным ресурсам. 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 , реализуйте несколько обработчиков для одного требования. Например, у Майкрософт есть двери, которые открываются только с помощью карт ключей. Если вы покидаете карточку homeключа, администратор печатает временную наклейку и открывает дверь для вас. В этом сценарии у вас будет одно требование , 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 для выполнения политики

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

Например, предыдущий 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.
}

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

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