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

Blazor Server中JWT认证实现:结合内置特性管理认证状态

Blazor Server 集成第三方JWT API并支持[Authorize]特性实现方案

一、配置认证服务(Program.cs)

首先在Program.cs中配置认证体系,结合Cookie认证维护服务器端会话,同时添加JWT验证支持(用于校验API返回的令牌):

builder.Services.AddAuthentication(options =>
{
    options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
    options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
    options.DefaultChallengeScheme = "JwtBearer";
})
.AddCookie(options =>
{
    options.Cookie.HttpOnly = true;
    options.Cookie.SecurePolicy = CookieSecurePolicy.Always; // 生产环境强制HTTPS
    options.ExpireTimeSpan = TimeSpan.FromHours(8);
    options.LoginPath = "/Login"; // 未认证时自动跳转登录页
})
.AddJwtBearer("JwtBearer", options =>
{
    options.TokenValidationParameters = new TokenValidationParameters
    {
        ValidateIssuer = true,
        ValidateAudience = true,
        ValidateLifetime = true,
        ValidateIssuerSigningKey = true,
        ValidIssuer = builder.Configuration["Jwt:Issuer"], // 与API配置一致
        ValidAudience = builder.Configuration["Jwt:Audience"], // 与API配置一致
        IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration["Jwt:SecretKey"]))
    };
});

// 启用授权服务
builder.Services.AddAuthorization();
// 注入HttpContextAccessor用于操作认证会话
builder.Services.AddHttpContextAccessor();

确保中间件顺序正确:

app.UseAuthentication();
app.UseAuthorization();

二、实现登录逻辑,将JWT转换为服务器端认证会话

创建Login.razor组件,调用API获取JWT后,验证令牌并生成服务器端Cookie会话:

@inject HttpClient Http
@inject IHttpContextAccessor HttpContextAccessor
@inject IConfiguration Configuration
@inject NavigationManager NavigationManager

<h3>登录</h3>

<form @onsubmit="HandleLogin">
    <div>
        <label>用户名:</label>
        <input type="text" @bind="Username" required />
    </div>
    <div>
        <label>密码:</label>
        <input type="password" @bind="Password" required />
    </div>
    <button type="submit">登录</button>
    @if (!string.IsNullOrEmpty(ErrorMsg))
    {
        <p style="color:red">@ErrorMsg</p>
    }
</form>

@code {
    private string Username { get; set; }
    private string Password { get; set; }
    private string ErrorMsg { get; set; }

    private async Task HandleLogin()
    {
        ErrorMsg = string.Empty;
        try
        {
            // 调用API获取令牌
            var loginDto = new { Username = Username, Password = Password };
            var response = await Http.PostAsJsonAsync($"{Configuration["ApiBaseUrl"]}/auth/login", loginDto);
            
            if (!response.IsSuccessStatusCode)
            {
                ErrorMsg = await response.Content.ReadAsStringAsync();
                return;
            }

            var tokenRes = await response.Content.ReadFromJsonAsync<TokenResponse>();
            var tokenHandler = new JwtSecurityTokenHandler();
            var validationParams = new TokenValidationParameters
            {
                ValidateIssuer = true,
                ValidateAudience = true,
                ValidateLifetime = true,
                ValidateIssuerSigningKey = true,
                ValidIssuer = Configuration["Jwt:Issuer"],
                ValidAudience = Configuration["Jwt:Audience"],
                IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Jwt:SecretKey"]))
            };

            // 验证令牌并生成ClaimsPrincipal
            var principal = tokenHandler.ValidateToken(tokenRes.AccessToken, validationParams, out _);
            
            // 创建服务器端认证会话,持久化到Cookie
            await HttpContextAccessor.HttpContext.SignInAsync(
                CookieAuthenticationDefaults.AuthenticationScheme, 
                principal,
                new AuthenticationProperties
                {
                    IsPersistent = true,
                    ExpiresUtc = DateTimeOffset.UtcNow.AddHours(8)
                });

            // 跳转到授权页面
            NavigationManager.NavigateTo("/");
        }
        catch (Exception ex)
        {
            ErrorMsg = ex.Message;
        }
    }

    private class TokenResponse
    {
        public string AccessToken { get; set; }
        public string RefreshToken { get; set; }
    }
}

三、直接使用[Authorize]特性

现在可以在任意组件或页面上添加@attribute [Authorize],Blazor Server会自动识别服务器端的认证状态:

@attribute [Microsoft.AspNetCore.Authorization.Authorize]

<h1>用户专属页面</h1>
<p>只有已登录用户才能访问此内容</p>

<p>当前用户:@context.User.Identity.Name</p>

四、安全管理令牌的关键要点

  • 避免客户端存储JWT:不要把JWT存在localStorage或sessionStorage,依赖服务器端HttpOnly Cookie维护会话,防止XSS攻击。
  • 令牌刷新机制:如果API提供刷新令牌,可在服务器端将刷新令牌存储到数据库/缓存,当会话即将过期时自动调用API刷新令牌,更新会话。
  • 权限控制:利用JWT中的Claims(如角色、权限),结合[Authorize(Roles = "Admin")]或自定义授权策略实现细粒度权限控制。

五、注销逻辑实现

创建注销组件,清除服务器端的认证会话:

@inject IHttpContextAccessor HttpContextAccessor
@inject NavigationManager NavigationManager
@inject HttpClient Http
@inject IConfiguration Configuration

<button @onclick="HandleLogout" style="cursor:pointer">注销</button>

@code {
    private async Task HandleLogout()
    {
        // 可选:调用API的注销接口,失效刷新令牌
        // await Http.PostAsync($"{Configuration["ApiBaseUrl"]}/auth/logout", null);
        
        // 清除服务器端认证Cookie
        await HttpContextAccessor.HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
        NavigationManager.NavigateTo("/Login");
    }
}

内容的提问来源于stack exchange,提问作者Shervin Ivari

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.29 19:10:34