You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Blazor/.NET Core中创建ClaimsPrincipal并同步至WASM与API

解决方案:Blazor WASM + ASP.NET Core 宿主自定义认证适配现有存储

针对你的场景,核心问题在于Blazor WASM是客户端应用,和服务端的认证状态需要通过「持久化身份凭证+客户端状态同步」来打通,你之前的尝试混淆了服务端和客户端的AuthenticationStateProvider职责,下面是具体可落地的方案:

一、核心思路

  1. 服务端:用Cookie或JWT完成身份持久化,验证密码后生成合法的ClaimsPrincipal并绑定到请求上下文
  2. 客户端:实现自定义AuthenticationStateProvider,通过API获取当前用户身份或解析本地凭证,同步UI的认证状态
  3. 双向打通:API控制器通过[Authorize]自动识别服务端的ClaimsPrincipal;WASM UI通过自定义认证状态提供者感知用户身份,支持[Authorize]特性和权限控制

二、具体实现步骤

1. 服务端配置(ASP.NET Core 宿主)

1.1 配置Cookie认证(适合同域场景,无需额外存储token)

在Program.cs中添加认证授权服务:

using Microsoft.AspNetCore.Authentication.Cookies;

var builder = WebApplication.CreateBuilder(args);

// 添加Cookie认证
builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
    .AddCookie(options =>
    {
        options.Cookie.HttpOnly = true; // 防止XSS
        options.Cookie.SameSite = SameSiteMode.Lax;
        options.Cookie.SecurePolicy = CookieSecurePolicy.Always; // 生产环境强制HTTPS
        options.ExpireTimeSpan = TimeSpan.FromHours(8);
        options.LoginPath = "/api/account/login";
    });

builder.Services.AddAuthorization();
builder.Services.AddControllersWithViews();
builder.Services.AddRazorPages();

var app = builder.Build();

// 启用认证授权中间件(顺序不能错)
app.UseAuthentication();
app.UseAuthorization();

// 其他中间件配置...
app.Run();

1.2 实现登录接口(AccountController)

复用你已有的密码验证逻辑,验证通过后生成ClaimsPrincipal并完成Cookie登录:

[ApiController]
[Route("api/account")]
public class AccountController : ControllerBase
{
    private readonly IUserStore _userStore; // 你的自定义用户存储服务

    public AccountController(IUserStore userStore)
    {
        _userStore = userStore;
    }

    [HttpPost("login")]
    public async Task<IActionResult> Login([FromBody] LoginRequest request)
    {
        // 1. 复用旧逻辑验证用户名密码
        var user = await _userStore.GetUserByUsername(request.Username);
        if (user == null || !VerifyPassword(request.Password, user.HashedPassword))
        {
            return Unauthorized("用户名或密码错误");
        }

        // 2. 创建ClaimsPrincipal
        var claims = new List<Claim>
        {
            new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
            new Claim(ClaimTypes.Name, user.Username),
            new Claim(ClaimTypes.Role, user.Role) // 适配你的权限字段
            // 添加其他业务需要的Claims
        };
        var identity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
        var principal = new ClaimsPrincipal(identity);

        // 3. 写入Cookie,完成服务端登录
        await HttpContext.SignInAsync(
            CookieAuthenticationDefaults.AuthenticationScheme,
            principal,
            new AuthenticationProperties { IsPersistent = request.RememberMe });

        // 4. 返回用户基础信息,供客户端同步状态
        return Ok(new UserInfoDto
        {
            UserId = user.Id.ToString(),
            Username = user.Username,
            Role = user.Role
        });
    }

    // 供客户端获取当前用户身份的接口
    [HttpGet("current-user")]
    [Authorize]
    public IActionResult GetCurrentUser()
    {
        // 从HttpContext.User中读取Claims
        var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
        var username = User.FindFirstValue(ClaimTypes.Name);
        var role = User.FindFirstValue(ClaimTypes.Role);

        return Ok(new UserInfoDto { UserId = userId, Username = username, Role = role });
    }

    // 密码验证逻辑(复用你的旧代码)
    private bool VerifyPassword(string inputPassword, string hashedPassword)
    {
        // 示例:用BCrypt验证,替换成你的旧哈希验证逻辑
        return BCrypt.Net.BCrypt.Verify(inputPassword, hashedPassword);
    }
}

// 辅助DTO
public class LoginRequest
{
    public string Username { get; set; }
    public string Password { get; set; }
    public bool RememberMe { get; set; }
}

public class UserInfoDto
{
    public string UserId { get; set; }
    public string Username { get; set; }
    public string Role { get; set; }
}

2. 客户端配置(Blazor WASM)

2.1 实现自定义AuthenticationStateProvider

WASM无法直接读取HttpOnly Cookie,所以需要通过API获取用户身份,同步UI的认证状态:

using System.Security.Claims;
using System.Net.Http.Json;

public class CustomAuthStateProvider : AuthenticationStateProvider
{
    private readonly HttpClient _httpClient;
    private readonly ClaimsPrincipal _anonymousUser = new(new ClaimsIdentity());

    public CustomAuthStateProvider(HttpClient httpClient)
    {
        _httpClient = httpClient;
    }

    // 核心方法:返回当前认证状态
    public override async Task<AuthenticationState> GetAuthenticationStateAsync()
    {
        try
        {
            // 调用服务端接口获取当前用户信息
            var response = await _httpClient.GetAsync("api/account/current-user");
            if (response.IsSuccessStatusCode)
            {
                var userInfo = await response.Content.ReadFromJsonAsync<UserInfoDto>();
                if (userInfo != null)
                {
                    // 构建客户端的ClaimsPrincipal
                    var claims = new List<Claim>
                    {
                        new(ClaimTypes.NameIdentifier, userInfo.UserId),
                        new(ClaimTypes.Name, userInfo.Username),
                        new(ClaimTypes.Role, userInfo.Role)
                    };
                    var identity = new ClaimsIdentity(claims, "CookieAuth");
                    var principal = new ClaimsPrincipal(identity);

                    return new AuthenticationState(principal);
                }
            }
        }
        catch (Exception)
        {
            // 网络错误或未登录时返回匿名用户
        }

        return new AuthenticationState(_anonymousUser);
    }

    // 登录成功后手动触发状态更新
    public void NotifyAuthStateChanged()
    {
        NotifyAuthenticationStateChanged(GetAuthenticationStateAsync());
    }
}

2.2 注册自定义认证服务

在WASM项目的Program.cs中替换默认的AuthenticationStateProvider:

var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("#app");
builder.RootComponents.Add<HeadOutlet>("head::after");

builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });

// 注册自定义认证状态提供者
builder.Services.AddScoped<AuthenticationStateProvider, CustomAuthStateProvider>();
// 添加授权服务
builder.Services.AddAuthorizationCore();

await builder.Build().RunAsync();

2.3 实现WASM登录组件

登录成功后触发认证状态更新,让UI感知用户已登录:

@page "/login"
@inject HttpClient Http
@inject AuthenticationStateProvider AuthStateProvider
@inject NavigationManager NavManager

<h3>登录</h3>

<div class="mb-3">
    <label>用户名</label>
    <input @bind="@Username" class="form-control" />
</div>
<div class="mb-3">
    <label>密码</label>
    <input type="password" @bind="@Password" class="form-control" />
</div>
<div class="mb-3">
    <input type="checkbox" @bind="@RememberMe" /> 记住我
</div>
<button @onclick="HandleLogin" class="btn btn-primary">登录</button>
@if (!string.IsNullOrEmpty(ErrorMessage))
{
    <div class="text-danger mt-2">@ErrorMessage</div>
}

@code {
    private string Username { get; set; }
    private string Password { get; set; }
    private bool RememberMe { get; set; }
    private string ErrorMessage { get; set; }

    private async Task HandleLogin()
    {
        ErrorMessage = null;
        var request = new LoginRequest { Username = Username, Password = Password, RememberMe = RememberMe };
        
        var response = await Http.PostAsJsonAsync("api/account/login", request);
        if (response.IsSuccessStatusCode)
        {
            // 通知认证状态更新,UI会自动刷新[Authorize]组件
            ((CustomAuthStateProvider)AuthStateProvider).NotifyAuthStateChanged();
            NavManager.NavigateTo("/");
        }
        else
        {
            ErrorMessage = await response.Content.ReadAsStringAsync();
        }
    }
}

3. 验证授权效果

  • API控制器:在需要授权的控制器/方法上添加[Authorize]特性,User属性会自动绑定到当前ClaimsPrincipal:
    [ApiController]
    [Route("api/admin")]
    [Authorize(Roles = "Admin")]
    public class AdminController : ControllerBase
    {
        [HttpGet("stats")]
        public IActionResult GetAdminStats()
        {
            var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
            return Ok($"管理员 {userId} 查看统计数据");
        }
    }
    
  • WASM UI:使用[Authorize]特性控制组件显示,或通过AuthenticationStateProvider获取用户身份:
    <AuthorizeView Roles="Admin">
        <Authorized>
            <p>欢迎管理员 @context.User.Identity.Name</p>
            <NavLink href="/admin">管理后台</NavLink>
        </Authorized>
        <NotAuthorized>
            <p>您没有管理员权限</p>
        </NotAuthorized>
    </AuthorizeView>
    

三、常见问题说明

  1. 为什么之前的方法无效?
    • 服务端的AuthenticationStateProvider只对Blazor Server有效,WASM的认证状态由客户端的提供者管理
    • SignInManager是ASP.NET Core Identity专属组件,不用Identity的话无法兼容
    • 直接设置HttpContext.User仅对当前请求有效,必须通过Cookie/JWT持久化身份
  2. 如果想用JWT代替Cookie?
    • 服务端配置JwtBearer认证,登录接口返回JWT token
    • 客户端将token存储在localStorage,自定义AuthenticationStateProvider解析token生成ClaimsPrincipal
    • 每次API请求在Header中携带Authorization: Bearer {token}

内容的提问来源于stack exchange,提问作者Jeff Whitty

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.07 18:15:42