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

Blazor Server同一用户多角色同时登录的实现问题

解决Blazor Server多角色会话共存问题

要实现管理员(Supervisor)和参会者(Attendee)角色的会话共存且互不覆盖,需要从多会话存储和激活角色管理两方面修改CustomAuthenticationStateProvider,以下是完整解决方案:

核心修改思路

  1. 按角色拆分会话存储键,用UserSession_{Role}格式区分不同角色的会话
  2. 新增存储键记录当前激活的角色,让GetAuthenticationStateAsync知道要加载哪个角色的会话
  3. 支持角色切换功能,允许在已登录的角色间快速切换
  4. 登出某角色时自动切换到其他存在的会话(如果有的话)

完整修改后的代码

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;
    }
}

使用说明

  1. 登录角色:调用UpdateAuthenticationState时传入角色参数
    // 登录管理员
    await _authStateProvider.UpdateAuthenticationState(supervisorSession, "Supervisor");
    // 登录参会者(不会覆盖管理员会话)
    await _authStateProvider.UpdateAuthenticationState(attendeeSession, "Attendee");
    
  2. 切换角色:在UI中添加切换按钮,调用SwitchRole方法
    // 从管理员切换到参会者
    await _authStateProvider.SwitchRole("Attendee");
    
  3. 登出角色:调用UpdateAuthenticationState并传入null和对应角色
    // 登出参会者,自动切换回管理员会话(如果存在)
    await _authStateProvider.UpdateAuthenticationState(null, "Attendee");
    

注意事项

  • 确保UserSession类的UserRole属性与传入的角色参数一致,避免权限判断混乱
  • ProtectedSessionStorage是浏览器会话级存储,关闭浏览器后所有会话会自动清除,符合测试场景需求
  • 可以在UI顶部添加角色标识(如当前角色:管理员),方便用户识别当前身份

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.28 09:45:03