Blazor服务器端-在页面开始加载之前进行重定向(如果未经身份验证)

时间:2020-06-02 01:58:36

标签: .net-core blazor blazor-server-side

可能会尝试以错误的方式解决此问题,但这就是这种情况。

我有一个组件,如果用户尝试访问的登录页面未经身份验证,则重定向到该登录页面;如果未授权所请求页面的访问权限,则显示找不到页面。

<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()
    {

    }
}

LoginRedirect是这样的:

    public class LoginRedirect : ComponentBase
    {
        [Inject] protected NavigationManager NavigationManager { get; set; }

        protected override void OnInitialized()
        {
            NavigationManager.NavigateTo("/Login", true);
        }
    }

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

身份验证和授权检查工作正常,但是问题是页面OnInitializedAsync或OnParametersSetAsync在LoginRedirect OnInitialized之前启动。

我在OnInitializedAsync中启动了对数据的调用(调用API,该API使用存储在已登录用户数据中的令牌),这导致它尝试加载数据(没有auth令牌),而不仅仅是重定向。如果我注释掉数据调用,重定向将按预期正常工作而不会出现问题,因此这只是事件发生的时间/顺序。

有解决方案吗?我是否应该将api客户端代码更改为静默失败,而不是在缺少auth令牌的情况下抛出未经授权的异常?

这也是我的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>

2 个答案:

答案 0 :(得分:1)

只需在调用api之前检查您是否有用户:

@inject AuthenticationStateProvider AuthenticationStateProvider

...

@code {
    protected override async Task OnInitializedAsync()
    {
        var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync();
        var user = authState.User;

        if (!user.Identity.IsAuthenticated)
        {
            return;
        }
    }
}

doc

答案 1 :(得分:0)

不熟悉内部的AuthorizeViewWithPermissions,我只能冒险地说LoginRedirect组件应该从NotAuthorized属性元素中公开,因为NotAuthorized传达了没有访问资源权限以及未经身份验证的双重含义。 。您可以通过身份验证,但不能被授权,并且仅在通过身份验证(这是默认设置)的情况下,才能被授权,除非您定义策略以更具体地满足您的要求。

在编码方面,请查看默认VS模板中的编码方式:

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

同样,您发布的代码是不完整的,我只能猜测...但是我认为您的AuthorizeViewWithPermissions的设计存在问题,与我上面提到的有关。尝试以其他方式设计它。不要寻找解决方法,因为从长远来看,这可能会致命。只需根据了解系统的工作原理来更改设计即可。

希望这对您有帮助...