在 Blazor 服务器中强制执行全局授权要求

Enforce Global Authorization Requirement in Blazor Server

我有一个 Blazor 服务器应用程序,我在其中使用 ASP.Net Core Identity 实现了 authentication/authorization。我的目标是在全球范围内要求授权。如果用户浏览到任何路由,无论是否有效,但他们还没有登录,他们将被重定向到登录页面。

该过程目前仅部分起作用。假设我是 运行 应用程序,但我还没有登录。根据我采取的操作,我会收到三种不同的响应。

我试过几种不同的方法来处理这个问题。这是我目前所处的位置。

App.razor

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

_Imports.razor

@using System.Net.Http
@using Microsoft.AspNetCore.Authorization
@using Microsoft.AspNetCore.Components.Authorization
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web
@using Microsoft.JSInterop
@using Blazored.Modal
@using Blazored.Modal.Services
@attribute [Authorize]

RedirectToLogin.razor

@using System.Security.Claims
@inject NavigationManager Navigation

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

    protected override async Task OnInitializedAsync()
    {
        ClaimsPrincipal authenticationState = (await AuthenticationState).User;

        if (!authenticationState.Identity.IsAuthenticated)
        {
            var returnUrl = Navigation.ToBaseRelativePath(Navigation.Uri);

            if (String.IsNullOrWhiteSpace(returnUrl))
            {
                Navigation.NavigateTo("/identity/account/login", true);
                
            }

        }
    }
}

_LoginPartial.cshtml

@using Microsoft.AspNetCore.Authorization
@using Microsoft.AspNetCore.Identity
@attribute [AllowAnonymous]
@inject SignInManager<UserModel> SignInManager
@inject UserManager<UserModel> UserManager
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

<ul class="navbar-nav">
    @if (SignInManager.IsSignedIn(User))
    {
        <li class="nav-item">
            <a class="nav-link text-dark" asp-area="Identity" asp-page="/Account/Manage/Index" title="Manage">Hello @User.Identity.Name!</a>
        </li>
        <li class="nav-item">
            <form class="form-inline" asp-area="Identity" asp-page="/Identity/Account/Logout" asp-route-returnUrl="/" method="post">
                <button type="submit" class="nav-link btn btn-link text-dark">Logout</button>
            </form>
        </li>
    }
    else
    {
        <li class="nav-item">
            <a class="nav-link text-dark" asp-area="Identity" asp-page="/Account/Register">Register</a>
        </li>
        <li class="nav-item">
            <a class="nav-link text-dark" asp-area="Identity" asp-page="/Account/Login">Login</a>
        </li>
    }
</ul>

如果需要的话,我可以在每个剃须刀页面上添加 @attribute[Authorize]。一旦我介绍角色,他们中的大多数人可能无论如何都会得到一个。我绝对希望未经身份验证的用户的默认操作是重定向到登录页面。我错过了什么?

不确定为什么要写 ASP.Net Identity 4。你可能是说 Asp.Net Core Identity,对吧?

I'm alright with adding an @attribute[Authorize] to each razor page if that's what it takes

但是您已经在 _Imports.razor 文件中包含了 @attribute[Authorize],这使得无需将此指令添加到每个 razor 文件。

我不确定我是否理解这个问题。如果您的 _Imports.razor 文件包含@attribute[Authorize],则未经授权的用户会自动重定向到登录页面...

RedirectToLogin 组件应仅包含执行重定向的代码。它不应包含验证用户是否已通过身份验证的代码。重定向到 RedirectToLogin 组件的代码应该包含执行此检查的代码。在你的情况下,来自 AuthorizeRouteView.NotAuthorized 您的代码应如下所示:

<AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)">
                <NotAuthorized>
              
                    @if (!context.User.Identity.IsAuthenticated)
                    {
                       var returnUrl =
               NavigationManager.ToBaseRelativePath(NavigationManager.Uri);
                <RedirectToLogin ReturnUrl="@returnUrl" />

                        
                    }
                    else
                    {
                        <p>You are not authorized to access this resource.</p>
                    }
                </NotAuthorized>
            </AuthorizeRouteView>

注意,当用户是 <NotAuthorized> 时,我们检查他是否已通过身份验证,如果他未通过身份验证,我们将呈现代码为 :

RedirectToLogin 组件
@inject NavigationManager NavigationManager

@code{
    
    [Parameter]
    public string ReturnUrl { get; set; }
    protected override void OnInitialized()
    {
        ReturnUrl = "~/" + ReturnUrl;
        NavigationManager.NavigateTo($"Identity/Account/Login?returnUrl= 
             {ReturnUrl}", forceLoad:true);
    }
}

但是,如果用户通过身份验证,我们会显示消息:

You are not authorized to access this resource. 您现在可能已经意识到,用户可能已通过身份验证但仍未获得访问给定资源的授权。

以下是您的 App.razor 文件中应包含的完整代码

@inject NavigationManager NavigationManager

<CascadingAuthenticationState>
    <Router AppAssembly="@typeof(Program).Assembly">
        <Found Context="routeData">
            <AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)">
                <NotAuthorized>
                    @if (!context.User.Identity.IsAuthenticated)
                        {
                           var returnUrl =
                   NavigationManager.ToBaseRelativePath(NavigationManager.Uri);
                    <RedirectToLogin ReturnUrl="@returnUrl" />

                            
                        }
                        else
                        {
                            <p>You are not authorized to access this resource. 
                            </p>
                        }
                    </NotAuthorized>
            </AuthorizeRouteView>
        </Found>
        <NotFound>
            <LayoutView Layout="@typeof(MainLayout)">
                <p>Sorry, there's nothing at this address.</p>
            </LayoutView>
        </NotFound>
    </Router>
</CascadingAuthenticationState>

请注意,CascadingAuthenticationState 只能使用一次,而不是像您那样使用。

  1. 将 [授权] 添加到您的后端 api 控制器

  2. 注册者'OnRedirectToLogin',例如

    if (context.Request.Path.Value.StartsWith("/api")) { context.Response.Clear(); context.Response.StatusCode = 401; return Task.FromResult(0); }

  3. 拦截HttpClient

    builder.Services.AddScoped(sp => new HttpClient( sp.GetRequiredService<CustomAuthorizationMessageHandler>()) { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });

  4. `public class CustomAuthorizationMessageHandler : DelegatingHandler { 私有只读 NavigationManager navManager; public CustomAuthorizationMessageHandler(NavigationManager navManager, HttpMessageHandler innerHandler=null) { InnerHandler = InnerHandler = innerHandler ??新的 HttpClientHandler(); this.navManager = 导航管理器; }

     protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
     {
         var response = await base.SendAsync(request, cancellationToken);
         var status = response.StatusCode;
         if (status == HttpStatusCode.Unauthorized) //throw new ApplicationException(status.ToString());
         {
             navManager.NavigateTo("login");
         }
         return response;
     }
    

    }`