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

ASP.NET Core 8中同时支持Cookie、Bearer Token认证及角色权限管理的配置问题

ASP.NET Core 8中同时支持Cookie、Bearer Token认证及角色权限管理的配置问题

我来帮你搞定这个同时支持双认证+角色管理的问题,咱们一步步梳理调整:

一、核心问题分析

你之前的几次配置踩了两个关键坑:

  1. 用AddDefaultIdentity时没启用角色支持,导致RoleManager无法注册
  2. 切换到AddIdentity<IdentityUser, IdentityRole>后,没正确配置Bearer认证的关联逻辑,导致Token登录报错
  3. 用AddIdentityApiEndpoints时,默认生成的Token没包含角色Claims,所以角色策略授权返回403

二、正确的服务注册配置

直接上可运行的完整配置,我会逐段解释:

// 1. 先注册数据库上下文
builder.Services.AddDbContext<ApplicationDbContext>(options =>
    options.UseSqlServer(builder.Configuration.GetConnectionString("Communities")));

// 2. 注册Identity并启用角色支持
builder.Services.AddIdentity<IdentityUser, IdentityRole>(options =>
    {
        // 这里可以按需配置密码、锁定等Identity规则,比如:
        // options.Password.RequireDigit = true;
    })
    .AddEntityFrameworkStores<ApplicationDbContext>()
    .AddDefaultTokenProviders() // 用于生成密码重置、邮箱验证等Token
    .AddApiEndpoints(); // 启用Identity自带的API端点(登录、注册等)

// 3. 配置双认证方案:Cookie + Bearer Token
builder.Services.AddAuthentication(options =>
    {
        // 设置默认认证/挑战方案,这里用Identity默认的Cookie方案,也可以改成Bearer
        options.DefaultAuthenticateScheme = IdentityConstants.ApplicationScheme;
        options.DefaultChallengeScheme = IdentityConstants.ApplicationScheme;
        options.DefaultSignInScheme = IdentityConstants.ApplicationScheme;
    })
    .AddCookie(IdentityConstants.ApplicationScheme) // 保留Identity默认的Cookie认证
    .AddJwtBearer(options =>
    {
        // 配置JWT验证参数,要和你生成Token时的参数一致
        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuer = true,
            ValidateAudience = true,
            ValidateLifetime = true,
            ValidateIssuerSigningKey = true,
            ValidIssuer = builder.Configuration["Jwt:Issuer"], // 从配置文件读,比如appsettings.json
            ValidAudience = builder.Configuration["Jwt:Audience"],
            IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"]))
        };
        // 可选:支持从QueryString获取Token(方便调试)
        options.Events = new JwtBearerEvents
        {
            OnMessageReceived = context =>
            {
                var token = context.Request.Query["access_token"];
                if (!string.IsNullOrEmpty(token))
                {
                    context.Token = token;
                }
                return Task.CompletedTask;
            }
        };
    });

// 4. 配置授权策略
builder.Services.AddAuthorizationBuilder()
    .AddPolicy(Policy.Admin, policy => policy
        .RequireAuthenticatedUser()
        .RequireRole(Roles.Admin));

// 5. 注册自定义邮件发送器
builder.Services.AddTransient<IEmailSender<IdentityUser>, CommunitiesEmailSender<IdentityUser>>();

// 关键:添加自定义Claims工厂,确保Token中包含角色信息
builder.Services.AddScoped<IUserClaimsPrincipalFactory<IdentityUser>, CustomUserClaimsPrincipalFactory>();

补充:自定义Claims工厂代码

这个类的作用是把用户的角色信息添加到Token的Claims中,否则授权策略无法识别用户角色:

public class CustomUserClaimsPrincipalFactory : UserClaimsPrincipalFactory<IdentityUser, IdentityRole>
{
    public CustomUserClaimsPrincipalFactory(UserManager<IdentityUser> userManager, 
        RoleManager<IdentityRole> roleManager, 
        IOptions<IdentityOptions> optionsAccessor) 
        : base(userManager, roleManager, optionsAccessor)
    {
    }

    protected override async Task<ClaimsIdentity> GenerateClaimsAsync(IdentityUser user)
    {
        var identity = await base.GenerateClaimsAsync(user);
        // 把用户的所有角色添加到Claims里
        var roles = await UserManager.GetRolesAsync(user);
        foreach (var role in roles)
        {
            identity.AddClaim(new Claim(ClaimTypes.Role, role));
        }
        return identity;
    }
}

三、中间件顺序必须正确

中间件的执行顺序直接影响认证授权是否生效,一定要按这个顺序写:

var app = builder.Build();

// 先映射Identity的API端点(登录、注册等)
app.MapIdentityApi<IdentityUser>();

// 认证必须在授权之前!
app.UseAuthentication();
app.UseAuthorization();

// 最后映射你的控制器路由
app.MapControllers();

app.Run();

四、控制器中使用RoleManager

现在配置完成后,你可以直接在控制器中注入RoleManager<IdentityRole>和UserManager<IdentityUser>了:

private readonly RoleManager<IdentityRole> _roleManager;
private readonly UserManager<IdentityUser> _userManager;

public YourController(RoleManager<IdentityRole> roleManager, UserManager<IdentityUser> userManager)
{
    _roleManager = roleManager;
    _userManager = userManager;
}

[HttpPost]
[Authorize(Policy = Policy.Admin)]
[Route(nameof(SetUserRole))]
public async Task<IActionResult> SetUserRole(string userName, string desiredRole)
{
    // 检查目标角色是否存在
    var roleExists = await _roleManager.RoleExistsAsync(desiredRole);
    if (!roleExists)
    {
        return BadRequest($"角色「{desiredRole}」不存在");
    }

    // 查找目标用户
    var user = await _userManager.FindByNameAsync(userName);
    if (user == null)
    {
        return NotFound($"用户「{userName}」不存在");
    }

    // 添加角色
    var result = await _userManager.AddToRoleAsync(user, desiredRole);
    if (result.Succeeded)
    {
        return Ok("角色添加成功");
    }

    return BadRequest(result.Errors.Select(e => e.Description));
}

五、验证要点

  1. 确保你的appsettings.json中配置了JWT相关参数:
"Jwt": {
  "Issuer": "YourAppIssuer",
  "Audience": "YourAppAudience",
  "Key": "YourSuperSecretKeyAtLeast16CharsLong"
}
  1. 登录获取Token后,用Postman等工具测试时,把Token放在Authorization头中,格式为Bearer {你的Token}
  2. 给用户分配Admin角色后,重新登录获取Token,此时Token中会包含角色Claims,就能通过Admin策略的授权了

备注:内容来源于stack exchange,提问作者Juan Pablo Gomez

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.20 10:40:29