.net core Blazor服务器端-如果在页面开始加载之前未经验证,则重定向

.net core Blazor服务器端-如果在页面开始加载之前未经验证,则重定向,.net-core,blazor,blazor-server-side,.net Core,Blazor,Blazor Server Side,可能试图用错误的方法解决这个问题,但情况是这样的 我设计了一个组件,用于在试图访问它的用户未经身份验证时重定向到登录,如果他们未获得请求页面的授权,则显示not found <AuthorizeViewWithPermissions RequiredPermission="RequiredPermission"> <Authorized> @ChildContent </Authorized> <NotAuthen

可能试图用错误的方法解决这个问题,但情况是这样的

我设计了一个组件,用于在试图访问它的用户未经身份验证时重定向到登录,如果他们未获得请求页面的授权,则显示not found

<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>