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

Blazor WASM的正确认证与授权方式是什么?

Blazor WASM的正确认证与授权方式是什么?

嘿,刚好我最近也在折腾Blazor WASM的认证授权,结合你提到的用HttpOnly Cookie存储JWT的场景,来分享一套靠谱的实现方案,完全贴合你的需求:

一、API端:正确返回HttpOnly Cookie存储JWT

你已经选对了方向——用HttpOnly Cookie存JWT确实比localStorage安全太多,能直接避免XSS窃取的风险。你的Login接口可以这样完善:

[AllowAnonymous]
[HttpPost("Login")]
public IActionResult Login([FromBody] AccountLoginModel model)
{
    if (!ModelState.IsValid) return BadRequest(ModelState);
    var user = Authenticate(model);
    if (user == null)
        return NotFound("用户名或密码错误");
    
    var token = GenerateToken(user);
    
    // 配置安全的Cookie参数,这几个属性缺一不可
    HttpContext.Response.Cookies.Append(
        "AuthToken", 
        token, 
        new CookieOptions
        {
            HttpOnly = true, // 禁止JS读取,防XSS
            Secure = Environment.IsProduction(), // 生产环境必须开启HTTPS才会发送Cookie
            SameSite = SameSiteMode.Lax, // 防CSRF攻击
            Expires = DateTime.UtcNow.AddHours(1), // 设置Token过期时间
            Path = "/" // 确保整个站点都能访问该Cookie
        });
    
    return Ok(new { UserName = user.UserName, Email = user.Email });
}

同时API端要配置JWT验证,并且从Cookie中读取Token:

// 在Program.cs中配置认证服务
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(options =>
    {
        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuer = true,
            ValidateAudience = true,
            ValidateLifetime = true,
            ValidateIssuerSigningKey = true,
            ValidIssuer = builder.Configuration["Jwt:Issuer"],
            ValidAudience = builder.Configuration["Jwt:Audience"],
            IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"]))
        };
        
        // 核心:从请求Cookie中获取JWT Token
        options.Events = new JwtBearerEvents
        {
            OnMessageReceived = context =>
            {
                context.Token = context.Request.Cookies["AuthToken"];
                return Task.CompletedTask;
            }
        };
    });

// 别忘了开启授权中间件
app.UseAuthentication();
app.UseAuthorization();

另外CORS配置必须允许携带凭据,否则Blazor客户端的请求不会带上Cookie:

builder.Services.AddCors(options =>
{
    options.AddPolicy("BlazorClientCors", policy =>
    {
        policy.WithOrigins("https://your-blazor-client-domain.com") // 替换成你的客户端地址
              .AllowAnyHeader()
              .AllowAnyMethod()
              .AllowCredentials(); // 这个是关键,允许Cookie跨域传递
    });
});

// 在中间件中启用CORS
app.UseCors("BlazorClientCors");

二、Blazor WASM端:处理认证状态与请求凭据

Blazor WASM是纯客户端应用,默认不会自动在请求中带上Cookie,也无法直接读取HttpOnly Cookie,所以需要做以下配置:

  1. 配置HttpClient携带凭据
    在客户端的Program.cs中,给HttpClient加上允许携带凭据的配置:
builder.Services.AddScoped(sp => new HttpClient
{
    BaseAddress = new Uri(builder.HostEnvironment.BaseAddress)
}).AddHttpMessageHandler<BaseAddressAuthorizationMessageHandler>();

// 开启授权服务
builder.Services.AddAuthorizationCore();
// 添加自定义认证状态提供者
builder.Services.AddScoped<AuthenticationStateProvider, CustomAuthStateProvider>();
  1. 自定义AuthenticationStateProvider
    因为客户端拿不到HttpOnly Cookie,所以需要调用API的一个端点(比如/api/Account/Me)来获取当前用户的身份信息,以此构建认证状态:
public class CustomAuthStateProvider : AuthenticationStateProvider
{
    private readonly HttpClient _httpClient;

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

    public override async Task<AuthenticationState> GetAuthenticationStateAsync()
    {
        try
        {
            // 调用API获取当前用户信息
            var response = await _httpClient.GetFromJsonAsync<UserInfoDto>("api/Account/Me");
            if (response != null)
            {
                // 构建用户Claims
                var claims = new List<Claim>
                {
                    new Claim(ClaimTypes.Name, response.UserName),
                    new Claim(ClaimTypes.Email, response.Email)
                    // 可以添加更多角色、权限等Claims
                };
                var identity = new ClaimsIdentity(claims, "CookieAuth");
                var user = new ClaimsPrincipal(identity);
                return new AuthenticationState(user);
            }
        }
        catch (HttpRequestException)
        {
            // 如果请求失败(比如Token过期),返回未认证状态
        }

        return new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity()));
    }

    // 提供一个方法手动刷新认证状态,比如登录/登出后调用
    public void NotifyAuthenticationStateChanged()
    {
        NotifyAuthenticationStateChanged(GetAuthenticationStateAsync());
    }
}

对应的API端/Me端点:

[HttpGet("Me")]
[Authorize]
public IActionResult GetCurrentUser()
{
    var user = User;
    return Ok(new UserInfoDto
    {
        UserName = user.Identity.Name,
        Email = user.FindFirstValue(ClaimTypes.Email)
    });
}
  1. 在组件中使用授权
    现在你可以在Blazor页面/组件中正常使用授权功能了:
  • 用<AuthorizeView>组件根据认证状态显示不同内容:
<AuthorizeView>
    <Authorized>
        <p>欢迎你,@context.User.Identity.Name!</p>
        <button @onclick="Logout">登出</button>
    </Authorized>
    <NotAuthorized>
        <p>请先登录</p>
        <NavLink href="/login">前往登录</NavLink>
    </NotAuthorized>
</AuthorizeView>
  • 用[Authorize]属性装饰路由页面,限制未认证用户访问:
[Authorize]
public class DashboardBase : ComponentBase
{
    // 页面逻辑
}

三、额外注意事项

  • 必须使用HTTPS:生产环境下Cookie的Secure属性必须开启,只有HTTPS请求才会携带Cookie,避免明文传输。
  • CSRF防护:如果API和客户端是同一域名,SameSite=Lax已经足够;如果是跨域,建议额外添加CSRF Token验证。
  • Token刷新:可以在API端实现刷新Token的逻辑,当用户访问时如果Token即将过期,自动返回新的Token到Cookie中,Blazor端通过CustomAuthStateProvider刷新认证状态即可。

备注:内容来源于stack exchange,提问作者Riley R Arbin

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.17 10:48:15