Cookie认证用户角色Claims更新不生效问题排查求助
问题描述
我在控制器中实现了两个端点,目标是传入角色列表并将其添加到当前Cookie认证用户的Claims中。该API同时使用AddNegotiate和AddCookie两种认证方式,但调用SignIn前后,Claims端点返回的内容一致,新添加的Claims并未显示。我的目标是让同域下的另一个应用发送新角色列表,应用到用户后,重新登录能在Claims中看到更新。
控制器代码
[HttpPost("updateroles")] [Authorize(AuthenticationSchemes = CookieAuthenticationDefaults.AuthenticationScheme)] public async Task<IActionResult> UpdateRoles([FromBody] RoleUpdateRequest request) { if (request.Roles == null) { return BadRequest("Roles cannot be null"); } var currentClaimsIdentity = (ClaimsIdentity)User.Identity!; if (currentClaimsIdentity != null) { try { var claims = request.Roles!.Select(role => new Claim(ClaimTypes.Role, role)).ToList(); currentClaimsIdentity.AddClaims(claims); var newPrincipal = new ClaimsPrincipal(currentClaimsIdentity); var lastAuthenticated = DateTime.UtcNow; var authProperties = new AuthenticationProperties() { IsPersistent = true, ExpiresUtc = lastAuthenticated.AddHours(_appConfiguration.CookieTimeout) }; var claimsMessage = string.Join(", ", newPrincipal.Claims.Select(c => c.Value)); _logger.LogMessage("Updating User with Following Claims : " + claimsMessage); // Sign in with the new principal await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme); await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, newPrincipal, authProperties); } catch (Exception ex) { _logger.LogError("Role Update Error", ex); return StatusCode(StatusCodes.Status500InternalServerError, "An error occurred while updating roles."); } return Ok(new { Message = "Roles updated successfully", Roles = request.Roles }); } return BadRequest("Unable to update roles."); } [HttpGet("claims")] [Authorize(AuthenticationSchemes = CookieAuthenticationDefaults.AuthenticationScheme)] public async Task<IActionResult> GetClaims() { if (User.Identity!.IsAuthenticated) { var userId = User.Identity!.Name; return Ok(User.Claims.Select(c => new { c.Type, c.Value })); } return Unauthorized(); }
认证配置代码
// Cookies stored key ring services.AddDataProtection() .PersistKeysToFileSystem(new DirectoryInfo(PHYSICALPATH)) .SetApplicationName(config["CookieName"]!); services.AddAuthentication(NegotiateDefaults.AuthenticationScheme) .AddNegotiate() // For Windows Authentication to retrieve User Groups .AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, options => { options.Cookie.Name = config["CookieName"]; options.Cookie.Domain = config["DomainSite"]; options.Cookie.Path = "/"; options.Cookie.HttpOnly = true; options.Cookie.SecurePolicy = CookieSecurePolicy.Always; options.Cookie.SameSite = SameSiteMode.None; });
登录方法代码
[AllowAnonymous] [HttpGet("login")] public async Task<IActionResult> Login(string returnUrl) { if (User.Identity != null && User.Identity.IsAuthenticated) { var username = User.Identity.Name; var lastAuthenticated = DateTime.UtcNow; var roles = _domainUserGroupService.GetRolesFromGroups(User); // Create claims with the Claim Name from Identity Name as well as adding roles and Last Authenticated Timestamp var claims = _domainUserGroupService.CreateClaims(User.Identity.Name!, roles, lastAuthenticated); var identity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme); var principal = new ClaimsPrincipal(identity); var authProperties = new AuthenticationProperties() { IsPersistent = true, ExpiresUtc = lastAuthenticated.AddHours(_appConfiguration.CookieTimeout) }; await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme); await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, principal, authProperties); _logger.LogMessage("Login User Identity Name : " + username); return Redirect(returnUrl); } else { return Unauthorized(); } }
错误分析
- 直接修改当前请求的ClaimsIdentity无效:当前请求的
User.Identity是认证流程生成的实例,直接修改它不会影响后续请求的Claims,SignIn操作生成新Cookie后,当前请求的User对象不会自动更新。 - SignOut/SignIn后未刷新当前请求上下文:即使执行了SignIn,当前请求的
User还是原有实例,必须发起新请求才能读取到新Cookie的内容。 - 未处理Role Claim重复问题:多次调用UpdateRoles会重复添加相同Role,导致Claims冗余,还可能影响后续登录的角色合并逻辑。
- 认证Scheme优先级冲突:默认认证Scheme是
NegotiateDefaults.AuthenticationScheme,若后续请求通过Negotiate自动登录,会覆盖Cookie中的Claims,导致更新的角色丢失。
改进方案
1. 修正UpdateRoles端点逻辑
避免直接修改当前请求的ClaimsIdentity,基于现有Claims创建新实例,并处理Role去重:
[HttpPost("updateroles")] [Authorize(AuthenticationSchemes = CookieAuthenticationDefaults.AuthenticationScheme)] public async Task<IActionResult> UpdateRoles([FromBody] RoleUpdateRequest request) { if (request.Roles == null || !request.Roles.Any()) { return BadRequest("Roles cannot be null or empty"); } // 提取当前用户非Role类型的Claims var existingNonRoleClaims = User.Claims .Where(c => c.Type != ClaimTypes.Role) .Select(c => new Claim(c.Type, c.Value)) .ToList(); // 生成去重后的新Role Claims var newRoleClaims = request.Roles .Distinct() .Select(role => new Claim(ClaimTypes.Role, role)) .ToList(); // 创建新的ClaimsIdentity,指定Cookie认证Scheme var newIdentity = new ClaimsIdentity( existingNonRoleClaims.Concat(newRoleClaims), CookieAuthenticationDefaults.AuthenticationScheme ); var newPrincipal = new ClaimsPrincipal(newIdentity); var lastAuthenticated = DateTime.UtcNow; var authProperties = new AuthenticationProperties() { IsPersistent = true, ExpiresUtc = lastAuthenticated.AddHours(_appConfiguration.CookieTimeout), IssuedUtc = lastAuthenticated }; try { await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme); await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, newPrincipal, authProperties); _logger.LogMessage($"Updated user {User.Identity?.Name} with roles: {string.Join(", ", request.Roles)}"); return Ok(new { Message = "Roles updated successfully. Send a new request to see changes.", Roles = request.Roles }); } catch (Exception ex) { _logger.LogError(ex, "Failed to update user roles"); return StatusCode(StatusCodes.Status500InternalServerError, "An error occurred while updating roles."); } }
2. 强制接口使用Cookie认证
在需要读取更新后Claims的接口上,明确指定认证Scheme,避免Negotiate覆盖Cookie内容:
[HttpGet("claims")] [Authorize(AuthenticationSchemes = CookieAuthenticationDefaults.AuthenticationScheme)] public IActionResult GetClaims() { if (User.Identity!.IsAuthenticated) { return Ok(User.Claims.Select(c => new { c.Type, c.Value })); } return Unauthorized(); }
3. 优化登录逻辑,优先使用Cookie认证
登录时优先检查是否已有有效Cookie,避免Negotiate的Groups覆盖更新后的角色:
[AllowAnonymous] [HttpGet("login")] public async Task<IActionResult> Login(string returnUrl) { // 优先检查Cookie认证状态 var cookieAuthResult = await HttpContext.AuthenticateAsync(CookieAuthenticationDefaults.AuthenticationScheme); if (cookieAuthResult.Succeeded) { return Redirect(returnUrl); } // 无有效Cookie时,使用Negotiate获取用户信息生成Cookie if (User.Identity != null && User.Identity.IsAuthenticated) { var username = User.Identity.Name; var lastAuthenticated = DateTime.UtcNow; var roles = _domainUserGroupService.GetRolesFromGroups(User); var claims = _domainUserGroupService.CreateClaims(User.Identity.Name!, roles, lastAuthenticated); var identity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme); var principal = new ClaimsPrincipal(identity); var authProperties = new AuthenticationProperties() { IsPersistent = true, ExpiresUtc = lastAuthenticated.AddHours(_appConfiguration.CookieTimeout) }; await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, principal, authProperties); _logger.LogMessage($"Logged in user: {username}"); return Redirect(returnUrl); } else { return Unauthorized(); } }
4. 添加Cookie自动刷新逻辑
配置Cookie认证的OnValidatePrincipal事件,在用户活跃时自动从数据源同步最新角色:
services.AddAuthentication(NegotiateDefaults.AuthenticationScheme) .AddNegotiate() .AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, options => { // 原有Cookie配置 options.Cookie.Name = config["CookieName"]; options.Cookie.Domain = config["DomainSite"]; options.Cookie.Path = "/"; options.Cookie.HttpOnly = true; options.Cookie.SecurePolicy = CookieSecurePolicy.Always; options.Cookie.SameSite = SameSiteMode.None; // 自动刷新Claims逻辑 options.Events = new CookieAuthenticationEvents { OnValidatePrincipal = async context => { var userService = context.HttpContext.RequestServices.GetRequiredService<IDomainUserGroupService>(); var username = context.Principal.Identity?.Name; if (!string.IsNullOrEmpty(username)) { var latestRoles = userService.GetRolesFromUser(username); var existingRoles = context.Principal.Claims .Where(c => c.Type == ClaimTypes.Role) .Select(c => c.Value) .ToList(); // 角色变化时更新Principal并刷新Cookie if (!latestRoles.SequenceEqual(existingRoles)) { var nonRoleClaims = context.Principal.Claims .Where(c => c.Type != ClaimTypes.Role) .Select(c => new Claim(c.Type, c.Value)); var newRoleClaims = latestRoles.Select(r => new Claim(ClaimTypes.Role, r)); var newIdentity = new ClaimsIdentity( nonRoleClaims.Concat(newRoleClaims), context.Principal.Identity.AuthenticationType ); context.ReplacePrincipal(new ClaimsPrincipal(newIdentity)); context.ShouldRenew = true; } } } }; });
内容的提问来源于stack exchange,提问作者Wolfenstyne
相关产品推荐
相关产品推荐

