如果用户未经身份验证,则Blazor重定向到登录页面。

52

我正在尝试使用Blazor WebAssembly开发应用程序,我想知道如何保护整个应用程序以防用户未经身份验证。我要实现的行为是:

  • 如果匿名用户请求任何页面,则将重定向到登录页面

更好的做法:

  • 必须经过身份验证才能使用此应用程序

目前,我已经通过将[Authorize]属性应用于每个页面来实现此行为,但我希望将其集中化。

在Blazor Server Side上,我通过在_host.razor组件中应用[Authorize]属性来实现了这个目标。

是否有解决方案适用于Blazor Client Side?


如果有人正在寻找完整的答案,请查看此链接:https://dev59.com/_rnoa4cB1Zd3GeqPKBq4#74721678 - Ash K
11个回答

60

可能有更加简洁的方法,但这是对我有效的方法:

假设您已经按照这些说明正确地配置了身份验证

在您的 MainLayout.razor 文件中(默认用于所有组件),添加以下 code 块:

@inject NavigationManager NavigationManager
@code{ 

    [CascadingParameter] protected Task<AuthenticationState> AuthStat { get; set; }

    protected async override Task OnInitializedAsync()
    {
        base.OnInitialized();
        var user = (await AuthStat).User;
        if(!user.Identity.IsAuthenticated)
        {
            NavigationManager.NavigateTo($"authentication/login?returnUrl={Uri.EscapeDataString(NavigationManager.Uri)}");
        }
    }
}
如果用户未通过身份验证,我们将重定向到内置的RemoteAuthenticatorView组件,并使用“login”操作在“authentication/”端点上启动身份验证。这应该启动身份验证。

13
你需要使用 @inject NavigationManager NavigationManager,否则会出现 CS0120: An object reference is required for the non-static field, method, or property 'NavigationManager.Uri' 的错误。 - Arsinclair
5
另外一件事是,NavigationManager.Uri返回完全限定的URL,但如果使用AspNet Identity作为身份验证提供程序,它会想要将您重定向到本地URL,使用LocalRedirect(returnUrl);。因此,它需要一个本地URL,可以使用NavigationManager.ToBaseRelativePath(NavigationManager.Uri)获取。 - Arsinclair
1
谢谢@Arsinclair - 你的调整让我成功了。我将它封装在一个组件中,现在它运行得很好。https://github.com/Webreaper/Damselfly/blob/develop/Damselfly.Web/Shared/LoginForcer.razor - Webreaper

35

基本上,要在 BlazorApp.Client 中为所有页面应用授权,您需要添加:

@attribute [Microsoft.AspNetCore.Authorization.Authorize]

将内容添加到您的_Imports.razor文件中。

此外,您还可以添加:

@attribute [Microsoft.AspNetCore.Authorization.AllowAnonymous]

...在不需要授权的页面上。

另外,如果你想将用户重定向到任何页面,这是我想出来的一个方法:

<NotAuthorized>
    @{ navMan.NavigateTo("login"); }
</NotAuthorized>

...其中navMan是注入的NavigationManager实例。在这里,如果用户尝试访问仅限授权用户的页面,我将重定向他们到我的Login.razor页面。


1
Blazor wasm目前最好的重定向到登录页面的方法还是使用<NotAuthorized>吗? - Benni
14
条件语句是冗余的。只需要使用以下代码: <NotAuthorized>@{ _navigationManager.NavigateTo("/login"); }</NotAuthorized> - ssamko
1
@ssamko 是的,这是一个更好的陈述。 - mend0k
@mend0k,你能否编辑你的原始帖子以包含ssamko的建议?此外,你能否指定<NotAuthorized>块放在哪里(因为它不应该放在_Imports.razor中)。 - Naughton

16

我尝试了@Brett的解决方案,它起作用了,但在重定向回用户来自的页面时,它最终显示“授权中...”、“检查登录状态...”然后是“完成登录...”,并卡在那里。然后用户必须点击链接或手动键入先前的URL才能返回。

enter image description here

enter image description here

enter image description here

微软现在有文档介绍“要求整个应用程序进行授权”。

https://learn.microsoft.com/en-us/aspnet/core/blazor/security/webassembly/?view=aspnetcore-5.0#require-authorization-for-the-entire-app

根据文档,您可以选择以下方式之一:

  • _Imports.razor文件中使用Authorize属性指令:
@using Microsoft.AspNetCore.Authorization
@attribute [Authorize]
  • 将Authorize特性添加到Pages文件夹中的每个Razor组件中。

我将代码添加到_Imports.razor,但是内容变成了一个白屏:

enter image description here

然后我发现https://localhost:44123/authentication/login也给了我一个白屏,这个通常会指向Shared\RedirectToLogin.razor。 然后我在Pages\Authentication.razor中添加了@attribute [AllowAnonymous],然后一切都按预期工作了,我没有卡住。

enter image description here

通过这种解决方案,我还可以看到默认的You are logged out.消息。

enter image description here


非常感谢,这帮助我解决了问题。 - Schoof

10
你可以创建自己的重定向组件 LoginRedirect.razor
@attribute [AllowAnonymous]
@inject NavigationManager _navigationManager

@code {
    protected override void OnInitialized()
    {
        _navigationManager.NavigateTo("/login");
    }
}

然后在App.razor中像这样使用它:
<NotAuthorized>
    <LoginRedirect />
</NotAuthorized>

不要忘记根据你的项目结构,在App.razor的顶部添加using到那个组件。
@using SolutionName.ProjectName.Pages.MyFolder;

编辑:

看起来你不需要创建重定向组件,而是可以直接使用页面,例如:

<NotAuthorized>
    <LoginPage />
</NotAuthorized>

Microsoft.AspNetCore.Components.NavigationException gets thrown on _navigationManager.NavigateTo("/login"); - iggy12345
1
@iggy12345 试着将它放到 OnAfterRender() 中,而不是 OnInitialized() - ssamko

3

如果您的Blazor WASM托管在.net .Server项目中(解决方案中有3个项目:.Client、.Server和.Shared),请将.RequireAuthorization添加到以下位置:

.Server/Program.cs(而不是.Client项目中的那一个)

app.MapFallbackToFile("index.html").RequireAuthorization(); 
// redirect to login page BEFORE loading Blazor WASM page if auth is missing,
// use when ALL Blazor pages require autentication
// and you don't handle login screens etc. in Blazor but in the backend

您可以将您的``标签包含在`app.razor`文件中,并添加自定义的`LoginRedirect`组件,建议由@ssamko提供,但请确保如果身份验证URL由.Server应用程序处理,则使用强制重定向。

app.razor:

@using Microsoft.AspNetCore.Components.Authorization
@using YourBlazor.Client.Components

<CascadingAuthenticationState>
<AuthorizeView>
    <Authorized>
        Current user: @context.User.Identity.Name
            <Router AppAssembly="@typeof(App).Assembly">
                <Found Context="routeData">
                    <AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
                    <FocusOnNavigate RouteData="@routeData" Selector="h1" />
                </Found>
                <NotFound>
                    <PageTitle>Not found</PageTitle>
                    <LayoutView Layout="@typeof(MainLayout)">
                        <p role="alert">Sorry, there's nothing at this address.</p>
                    </LayoutView>
                </NotFound>
            </Router>
    </Authorized>
    <NotAuthorized>
        <LoginRedirect />
    </NotAuthorized>
</AuthorizeView>

组件/LoginRedirect.razor(注意forceLoad:true);

@using System.Web
@attribute [Microsoft.AspNetCore.Authorization.AllowAnonymous]
@inject NavigationManager _navigationManager

@code {
    protected override void OnInitialized()
    {
        _navigationManager.NavigateTo("/authentication/login?returnUrl=" + HttpUtility.UrlEncode(_navigationManager.ToBaseRelativePath(_navigationManager.Uri)), forceLoad:true);
        // /authentication controller is handled by hosted .Server project 
    }
}

3
在我熟悉Blazor的过程中,我正在跟随一份教程学习,作者提供了一种解决这个问题的好方法。在Blazor中,似乎所有东西都是组件,你的登录页面可能也是一个组件。至少在教程中是这样。所以他所做的就是这样:
<NotAuthorized>
    <Login />
</NotAuthorized>

当然,您需要将正确的使用方法添加到您的登录组件中。

这种解决方案的缺点是,URL与您未登录时看到的页面不匹配。


2
只需将以下行添加到客户端的 ..pages/Index.razor 文件中即可。
@using Microsoft.AspNetCore.Authorization
@attribute [Authorize]

这将要求在启动时对索引页面进行授权,并根据App.Razor中使用的RedirectToLogin组件强制进行登录重定向。

2
您可以利用<Router>组件的<NotAuthorizedContent>模板,如这里所述,来定制未经授权用户的显示内容。
<CascadingAuthenticationState>
    <Router AppAssembly="typeof(Startup).Assembly">
        <NotFoundContent>
            <p>Sorry, there's nothing at this address.</p>
        </NotFoundContent>
        <NotAuthorizedContent>
            <h1>Sorry</h1>
            <p>You're not authorized to reach this page. You may need to log in as a different user.</p>
        </NotAuthorizedContent>
        <AuthorizingContent>
            <p>Please wait...</p>
        </AuthorizingContent>
    </Router>
</CascadingAuthenticationState>

<NotAuthorizedContent>的内容替换为一个名为RedirectToLogin的自定义组件,其OnInitializedAsync检查用户是否已登录,如果未登录,则进行重定向。


4
现在画出猫头鹰的其余部分。 - Webreaper

1

直接使用asp.net core身份验证登录页面

以下解决方案可能不能满足所有需求。在这里,您需要为自己的登录制作自己的razor组件,而不是使用asp.net core的身份验证页面。

<NotAuthorized>
    <Login />
</NotAuthorized>

更好的方法是使用来自asp.net core的方法。问题在于,如果您没有检查NavigationManager,就会出现异常。以下是不会出现异常的解决方案。
<NotAuthorized>
    @{
        try
        {
            var hasJSRuntime = navManager != null && (navManager.GetType()?.GetProperty("HasAttachedJSRuntime") != null ? 
                (bool)navManager.GetType().GetProperty("HasAttachedJSRuntime").GetValue(navManager) : true);
        
            if (hasJSRuntime)
            {
                navManager.NavigateTo("/Identity/Account/Login", true);
            }

        }
        catch
        {
                <NavLink href="Identity/Account/Login">
                    Redirect to Login
                </NavLink>
        }
    }
</NotAuthorized>

1
在Blazor Server中,我们可以通过JavaScript Interop实现以下功能:
在_Layout.cshtml中添加JS代码:
    window.forwardToLoginPage = () =>{
        window.location = "MicrosoftIdentity/Account/SignIn"
    };
 

创建一个名为 ForwardToLogin 的组件。
@inject IJSRuntime JS

@code {

  protected override async Task OnAfterRenderAsync()
  {
      await ForwardToLoginPage();
  }

  private async Task ForwardToLoginPage()
  {
      await JS.InvokeVoidAsync("forwardToLoginPage");
  }
}

更新 App.razor 文件:

 <AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" >
       <NotAuthorized>
             <ForwardToLogin /> 
         </NotAuthorized>    
 </AuthorizeRouteView>

现在,每当未经授权的用户尝试访问带有 [Authorize] 属性的页面时,他将自动重定向到登录页面。请注意,用户不会被重定向回原始页面。


对我来说它不起作用。我得到了:InvalidOperationException: JavaScript互操作调用此时无法发出。这是因为组件正在静态呈现。启用预呈现时,JavaScript互操作调用只能在OnAfterRenderAsync生命周期方法期间执行。-从await JS.InvokeVoidAsync("forwardToLoginPage")调用。 - JClarkCDS
1
@JClarkCDS,我更新了示例,使用OnAfterRenderAsync()应该可以解决你遇到的问题。 - Greg Gum

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接