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

无需Duende Identity Server实现Blazor WASM用户登录角色管理可行吗?

当然可以不使用Duende Identity Server

你的场景是单应用本地用户管理,只需要实现本应用内的用户存储、登录和角色分配——ASP.NET Core Identity(你已经在代码里用了AddDefaultIdentity)本身就提供了这些核心能力,完全没必要依赖Duende Identity Server(它主要是做跨应用单点登录或独立API授权服务的)。下面是具体的改造步骤:

1. 删掉Duende Identity Server相关代码

把Program.cs里这些和Duende有关的代码全部移除:

  • 整个builder.Services.AddIdentityServer().AddApiAuthorization<...>()代码块
  • app.UseIdentityServer()中间件
  • builder.Services.AddAuthentication().AddIdentityServerJwt()这一行

2. 替换为JWT认证配置

在原来AddIdentityServerJwt的位置,添加JWTBearer认证的配置,同时保留你原来的Claim映射清理逻辑:

// 保留原有Claim映射清理
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
JwtSecurityTokenHandler.DefaultOutboundClaimTypeMap.Clear();
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Remove("role");

// 添加JWT认证
builder.Services.AddAuthentication(options =>
{
    options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
    options.DefaultChallengeScheme = 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"]))
    };
});

// 支持角色授权策略(可选,根据你的需求定义)
builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("AdminOnly", policy => policy.RequireRole("Admin"));
});

3. 添加Token生成API

Blazor WASM客户端需要通过API获取JWT Token来完成登录,所以新建一个AuthController:

[Route("api/auth")]
[ApiController]
public class AuthController : ControllerBase
{
    private readonly UserManager<ApplicationUser> _userManager;
    private readonly IConfiguration _config;

    public AuthController(UserManager<ApplicationUser> userManager, IConfiguration config)
    {
        _userManager = userManager;
        _config = config;
    }

    [HttpPost("login")]
    public async Task<IActionResult> Login([FromBody] LoginRequest model)
    {
        var user = await _userManager.FindByEmailAsync(model.Email);
        if (user == null || !await _userManager.CheckPasswordAsync(user, model.Password))
        {
            return Unauthorized("邮箱或密码错误");
        }

        // 获取用户角色
        var roles = await _userManager.GetRolesAsync(user);
        // 组装Claim,包含你原来自定义的Avatar、Nick等
        var claims = new List<Claim>
        {
            new Claim(ClaimTypes.Name, user.UserName),
            new Claim(ClaimTypes.Email, user.Email),
            new Claim(UserClaim.Avatar, user.Avatar),
            new Claim(UserClaim.Nick, user.Nick),
            new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())
        };
        // 添加角色Claim
        foreach (var role in roles)
        {
            claims.Add(new Claim(ClaimTypes.Role, role));
        }

        // 生成Token
        var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config["Jwt:Key"]));
        var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
        var token = new JwtSecurityToken(
            issuer: _config["Jwt:Issuer"],
            audience: _config["Jwt:Audience"],
            claims: claims,
            expires: DateTime.Now.AddHours(3),
            signingCredentials: creds
        );

        return Ok(new
        {
            Token = new JwtSecurityTokenHandler().WriteToken(token),
            Expiration = token.ValidTo
        });
    }
}

// 登录请求模型
public class LoginRequest
{
    public string Email { get; set; }
    public string Password { get; set; }
}

4. 配置JWT参数到appsettings.json

在服务器项目的appsettings.json里添加JWT的密钥和 issuer/audience(密钥要设置成足够长的随机字符串,别用示例里的):

"Jwt": {
  "Key": "Your_Super_Long_Random_Secret_Key_Here_At_Least_32_Chars",
  "Issuer": "https://yourappdomain.com",
  "Audience": "https://yourappdomain.com"
}

5. 调整Blazor客户端的认证逻辑

在客户端项目的Program.cs里,替换原来的OIDC认证为自定义JWT认证:

builder.Services.AddAuthorizationCore();
builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
// 添加自定义认证服务和状态提供者
builder.Services.AddScoped<AuthService>();
builder.Services.AddScoped<CustomAuthStateProvider>();
builder.Services.AddScoped<AuthenticationStateProvider>(sp => sp.GetRequiredService<CustomAuthStateProvider>());

然后创建AuthService和CustomAuthStateProvider来处理登录、Token存储和认证状态:

public class AuthService
{
    private readonly HttpClient _http;
    private readonly CustomAuthStateProvider _authStateProvider;

    public AuthService(HttpClient http, CustomAuthStateProvider authStateProvider)
    {
        _http = http;
        _authStateProvider = authStateProvider;
    }

    public async Task<bool> Login(LoginRequest model)
    {
        var response = await _http.PostAsJsonAsync("api/auth/login", model);
        if (!response.IsSuccessStatusCode) return false;

        var tokenResult = await response.Content.ReadFromJsonAsync<TokenResponse>();
        if (tokenResult == null) return false;

        // 把Token存在本地安全存储里
        await SecureStorage.SetAsync("authToken", tokenResult.Token);
        // 通知认证状态更新
        _authStateProvider.NotifyAuthenticated(tokenResult.Token);
        return true;
    }

    public async Task Logout()
    {
        await SecureStorage.RemoveAsync("authToken");
        _authStateProvider.NotifyLoggedOut();
    }
}

public class CustomAuthStateProvider : AuthenticationStateProvider
{
    public override async Task<AuthenticationState> GetAuthenticationStateAsync()
    {
        var token = await SecureStorage.GetAsync("authToken");
        var identity = string.IsNullOrEmpty(token) 
            ? new ClaimsIdentity() 
            : new ClaimsIdentity(ParseClaimsFromToken(token), "jwt");
        
        return new AuthenticationState(new ClaimsPrincipal(identity));
    }

    public void NotifyAuthenticated(string token)
    {
        var identity = new ClaimsIdentity(ParseClaimsFromToken(token), "jwt");
        NotifyAuthenticationStateChanged(Task.FromResult(new AuthenticationState(new ClaimsPrincipal(identity))));
    }

    public void NotifyLoggedOut()
    {
        var identity = new ClaimsIdentity();
        NotifyAuthenticationStateChanged(Task.FromResult(new AuthenticationState(new ClaimsPrincipal(identity))));
    }

    private IEnumerable<Claim> ParseClaimsFromToken(string token)
    {
        var payload = token.Split('.')[1];
        var jsonBytes = Convert.FromBase64String(payload.PadRight(payload.Length + (4 - payload.Length % 4) % 4, '='));
        var claims = JsonSerializer.Deserialize<Dictionary<string, object>>(jsonBytes);
        return claims.Select(kvp => new Claim(kvp.Key, kvp.Value.ToString()));
    }
}

// 响应模型
public class TokenResponse
{
    public string Token { get; set; }
    public DateTime Expiration { get; set; }
}

最后验证功能

  • 用户管理:你原来的AppDbContext和ApplicationUser已经和ASP.NET Core Identity集成,可以直接用UserManager/RoleManager创建用户、分配角色(也可以用Identity自带的Razor Pages管理用户)
  • 登录:在Blazor组件里调用AuthService.Login即可完成登录,认证状态会自动同步
  • 角色授权:在组件或API控制器上用[Authorize(Roles = "Admin")]或者自定义策略做权限验证

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.06 11:25:27