如何使用 Blazor Server 对用户进行身份验证

Posted

技术标签:

【中文标题】如何使用 Blazor Server 对用户进行身份验证【英文标题】:How to authenticate a user with Blazor Server 【发布时间】:2020-06-08 12:52:53 【问题描述】:

我有一个使用 MongoDB 作为数据库的 Blazor Server 应用程序,因此我正在尝试使用它来实现身份验证。所以我可以在剃须刀页面中使用<Authenticted>, <AuthorizeView Roles="admin"> 和其他类似的标签。

内置的身份验证模板使用 SQL Server,在这种情况下我不想要它,并且没有一个明确的示例说明如何自己使用另一个数据库进行操作。鉴于微软提供的示例here

using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components.Authorization;

namespace BlazorSample.Services

    public class CustomAuthStateProvider : AuthenticationStateProvider
    
        public override Task<AuthenticationState> GetAuthenticationStateAsync()
        
            var identity = new ClaimsIdentity(new[]
            
                new Claim(ClaimTypes.Name, "mrfibuli"),
            , "Fake authentication type");

            var user = new ClaimsPrincipal(identity);

            return Task.FromResult(new AuthenticationState(user));
        
    

应该如何在应用程序中使用它?您显然不会硬编码单个值和值类型,作为您唯一的身份验证来源。那么应该如何参数化呢?具有本地属性,例如:

Username  get; set; 
UserType  get; set; 

在这种情况下,你会在哪里设置?

然后你将如何使用它来验证用户?我在ConfigurationServices(...)方法下的启动文件中添加了类:

...
services.AddScoped<AuthenticationStateProvider, MongoAuthenticationStateProvider>();
...

我不知道如何验证任何人。我想您可以通过多种方式验证用户名和密码,然后当您知道这很好时,您可以继续更新 .NET 中的身份验证。我正在关注一个教程,他们在后面的代码中提出了类似的建议:

using System;
using System.Linq;
using DocsPlatform.Services;
using System.Threading.Tasks;
using System.Security.Claims;
using Microsoft.AspNetCore.Http;
using System.Collections.Generic;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Components.Authorization;

namespace DocsPlatform.Pages

    public class LoginBase : ComponentBase
    
        [CascadingParameter]
        private Task<AuthenticationState> authStateTask  get; set; 
        protected string username  get; set; 
        protected string password  get; set; 

        protected async Task LoginUser()
        
            bool isValid = true;

            isValid = dbService.ValidateUser(username, password);

            string email = dbService.GetEmail(username);

            var claims = new List<Claim>
            
                new Claim(ClaimTypes.Name, username),
                new Claim(ClaimTypes.Email, email),
            ;

            var claimsIdentity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
            await HttpContext.SignInAsync(
                CookieAuthenticationDefaults.AuthenticationScheme,
                new ClaimsPrincipal(claimsIdentity));

            return NavigationManager.NavigateTo("/");
        
    


但是,返回中的导航不起作用(他们甚至没有解释他们的代码是如何编译的),并且 SignInAsync() 方法在他们展示的方式中不可用。同样,我不知道他们的代码是如何编译的。那么通常如何做到这一点呢?

除了数百个仅使用内置 SQL Server 模板的示例之外,我找不到任何教程、示例等。在哪里可以找到有关如何使用它的详细信息?除了“使用内置模板”或文档链接here 之外的任何内容都将不胜感激,因为它们都没有解释如何执行此操作。

【问题讨论】:

完美的问题说明了这是多么糟糕的记录。我和你在同一个地方。 【参考方案1】:

内置的身份验证模板使用 SQL Server,在这种情况下我不想要它,并且没有明确的示例说明如何自己使用另一个数据库进行操作

我猜你使用的是 ASP.NET Core Identity,对吧?如果您正在寻找使用其他提供商的方式,请参阅official docs

应该如何在应用程序中使用它?您显然不会将单个值和值类型硬编码为唯一的身份验证来源。那么应该如何参数化呢?

由于您使用的是 Blazor Server(而不是 Blazor Wasm),因此您不必自定义 GetAuthenticationStateAsync() 方法,然后手动创建主体。已经有一个内置的ServerAuthenticationStateProvider,它既继承自AuthenticationStateProvider,又实现了IHostEnvironmentAuthenticationStateProvider 接口:

// source code of the built-in ServerAuthenticationStateProvider 
public class ServerAuthenticationStateProvider : AuthenticationStateProvider, IHostEnvironmentAuthenticationStateProvider

    private Task<AuthenticationState> _authenticationStateTask;

    /// <inheritdoc />
    public override Task<AuthenticationState> GetAuthenticationStateAsync()
        => _authenticationStateTask
        ?? throw new InvalidOperationException($"nameof(GetAuthenticationStateAsync) was called before nameof(SetAuthenticationState).");

    /// <inheritdoc />
    public void SetAuthenticationState(Task<AuthenticationState> authenticationStateTask)
    
        _authenticationStateTask = authenticationStateTask ?? throw new ArgumentNullException(nameof(authenticationStateTask));
        NotifyAuthenticationStateChanged(_authenticationStateTask);
    

正如您在上面看到的,GetAuthenticationStateAsync() 将返回由IHostEnvironmentAuthenticationStateProvider 设置的身份验证状态。所以你需要注入一个IHostEnvironmentAuthenticationStateProvider 并调用IHostEnvironmentAuthenticationStateProvider::SetAuthenticationState(...)。最后身份验证状态会自动发送到 Blazor &lt;Authorize/&gt;

其实上面的ServerAuthenticationStateProvider不知道principal是否还有效。所以还有另一个内置的具体类:RevalidatingServerAuthenticationStateProvider

以上代码适用于每种身份验证方案,包括 ASP.NET Core Identity、JwtBearer、AAD 等。您使用什么身份验证方案或使用哪个数据库都没有关系。只需扩展 RevalidatingServerAuthenticationStateProvider 类即可。

例如,如果您使用的是 ASP.NET Core Identity(您可能会看到与 Cookies 相关的问题(请参阅 this thread),它将生成一个 RevalidatingIdentityAuthenticationStateProvider 类使用UserManager&lt;TUser&gt;来验证principal是否有效。

public class RevalidatingIdentityAuthenticationStateProvider<TUser>
    : RevalidatingServerAuthenticationStateProvider where TUser : class

    ...

    protected override async Task<bool> ValidateAuthenticationStateAsync(
        AuthenticationState authenticationState, CancellationToken cancellationToken)
    
        // Get the user manager from a new scope to ensure it fetches fresh data
        // use the UserManager to determine whether the current principal is still valid

由于 ASP.NET Core 标识 不仅限于 SQL Server,RevalidatingIdentityAuthenticationStateProvider 也适用于其他数据库。如果您想使用 MongoDB,请随意创建自定义 MyMongoDbRevalidatingAuthenticationStateProvider

然后你将如何使用它来验证用户

只需像这样声明组件:

<AuthorizeView>
    <Authorized>
        ...
    </Authorized>
    <NotAuthorized>
       ...
    </NotAuthorized>
</AuthorizeView>

如果您使用默认的RevalidatingServerAuthenticationStateProvider,您将手动执行此操作。在 Blazor Server Side 中,身份验证由 AuthenticationMiddleware 完成,然后身份验证状态将自动传递给 &lt;AuthorizeView/&gt;。并且当认证状态过期时,&lt;AuthorizeView/&gt;也会自动更新。

返回中的导航不起作用

实际上,您的代码在导航之前应该会失败:

HttpContext.SignInAsync(
                CookieAuthenticationDefaults.AuthenticationScheme,
                new ClaimsPrincipal(claimsIdentity));

请注意,SignIn() 方法将尝试通过 HTTP 发送 cookie。但是,大多数情况下,连接建立后没有 HTTP。其实我有answered an exactly same question several month ago。


简而言之:

    无论您使用哪种身份验证方案,都可以根据需要实现RevalidatingServerAuthenticationStateProvider,如RevalidatingIdentityAuthenticationStateProvider。 如果您使用的是 ASP.NET Core Identity,则会为您生成一个 RevalidatingIdentityAuthenticationStateProvider。 如果您想使用 ASP.NET Core Identity + MongoDB,请关注official docs 实现这样的功能。 如果要使用 ASP.NET Core Identity + Blazor Server Side,并使用 SignIn(...) 发送 cookie,请不要直接这样做。有关详细信息,请参阅this thread。

【讨论】:

你有关于如何实现你提到的类的项目或示例代码吗?

以上是关于如何使用 Blazor Server 对用户进行身份验证的主要内容,如果未能解决你的问题,请参考以下文章

使用服务器端 Blazor,在连接到 SQL Server 数据库时如何使用 Windows 身份验证模拟用户?

如何通过组织身份验证从 Blazor Server 中的身份获取用户属性

Blazor WebAssembly Server 项目如何引用客户端索引?

Blazor WebAssembly Server 项目如何引用客户端索引?

使用 Cookie 进行 Blazor 服务器端身份验证

如何将 returnUrl 传递到 Blazor Server 应用程序中的登录页面?