Blazor Server同一用户多角色同时登录的实现问题
解决Blazor Server多角色会话共存问题
要实现管理员(Supervisor)和参会者(Attendee)角色的会话共存且互不覆盖,需要从多会话存储和激活角色管理两方面修改CustomAuthenticationStateProvider,以下是完整解决方案:
核心修改思路
- 按角色拆分会话存储键,用
UserSession_{Role}格式区分不同角色的会话 - 新增存储键记录当前激活的角色,让
GetAuthenticationStateAsync知道要加载哪个角色的会话 - 支持角色切换功能,允许在已登录的角色间快速切换
- 登出某角色时自动切换到其他存在的会话(如果有的话)
完整修改后的代码
1. 自定义认证状态提供者
public class CustomAuthenticationStateProvider : AuthenticationStateProvider { private readonly ProtectedSessionStorage _browserStorage; private readonly ClaimsPrincipal _anonymous = new(new ClaimsIdentity()); // 常量定义避免硬编码 private const string SessionKeyPrefix = "UserSession_"; private const string ActiveRoleKey = "ActiveUserRole"; public CustomAuthenticationStateProvider(ProtectedSessionStorage protectedBrowserStorage) { _browserStorage = protectedBrowserStorage; } public override async Task<AuthenticationState> GetAuthenticationStateAsync() { try { // 获取当前激活的角色 var activeRoleResult = await _browserStorage.GetAsync<string>(ActiveRoleKey); var activeRole = activeRoleResult.Success ? activeRoleResult.Value : null; if (string.IsNullOrEmpty(activeRole)) { // 无激活角色时,自动查找已存在的会话(优先管理员) activeRole = await AutoDetectActiveRole(); if (activeRole == null) { return new AuthenticationState(_anonymous); } } // 根据激活角色加载对应会话 var userSessionResult = await _browserStorage.GetAsync<UserSession>($"{SessionKeyPrefix}{activeRole}"); var userSession = userSessionResult.Success ? userSessionResult.Value : null; if (userSession == null) { await _browserStorage.DeleteAsync(ActiveRoleKey); return new AuthenticationState(_anonymous); } var claimsPrincipal = new ClaimsPrincipal(new ClaimsIdentity(new List<Claim> { new(ClaimTypes.GivenName, userSession.FirstName!), new(ClaimTypes.Name, userSession.FullName!), new(ClaimTypes.Email, userSession.Email!), new(ClaimTypes.Role, userSession.UserRole!) }, "CustomAuth")); return new AuthenticationState(claimsPrincipal); } catch { return new AuthenticationState(_anonymous); } } public async Task UpdateAuthenticationState(UserSession? userSession, string? userRole) { if (string.IsNullOrEmpty(userRole)) throw new ArgumentNullException(nameof(userRole)); ClaimsPrincipal claimsPrincipal; var sessionKey = $"{SessionKeyPrefix}{userRole}"; if (userSession != null) { // 存储对应角色的会话,并设为当前激活角色 await _browserStorage.SetAsync(sessionKey, userSession); await _browserStorage.SetAsync(ActiveRoleKey, userRole); claimsPrincipal = new ClaimsPrincipal(new ClaimsIdentity(new List<Claim> { new(ClaimTypes.GivenName, userSession.FirstName!), new(ClaimTypes.Name, userSession.FullName!), new(ClaimTypes.Email, userSession.Email!), new(ClaimTypes.Role, userSession.UserRole!) }, "CustomAuth")); } else { // 删除对应角色的会话 await _browserStorage.DeleteAsync(sessionKey); // 登出后自动切换到其他存在的角色 var newActiveRole = await AutoDetectActiveRole(); if (newActiveRole != null) { await _browserStorage.SetAsync(ActiveRoleKey, newActiveRole); var sessionResult = await _browserStorage.GetAsync<UserSession>($"{SessionKeyPrefix}{newActiveRole}"); var activeSession = sessionResult.Value; claimsPrincipal = new ClaimsPrincipal(new ClaimsIdentity(new List<Claim> { new(ClaimTypes.GivenName, activeSession.FirstName!), new(ClaimTypes.Name, activeSession.FullName!), new(ClaimTypes.Email, activeSession.Email!), new(ClaimTypes.Role, activeSession.UserRole!) }, "CustomAuth")); } else { await _browserStorage.DeleteAsync(ActiveRoleKey); claimsPrincipal = _anonymous; } } NotifyAuthenticationStateChanged(Task.FromResult(new AuthenticationState(claimsPrincipal))); } // 角色切换方法:在已登录的角色间切换 public async Task SwitchRole(string targetRole) { var sessionKey = $"{SessionKeyPrefix}{targetRole}"; var sessionResult = await _browserStorage.GetAsync<UserSession>(sessionKey); if (!sessionResult.Success) throw new InvalidOperationException($"没有找到{targetRole}角色的登录会话"); await _browserStorage.SetAsync(ActiveRoleKey, targetRole); var userSession = sessionResult.Value; var claimsPrincipal = new ClaimsPrincipal(new ClaimsIdentity(new List<Claim> { new(ClaimTypes.GivenName, userSession.FirstName!), new(ClaimTypes.Name, userSession.FullName!), new(ClaimTypes.Email, userSession.Email!), new(ClaimTypes.Role, userSession.UserRole!) }, "CustomAuth")); NotifyAuthenticationStateChanged(Task.FromResult(new AuthenticationState(claimsPrincipal))); } // 自动检测已存在的会话,优先返回管理员角色 private async Task<string?> AutoDetectActiveRole() { var supervisorResult = await _browserStorage.GetAsync<UserSession>($"{SessionKeyPrefix}Supervisor"); if (supervisorResult.Success) return "Supervisor"; var attendeeResult = await _browserStorage.GetAsync<UserSession>($"{SessionKeyPrefix}Attendee"); if (attendeeResult.Success) return "Attendee"; return null; } }
使用说明
- 登录角色:调用
UpdateAuthenticationState时传入角色参数// 登录管理员 await _authStateProvider.UpdateAuthenticationState(supervisorSession, "Supervisor"); // 登录参会者(不会覆盖管理员会话) await _authStateProvider.UpdateAuthenticationState(attendeeSession, "Attendee"); - 切换角色:在UI中添加切换按钮,调用
SwitchRole方法// 从管理员切换到参会者 await _authStateProvider.SwitchRole("Attendee"); - 登出角色:调用
UpdateAuthenticationState并传入null和对应角色// 登出参会者,自动切换回管理员会话(如果存在) await _authStateProvider.UpdateAuthenticationState(null, "Attendee");
注意事项
- 确保
UserSession类的UserRole属性与传入的角色参数一致,避免权限判断混乱 ProtectedSessionStorage是浏览器会话级存储,关闭浏览器后所有会话会自动清除,符合测试场景需求- 可以在UI顶部添加角色标识(如当前角色:管理员),方便用户识别当前身份
内容的提问来源于stack exchange,提问作者Mads
相关产品推荐
相关产品推荐

