.net core Blazor服务器端-如果在页面开始加载之前未经验证,则重定向
可能试图用错误的方法解决这个问题,但情况是这样的 我设计了一个组件,用于在试图访问它的用户未经身份验证时重定向到登录,如果他们未获得请求页面的授权,则显示not found.net core Blazor服务器端-如果在页面开始加载之前未经验证,则重定向,.net-core,blazor,blazor-server-side,.net Core,Blazor,Blazor Server Side,可能试图用错误的方法解决这个问题,但情况是这样的 我设计了一个组件,用于在试图访问它的用户未经身份验证时重定向到登录,如果他们未获得请求页面的授权,则显示not found <AuthorizeViewWithPermissions RequiredPermission="RequiredPermission"> <Authorized> @ChildContent </Authorized> <NotAuthen
<AuthorizeViewWithPermissions RequiredPermission="RequiredPermission">
<Authorized>
@ChildContent
</Authorized>
<NotAuthenticated>
<LoginRedirect />
</NotAuthenticated>
<NotAuthorized>
<NotFoundRedirect />
</NotAuthorized>
</AuthorizeViewWithPermissions>
@code {
[Parameter] public RenderFragment ChildContent { get; set; }
[Parameter] public Permissions RequiredPermission { get; set; }
protected override void OnInitialized()
{
}
}
AuthorizeViewWithPermissions的内部结构:
/// <summary>
/// Largely borrowed from the original AuthorizeView, but cut up a bit to use custom permissions and cut out a lot of stuff that isn't needed.
/// </summary>
public class AuthorizeViewWithPermissions : ComponentBase
{
private AuthenticationState _currentAuthenticationState;
private bool _isAuthorized;
private bool _isAuthenticated;
/// <summary>
/// The permission type required to display the content
/// </summary>
[Parameter] public Permissions RequiredPermission { get; set; }
/// <summary>
/// The content that will be displayed if the user is authorized.
/// </summary>
[Parameter] public RenderFragment<AuthenticationState> ChildContent { get; set; }
/// <summary>
/// The content that will be displayed if the user is not authorized.
/// </summary>
[Parameter] public RenderFragment<AuthenticationState> NotAuthorized { get; set; }
/// <summary>
/// The content that will be displayed if the user is not authenticated.
/// </summary>
[Parameter] public RenderFragment<AuthenticationState> NotAuthenticated { get; set; }
/// <summary>
/// The content that will be displayed if the user is authorized.
/// If you specify a value for this parameter, do not also specify a value for <see cref="ChildContent"/>.
/// </summary>
[Parameter] public RenderFragment<AuthenticationState> Authorized { get; set; }
/// <summary>
/// The content that will be displayed while asynchronous authorization is in progress.
/// </summary>
[Parameter] public RenderFragment Authorizing { get; set; }
/// <summary>
/// The resource to which access is being controlled.
/// </summary>
[Parameter] public object Resource { get; set; }
[CascadingParameter] private Task<AuthenticationState> AuthenticationState { get; set; }
/// <inheritdoc />
protected override void BuildRenderTree(RenderTreeBuilder builder)
{
// We're using the same sequence number for each of the content items here
// so that we can update existing instances if they are the same shape
if (_currentAuthenticationState == null)
{
builder.AddContent(0, Authorizing);
}
else if (_isAuthorized)
{
var authorized = Authorized ?? ChildContent;
builder.AddContent(0, authorized?.Invoke(_currentAuthenticationState));
}
else if (!_isAuthenticated)
{
builder.AddContent(0, NotAuthenticated?.Invoke(_currentAuthenticationState));
}
else
{
builder.AddContent(0, NotAuthorized?.Invoke(_currentAuthenticationState));
}
}
/// <inheritdoc />
protected override async Task OnParametersSetAsync()
{
// We allow 'ChildContent' for convenience in basic cases, and 'Authorized' for symmetry
// with 'NotAuthorized' in other cases. Besides naming, they are equivalent. To avoid
// confusion, explicitly prevent the case where both are supplied.
if (ChildContent != null && Authorized != null)
{
throw new InvalidOperationException($"Do not specify both '{nameof(Authorized)}' and '{nameof(ChildContent)}'.");
}
if (AuthenticationState == null)
{
throw new InvalidOperationException($"Authorization requires a cascading parameter of type Task<{nameof(AuthenticationState)}>. Consider using {typeof(CascadingAuthenticationState).Name} to supply this.");
}
// First render in pending state
// If the task has already completed, this render will be skipped
_currentAuthenticationState = null;
// Then render in completed state
// Importantly, we *don't* call StateHasChanged between the following async steps,
// otherwise we'd display an incorrect UI state while waiting for IsAuthorizedAsync
_currentAuthenticationState = await AuthenticationState;
SetAuthorizedAndAuthenticated(_currentAuthenticationState.User);
}
private void SetAuthorizedAndAuthenticated(ClaimsPrincipal user)
{
var userWithData = SessionHelper.GetCurrentUser(user);
_isAuthenticated = userWithData != null;
_isAuthorized = userWithData?.Permissions.Any(p => p == RequiredPermission || p == Permissions.SuperAdmin) ?? false;
}
}
//
///很大程度上借鉴了最初的AuthorizeView,但减少了一点使用自定义权限,并减少了许多不需要的内容。
///
公共类AuthorizeViewWithPermissions:ComponentBase
{
私有身份验证状态\u currentAuthenticationState;
私人住宅获得授权;
私人住宅已通过认证;
///
///显示内容所需的权限类型
///
[参数]所需的公共权限权限权限{get;set;}
///
///如果用户获得授权,将显示的内容。
///
[参数]公共RenderFragment ChildContent{get;set;}
///
///如果用户未经授权,将显示的内容。
///
[参数]公共RenderFragment未授权{get;set;}
///
///如果用户未经身份验证,将显示的内容。
///
[参数]公共RenderFragment未经身份验证{get;set;}
///
///如果用户获得授权,将显示的内容。
///如果为此参数指定了值,请不要同时为其指定值。
///
[参数]公共RenderFragment授权{get;set;}
///
///正在进行异步授权时将显示的内容。
///
[参数]授权{get;set;}的公共呈现片段
///
///正在控制对其访问的资源。
///
[参数]公共对象资源{get;set;}
[CascadingParameter]专用任务身份验证状态{get;set;}
///
受保护的覆盖void BuildRenderTree(RenderTreeBuilder)
{
//我们在这里为每个内容项使用相同的序列号
//因此,如果现有实例的形状相同,我们可以更新它们
if(_currentAuthenticationState==null)
{
builder.AddContent(0,授权);
}
否则,如果(_已授权)
{
var authorized=authorized×ChildContent;
builder.AddContent(0,authorized?.Invoke(_currentAuthenticationState));
}
否则,如果(!\u已验证)
{
builder.AddContent(0,未验证?.Invoke(_currentAuthenticationState));
}
其他的
{
builder.AddContent(0,未授权?.Invoke(_currentAuthenticationState));
}
}
///
受保护的重写异步任务OnParametersSetAsync()
{
//在基本情况下,我们允许“ChildContent”为方便起见,允许“Authorized”为对称
//在其他情况下使用“NotAuthorized”。除了命名之外,它们是等效的。为了避免
//混淆,明确防止两者都提供的情况。
if(ChildContent!=null&&Authorized!=null)
{
抛出新的invalidoOperationException($“不要同时指定{nameof(Authorized)}和{nameof(ChildContent)}.”);
}
if(AuthenticationState==null)
{
抛出新的无效操作异常($)授权需要一个类型任务的级联参数。考虑使用{Type(CasCADQualIdCurtCurtStand).Name }来提供这个。
}
//第一次呈现处于挂起状态
//如果任务已完成,将跳过此渲染
_currentAuthenticationState=null;
//然后以完成状态渲染
//重要的是,我们*不*调用状态在以下异步步骤之间发生了更改,
//否则,在等待IsAuthorizedAsync时,我们将显示不正确的UI状态
_currentAuthenticationState=等待AuthenticationState;
SetAuthorizedAuthenticated(_currentAuthenticationState.User);
}
私有无效SetAuthorizedAuthenticated(ClaimsPrincipal用户)
{
var userWithData=SessionHelper.GetCurrentUser(用户);
_isAuthenticated=userWithData!=null;
_isAuthorized=userWithData?.Permissions.Any(p=>p==RequiredPermission | | p==Permissions.SuperAdmin)?false;
}
}
身份验证和授权检查工作正常,但问题是在LoginDirect OnInitializedAsync或OnParametersSetAsync启动之前,页面OnInitializedAsync启动 我开始在OnInitializedAsync中调用数据(到API,使用登录用户数据上存储的令牌),这会导致它尝试加载数据(没有身份验证令牌),而不仅仅是重定向。如果我注释掉数据调用,重定向将按预期工作,没有问题,因此它只是一个时间/事件序列问题 有解决办法吗?如果缺少身份验证令牌,我是否应该将api客户端代码更改为静默失败,而不是引发未经授权的异常 这也是我的app.razor组件:
CascadingAuthenticationState>
<Router AppAssembly="@typeof(Program).Assembly">
<Found Context="routeData">
<AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)">
<NotAuthorized>
<LoginRedirect />
</NotAuthorized>
<Authorizing>
<p>Checking authorization...</p>
</Authorizing>
</AuthorizeRouteView>
</Found>
<NotFound>
<LayoutView Layout="@typeof(MainLayout)">
<p>These aren't the droids you're looking for.</p>
</LayoutView>
</NotFound>
</Router>
</CascadingAuthenticationState>
CascadingAuthenticationState>
正在检查授权
这些不是你要找的机器人
只需在调用api之前检查是否有用户:
CascadingAuthenticationState>
<Router AppAssembly="@typeof(Program).Assembly">
<Found Context="routeData">
<AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)">
<NotAuthorized>
<LoginRedirect />
</NotAuthorized>
<Authorizing>
<p>Checking authorization...</p>
</Authorizing>
</AuthorizeRouteView>
</Found>
<NotFound>
<LayoutView Layout="@typeof(MainLayout)">
<p>These aren't the droids you're looking for.</p>
</LayoutView>
</NotFound>
</Router>
</CascadingAuthenticationState>
<AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)">
<NotAuthorized>
@if (!context.User.Identity.IsAuthenticated)
{
<RedirectToLogin />
}
else
{
<p>You are not authorized to access this resource.</p>
}
</NotAuthorized>
</AuthorizeRouteView>