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

ASP.NET Core поддерживает настройку и администрирование средств безопасности в приложениях Blazor.

Сценарии безопасности для приложений Blazor Server и Blazor WebAssembly отличаются. Так как приложения Blazor Server выполняются на стороне сервера, проверки авторизации могут определить следующее:

  • параметры пользовательского интерфейса, предоставленные пользователю (например, какие пункты меню доступны пользователю);
  • правила доступа для приложений и компонентов.

Приложения Blazor WebAssembly выполняются на стороне клиента. В этом случае авторизация используется только для определения отображаемых вариантов пользовательского интерфейса. Так как пользователь может изменить или обойти проверки на стороне клиента, приложение Blazor WebAssembly не может применять правила авторизации доступа.

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

Примечание

SignInManager<TUser> и UserManager<TUser> не поддерживаются в компонентах Razor.

Проверка подлинности

Blazor использует существующие механизмы проверки подлинности ASP.NET Core для установления личности пользователя. Конкретный механизм зависит от того, как размещается приложение Blazor (Blazor WebAssembly или Blazor Server).

Blazor WebAssembly аутентификация

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

Добавьте следующий код:

  • Ссылка на пакет для Microsoft.AspNetCore.Components.Authorization для файла проекта приложения.
  • Пространство имен Microsoft.AspNetCore.Components.Authorization для файла _Imports.razor приложения.

Вопросы обработки проверки подлинности и использования встроенной или настраиваемой службы AuthenticationStateProvider рассматриваются в следующих разделах.

Дополнительные сведения о создании и настройке приложений см. в статье Защита ASP.NET Core Blazor WebAssembly.

Blazor Server аутентификация

Приложения Blazor Server работают через подключение в реальном времени, созданное с помощью SignalR. Проверка подлинности в приложениях на основе SignalR выполняется при установлении подключения. Аутентификация может выполняться на основе cookie или других маркеров носителя.

Встроенная служба AuthenticationStateProvider для приложений Blazor Server получает данные о состоянии проверки подлинности из HttpContext.User в ASP.NET Core. Так состояние проверки подлинности интегрируется с существующими соответствующими механизмами проверки подлинности ASP.NET Core.

Дополнительные сведения о создании и настройке приложений см. в статье Защита приложений ASP.NET Core Blazor Server.

Служба AuthenticationStateProvider

AuthenticationStateProvider является базовой службой, которую компоненты AuthorizeView и CascadingAuthenticationState используют для получения состояния аутентификации.

Обычно вы не используете AuthenticationStateProvider напрямую. Выберите подход с использованием компонента AuthorizeView или Task<AuthenticationState>, как описано далее в этой статье. Основной недостаток при использовании AuthenticationStateProvider напрямую заключается в том, что компонент не получает автоматического уведомления при изменении базовых данных о состоянии аутентификации.

Служба AuthenticationStateProvider может предоставить данные ClaimsPrincipal о текущем пользователе, как показано в следующем примере:

@page "/"
@using System.Security.Claims
@using Microsoft.AspNetCore.Components.Authorization
@inject AuthenticationStateProvider AuthenticationStateProvider

<h3>ClaimsPrincipal Data</h3>

<button @onclick="GetClaimsPrincipalData">Get ClaimsPrincipal Data</button>

<p>@_authMessage</p>

@if (_claims.Count() > 0)
{
    <ul>
        @foreach (var claim in _claims)
        {
            <li>@claim.Type: @claim.Value</li>
        }
    </ul>
}

<p>@_surnameMessage</p>

@code {
    private string _authMessage;
    private string _surnameMessage;
    private IEnumerable<Claim> _claims = Enumerable.Empty<Claim>();

    private async Task GetClaimsPrincipalData()
    {
        var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync();
        var user = authState.User;

        if (user.Identity.IsAuthenticated)
        {
            _authMessage = $"{user.Identity.Name} is authenticated.";
            _claims = user.Claims;
            _surnameMessage = 
                $"Surname: {user.FindFirst(c => c.Type == ClaimTypes.Surname)?.Value}";
        }
        else
        {
            _authMessage = "The user is NOT authenticated.";
        }
    }
}

Если user.Identity.IsAuthenticated имеет значение true, а пользователь является ClaimsPrincipal, можно перечислить утверждения и оценить членство в ролях.

Дополнительные сведения о внедрении зависимостей и службах см. в статьях Внедрение зависимостей Blazor в ASP.NET Core и Внедрение зависимостей в ASP.NET Core.

Реализация пользовательского AuthenticationStateProvider

Если приложению требуется пользовательский поставщик, реализуйте AuthenticationStateProvider и переопределите GetAuthenticationStateAsync:

using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components.Authorization;

public class CustomAuthStateProvider : AuthenticationStateProvider
{
    public override Task<AuthenticationState> GetAuthenticationStateAsync()
    {
        var identity = new ClaimsIdentity(new[]
        {
            new Claim(ClaimTypes.Name, "mrfibuli"),
        }, "Fake authentication type");

        var user = new ClaimsPrincipal(identity);

        return Task.FromResult(new AuthenticationState(user));
    }
}

В приложении Blazor WebAssembly служба CustomAuthStateProvider регистрируется в Main Program.cs:

using Microsoft.AspNetCore.Components.Authorization;

...

builder.Services.AddScoped<AuthenticationStateProvider, CustomAuthStateProvider>();

В приложении Blazor Server служба CustomAuthStateProvider регистрируется в Startup.ConfigureServices:

using Microsoft.AspNetCore.Components.Authorization;

...

services.AddScoped<AuthenticationStateProvider, CustomAuthStateProvider>();

С помощью CustomAuthStateProvider в предыдущем примере все пользователи проходят проверку подлинности с использованием имени пользователя mrfibuli.

Предоставление состояния аутентификации в качестве каскадного параметра

Если процедурная логика требует данных о состоянии аутентификации, например при выполнении активируемых пользователем действий, для получения таких данных определите каскадный параметр типа Task<AuthenticationState>:

@page "/"

<button @onclick="LogUsername">Log username</button>

<p>@_authMessage</p>

@code {
    [CascadingParameter]
    private Task<AuthenticationState> authenticationStateTask { get; set; }

    private string _authMessage;

    private async Task LogUsername()
    {
        var authState = await authenticationStateTask;
        var user = authState.User;

        if (user.Identity.IsAuthenticated)
        {
            _authMessage = $"{user.Identity.Name} is authenticated.";
        }
        else
        {
            _authMessage = "The user is NOT authenticated.";
        }
    }
}

Если user.Identity.IsAuthenticated имеет значение true, можно перечислить утверждения и оценить членство в ролях.

Настройте каскадный параметр Task<AuthenticationState> с помощью компонентов AuthorizeRouteView и CascadingAuthenticationState в компоненте App (App.razor).

<CascadingAuthenticationState>
    <Router AppAssembly="@typeof(Program).Assembly">
        <Found Context="routeData">
            <AuthorizeRouteView RouteData="@routeData" 
                DefaultLayout="@typeof(MainLayout)" />
        </Found>
        <NotFound>
            <LayoutView Layout="@typeof(MainLayout)">
                <p>Sorry, there's nothing at this address.</p>
            </LayoutView>
        </NotFound>
    </Router>
</CascadingAuthenticationState>

Примечание

В выпуске ASP.NET Core 5.0.1 и дальнейших выпусках 5.x компонент Router содержит параметр PreferExactMatches со значением @true. Дополнительные сведения см. в разделе Миграция с ASP.NET Core 3,1 на 5,0.

В приложении Blazor WebAssembly добавьте службы для параметров и авторизации в Program.Main:

builder.Services.AddOptions();
builder.Services.AddAuthorizationCore();

В приложении Blazor Server уже есть службы для параметров и авторизации, поэтому ничего делать не нужно.

Авторизация

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

Доступ обычно предоставляется или запрещается в зависимости от следующих аспектов:

  • выполнена ли аутентификация (выполнен ли вход);
  • есть ли у пользователя определенная роль;
  • есть ли у пользователя определенное утверждение;
  • выполняются ли требования политики.

Каждый из этих аспектов применяется здесь так же, как в приложениях ASP.NET Core MVC или Razor Pages. Дополнительные сведения о безопасности в ASP.NET Core вы найдете в статьях о безопасности и Identity в ASP.NET Core.

Компонент AuthorizeView

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

Этот компонент представляет переменную context типа AuthenticationState, которую можно использовать для доступа к сведениям о пользователе, выполнившем вход:

<AuthorizeView>
    <h1>Hello, @context.User.Identity.Name!</h1>
    <p>You can only see this content if you're authenticated.</p>
</AuthorizeView>

Также вы можете указать другое содержимое, которое будет отображаться, если пользователь не авторизован:

<AuthorizeView>
    <Authorized>
        <h1>Hello, @context.User.Identity.Name!</h1>
        <p>You can only see this content if you're authorized.</p>
        <button @onclick="SecureMethod">Authorized Only Button</button>
    </Authorized>
    <NotAuthorized>
        <h1>Authentication Failure!</h1>
        <p>You're not signed in.</p>
    </NotAuthorized>
</AuthorizeView>

@code {
    private void SecureMethod() { ... }
}

Содержимое тегов <Authorized> и <NotAuthorized> может включать произвольные элементы, например другие интерактивные компоненты.

Обработчик событий по умолчанию для авторизованного элемента, например, метод SecureMethod для элемента <button> в предыдущем примере, может вызываться только авторизованным пользователем.

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

Если условия авторизации не указаны, AuthorizeView использует политику по умолчанию со следующими правилами:

  • выполнившие аутентификацию (выполнившие вход) пользователи считаются авторизованными;
  • не выполнившие аутентификацию (не выполнившие вход) пользователи считаются не авторизованными.

Компонент AuthorizeView можно использовать в компоненте NavMenu (Shared/NavMenu.razor) для отображения элемента списка (<li>...</li>) для компонента NavLink (NavLink), но следует учитывать, что при использовании этого подхода удаляется только элемент списка из отображаемых выходных данных. Пользователь по-прежнему может переходить к компоненту.

Приложения, созданные на основе шаблона проекта Blazor, который включает проверку подлинности, используют компонент LoginDisplay, который зависит от компонента AuthorizeView. Компонент AuthorizeView выборочно отображает пользователям содержимое для операций, связанных с Identity. Следующий пример относится к шаблону проекта Blazor WebAssembly.

Shared/LoginDisplay.razor:

@using Microsoft.AspNetCore.Components.Authorization
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication

@inject NavigationManager Navigation
@inject SignOutSessionStateManager SignOutManager

<AuthorizeView>
    <Authorized>
        Hello, @context.User.Identity.Name!
        <button class="nav-link btn btn-link" @onclick="BeginLogout">Log out</button>
    </Authorized>
    <NotAuthorized>
        <a href="authentication/login">Log in</a>
    </NotAuthorized>
</AuthorizeView>

@code{
    private async Task BeginLogout(MouseEventArgs args)
    {
        await SignOutManager.SetSignOutState();
        Navigation.NavigateTo("authentication/logout");
    }
}

Следующий пример относится к шаблону проекта Blazor Server и использует конечные точки ASP.NET Core Identity в области Identity приложения для обработки операций, связанных с Identity.

Shared/LoginDisplay.razor:

<AuthorizeView>
    <Authorized>
        <a href="Identity/Account/Manage">Hello, @context.User.Identity.Name!</a>
        <form method="post" action="Identity/Account/LogOut">
            <button type="submit" class="nav-link btn btn-link">Log out</button>
        </form>
    </Authorized>
    <NotAuthorized>
        <a href="Identity/Account/Register">Register</a>
        <a href="Identity/Account/Login">Log in</a>
    </NotAuthorized>
</AuthorizeView>

Авторизация на основе ролей и политик

Компонент AuthorizeView поддерживает авторизацию на основе ролей или политик.

Для авторизации на основе ролей примените параметр Roles:

<AuthorizeView Roles="admin, superuser">
    <p>You can only see this if you're an admin or superuser.</p>
</AuthorizeView>

Для получения дополнительной информации см. Авторизация на основе ролей в ASP.NET Core.

Для авторизации на основе политик примените параметр Policy:

<AuthorizeView Policy="content-editor">
    <p>You can only see this if you satisfy the "content-editor" policy.</p>
</AuthorizeView>

Авторизация на основе утверждений считается особым случаем авторизации на основе политик. Например, вы можете определить политику, которая требует наличия определенного утверждения у пользователя. Для получения дополнительной информации см. Авторизация на основе политик в ASP.NET Core.

Эти API-интерфейсы можно использовать в приложениях Blazor Server или Blazor WebAssembly.

Если не указано ни Roles, ни Policy, AuthorizeView использует политику по умолчанию.

Содержимое, отображаемое при асинхронной аутентификации

Blazor позволяет асинхронно определять состояние проверки подлинности. Основной сценарий для такого подхода — приложение Blazor WebAssembly, которое направляет запрос на проверку подлинности во внешнюю конечную точку.

Пока аутентификация выполняется, AuthorizeView по умолчанию не отображает содержимое. Для отображения содержимого в ходе проверки подлинности используйте тег <Authorizing>:

<AuthorizeView>
    <Authorized>
        <h1>Hello, @context.User.Identity.Name!</h1>
        <p>You can only see this content if you're authenticated.</p>
    </Authorized>
    <Authorizing>
        <h1>Authentication in progress</h1>
        <p>You can only see this content while authentication is in progress.</p>
    </Authorizing>
</AuthorizeView>

Такой подход обычно не применим к приложениям Blazor Server. Приложения Blazor Server узнают состояние проверки подлинности, как только оно устанавливается. Содержимое Authorizing можно указать в компоненте AuthorizeView для приложения Blazor Server, но это содержимое никогда не отображается.

Атрибут [Authorize]

Атрибут [Authorize] можно использовать в компонентах Razor:

@page "/"
@attribute [Authorize]

You can only see this if you're signed in.

Важно!

Используйте [Authorize] только для компонентов @page, полученных через маршрутизатор Blazor. Авторизация выполняется только как аспект маршрутизации и не для дочерних компонентов, которые отображаются на странице. Чтобы разрешить отображение конкретных частей на странице, используйте вместо этого AuthorizeView.

Атрибут [Authorize] также поддерживает авторизацию на основе ролей или политик. Для авторизации на основе ролей примените параметр Roles:

@page "/"
@attribute [Authorize(Roles = "admin, superuser")]

<p>You can only see this if you're in the 'admin' or 'superuser' role.</p>

Для авторизации на основе политик примените параметр Policy:

@page "/"
@attribute [Authorize(Policy = "content-editor")]

<p>You can only see this if you satisfy the 'content-editor' policy.</p>

Если не указано ни Roles, ни Policy, [Authorize] использует политику по умолчанию со следующими правилами:

  • выполнившие аутентификацию (выполнившие вход) пользователи считаются авторизованными;
  • не выполнившие аутентификацию (не выполнившие вход) пользователи считаются не авторизованными.

Настройка несанкционированного содержимого для компонента маршрутизатора

Компонент Router вместе с компонентом AuthorizeRouteView позволяет приложению указать пользовательское содержимое для следующих ситуаций:

  • Пользователь не удовлетворяет условию [Authorize], которое применено к компоненту. Отображается разметка для элемента <NotAuthorized>. Атрибут [Authorize] описан в разделе Атрибут [Authorize]).
  • Выполняется асинхронная авторизация. Как правило, это означает, что выполняется проверка подлинности пользователя. Отображается разметка для элемента <Authorizing>.
  • Содержимое не найдено. Отображается разметка для элемента <NotFound>.

В стандартном шаблоне проекта Blazor Server приложения есть компонент App (App.razor) для настройки пользовательского содержимого.

<CascadingAuthenticationState>
    <Router AppAssembly="@typeof(Program).Assembly">
        <Found Context="routeData">
            <AuthorizeRouteView RouteData="@routeData" 
                DefaultLayout="@typeof(MainLayout)">
                <NotAuthorized>
                    <h1>Sorry</h1>
                    <p>You're not authorized to reach this page.</p>
                    <p>You may need to log in as a different user.</p>
                </NotAuthorized>
                <Authorizing>
                    <h1>Authorization in progress</h1>
                    <p>Only visible while authorization is in progress.</p>
                </Authorizing>
            </AuthorizeRouteView>
        </Found>
        <NotFound>
            <LayoutView Layout="@typeof(MainLayout)">
                <h1>Sorry</h1>
                <p>Sorry, there's nothing at this address.</p>
            </LayoutView>
        </NotFound>
    </Router>
</CascadingAuthenticationState>

Примечание

В выпуске ASP.NET Core 5.0.1 и дальнейших выпусках 5.x компонент Router содержит параметр PreferExactMatches со значением @true. Дополнительные сведения см. в разделе Миграция с ASP.NET Core 3,1 на 5,0.

Содержимое тегов <NotFound>, <NotAuthorized> и <Authorizing> может включать произвольные элементы, например другие интерактивные компоненты.

Если тег <NotAuthorized> не указан, то AuthorizeRouteView использует следующее резервное сообщение:

Not authorized.

Уведомление об изменении состояния аутентификации

Если приложение определяет, что базовые данные о состоянии аутентификации изменились (например, пользователь вышел из системы или другой пользователь изменил роль), пользовательский компонент AuthenticationStateProvider может вызвать необязательный метод NotifyAuthenticationStateChanged в базовом классе AuthenticationStateProvider. Это действие информирует потребителей данных состояния аутентификации (таких, как AuthorizeView) о необходимости повторно отображения с учетом новых данных.

Процедурная логика

Если приложению нужно проверять правила авторизации в составе процедурной логики, используйте каскадный параметр с типом Task<AuthenticationState>, чтобы получить ClaimsPrincipal пользователя. Task<AuthenticationState> можно комбинировать для оценки политик с другими службами, например IAuthorizationService.

@using Microsoft.AspNetCore.Authorization
@inject IAuthorizationService AuthorizationService

<button @onclick="@DoSomething">Do something important</button>

@code {
    [CascadingParameter]
    private Task<AuthenticationState> authenticationStateTask { get; set; }

    private async Task DoSomething()
    {
        var user = (await authenticationStateTask).User;

        if (user.Identity.IsAuthenticated)
        {
            // Perform an action only available to authenticated (signed-in) users.
        }

        if (user.IsInRole("admin"))
        {
            // Perform an action only available to users in the 'admin' role.
        }

        if ((await AuthorizationService.AuthorizeAsync(user, "content-editor"))
            .Succeeded)
        {
            // Perform an action only available to users satisfying the 
            // 'content-editor' policy.
        }
    }
}

Примечание

В компоненте приложения Blazor WebAssembly добавьте пространства имен Microsoft.AspNetCore.Authorization и Microsoft.AspNetCore.Components.Authorization:

@using Microsoft.AspNetCore.Authorization
@using Microsoft.AspNetCore.Components.Authorization

Эти пространства имен можно предоставить глобально, добавив их в файл _Imports.razor приложения.

Устранение неполадок

Распространенные ошибки

  • Для авторизации требуется каскадный параметр с типом Task\<AuthenticationState>. Чтобы предоставить его, попробуйте использовать CascadingAuthenticationState.

  • Для authenticationStateTask возвращается значение null

Вполне вероятно, что проект не был создан на основе шаблона приложения Blazor Server с включенной проверкой подлинности. Примените <CascadingAuthenticationState> в качестве оболочки определенной части дерева пользовательского интерфейса, например в компоненте App (App.razor), следующим образом.

<CascadingAuthenticationState>
    <Router AppAssembly="@typeof(Program).Assembly">
        ...
    </Router>
</CascadingAuthenticationState>

Примечание

В выпуске ASP.NET Core 5.0.1 и дальнейших выпусках 5.x компонент Router содержит параметр PreferExactMatches со значением @true. Дополнительные сведения см. в разделе Миграция с ASP.NET Core 3,1 на 5,0.

CascadingAuthenticationState предоставляет каскадный параметр Task<AuthenticationState>, который он, в свою очередь, получает из базовой службы внедрения зависимостей AuthenticationStateProvider.

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