在 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
只能使用一次,而不是像您那样使用。
将 [授权] 添加到您的后端 api 控制器
注册者'OnRedirectToLogin',例如
if (context.Request.Path.Value.StartsWith("/api")) { context.Response.Clear(); context.Response.StatusCode = 401; return Task.FromResult(0); }
拦截HttpClient
builder.Services.AddScoped(sp => new HttpClient( sp.GetRequiredService<CustomAuthorizationMessageHandler>()) { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
`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;
}
}`
我有一个 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
只能使用一次,而不是像您那样使用。
将 [授权] 添加到您的后端 api 控制器
注册者'OnRedirectToLogin',例如
if (context.Request.Path.Value.StartsWith("/api")) { context.Response.Clear(); context.Response.StatusCode = 401; return Task.FromResult(0); }
拦截HttpClient
builder.Services.AddScoped(sp => new HttpClient( sp.GetRequiredService<CustomAuthorizationMessageHandler>()) { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
`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; }
}`