ASP.NET Core 2.0整合Azure AD认证、自定义JWT及身份存储遇问题
我之前在整合这些模块的时候也踩过不少坑,结合单独示例的经验,给你梳理一套可行的整合方案,一步步来:
整合Azure AD认证与自定义JWT方案的实操步骤
1. 双认证方案配置:Azure AD做身份源,自定义JWT做默认认证
首先在Startup.cs里同时配置Azure AD和自定义JWT认证,核心是区分挑战方案(未认证时跳转到Azure AD登录)和默认认证方案(后续请求用自定义JWT验证):
services.AddAuthentication(options => { // 默认用自定义JWT处理已认证请求,实现跨实例一致性 options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; // 未认证时触发Azure AD登录流程 options.DefaultChallengeScheme = AzureADDefaults.AuthenticationScheme; }) // 配置Azure AD身份提供者 .AddAzureAD(options => Configuration.Bind("AzureAd", options)) // 配置自定义JWT验证规则 .AddJwtBearer(options => { options.TokenValidationParameters = 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"])) }; });
2. Azure AD登录成功后生成自定义JWT
在Azure AD登录回调的Action里,先获取Azure AD返回的用户身份,再结合自定义身份存储的角色、权限信息,生成我们自己的JWT令牌:
public async Task<IActionResult> AzureADCallback() { // 验证Azure AD登录结果 var aadAuthResult = await HttpContext.AuthenticateAsync(AzureADDefaults.AuthenticationScheme); if (!aadAuthResult.Succeeded) { return RedirectToAction("Login"); } // 从自定义身份存储拉取用户角色、额外信息 var userService = HttpContext.RequestServices.GetRequiredService<IUserService>(); var userId = aadAuthResult.Principal.FindFirstValue(ClaimTypes.NameIdentifier); var userRoles = await userService.GetUserRoles(userId); // 合并Azure AD的Claims和自定义Claims var customClaims = new List<Claim>(); foreach (var role in userRoles) { customClaims.Add(new Claim(ClaimTypes.Role, role)); } // 可添加其他自定义字段,比如用户昵称、权限码等 customClaims.Add(new Claim("NickName", await userService.GetUserNickName(userId))); var claimsIdentity = new ClaimsIdentity( aadAuthResult.Principal.Claims.Concat(customClaims), JwtBearerDefaults.AuthenticationScheme ); // 生成自定义JWT var jwtToken = GenerateJwtToken(claimsIdentity); // 保存令牌:可以写入Cookie供Web端使用,也可以直接返回给前端/桌面客户端 Response.Cookies.Append("BearerToken", jwtToken, new CookieOptions { HttpOnly = true, Secure = true, SameSite = SameSiteMode.Strict, Expires = DateTime.Now.AddHours(8) }); return RedirectToAction("Index", "Home"); } // JWT生成工具方法 private string GenerateJwtToken(ClaimsIdentity identity) { var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Jwt:SecretKey"])); var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256); var token = new JwtSecurityToken( issuer: Configuration["Jwt:Issuer"], audience: Configuration["Jwt:Audience"], claims: identity.Claims, expires: DateTime.Now.AddHours(8), signingCredentials: credentials ); return new JwtSecurityTokenHandler().WriteToken(token); }
3. 支持桌面客户端使用Bearer令牌
桌面客户端只需要获取到这个JWT令牌(比如从Web应用的登录接口返回,或者用户手动复制),在请求API时,在请求头中添加:
Authorization: Bearer {你的JWT令牌}
因为我们已经配置了JwtBearer认证方案,API会自动验证令牌的有效性,无需额外修改。
4. 自定义角色与策略授权
基于自定义身份存储的角色,在Startup.cs中配置授权策略:
services.AddAuthorization(options => { options.AddPolicy("AdminOnly", policy => policy.RequireRole("Admin")); options.AddPolicy("EditorAccess", policy => policy.RequireRole("Editor", "Admin")); // 也可以基于自定义Claim配置策略,比如权限码 options.AddPolicy("CanEditPosts", policy => policy.RequireClaim("Permission", "Post.Edit")); });
然后在控制器或Action上直接使用:
[Authorize(Policy = "AdminOnly")] public IActionResult AdminDashboard() { return View(); }
5. 可选:自动Claims转换(更灵活的角色注入)
如果不想在登录回调里手动合并Claims,可以用IClaimsTransformation实现自动注入自定义角色:
// 注册Claims转换服务 services.AddScoped<IClaimsTransformation, CustomClaimsTransformation>(); public class CustomClaimsTransformation : IClaimsTransformation { private readonly IUserService _userService; public CustomClaimsTransformation(IUserService userService) { _userService = userService; } public async Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal) { var userId = principal.FindFirstValue(ClaimTypes.NameIdentifier); if (string.IsNullOrEmpty(userId)) return principal; var roles = await _userService.GetUserRoles(userId); var claimsIdentity = principal.Identity as ClaimsIdentity; // 避免重复添加角色 foreach (var role in roles) { if (!claimsIdentity.HasClaim(c => c.Type == ClaimTypes.Role && c.Value == role)) { claimsIdentity.AddClaim(new Claim(ClaimTypes.Role, role)); } } return principal; } }
这样每次认证通过后,系统会自动从自定义身份存储拉取角色并注入到Claims中。
常见整合坑点排查
- 认证方案冲突:务必确保
DefaultAuthenticateScheme和DefaultChallengeScheme分别指向自定义JWT和Azure AD,否则会出现登录后仍未认证的问题。 - JWT验证失败:检查配置文件中的
Jwt:Issuer、Jwt:Audience、Jwt:SecretKey是否和生成令牌时一致,同时确认令牌未过期。 - 角色不生效:确保自定义角色的Claim类型是
ClaimTypes.Role,如果用了自定义类型(比如"Role"),需要在策略中用RequireClaim("Role", "Admin")指定。
内容的提问来源于stack exchange,提问作者Astasian
相关产品推荐
相关产品推荐

