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

Blazor Server(.NET6)中SignalR的HubConnectionContext二次调用丢失用户信息

Blazor Server SignalR 导航后丢失用户认证信息的解决方案

问题描述

基于Blazor Server和.NET6实现了默认身份认证功能(登录、注销、注册正常),尝试通过SignalR向特定用户发送消息时遇到问题:已实现IUserIdProvider接口获取UserId,首次启动应用时可通过connection.GetHttpContext()或connection.User.FindFirstValue(ClaimTypes.Name)获取用户信息,但导航到其他页面再次调用Hub时,HubConnectionContext丢失了用户所有信息。强制使用常量ID可正常工作,疑惑二次调用丢失信息的原因,不确定是否需要配置Cookie(首次调用可正常获取信息)。

现有代码

CustomUserIdProvider.cs

public class CustomUserIdProvider : IUserIdProvider
{
    public string? GetUserId(HubConnectionContext connection)
    {
        var httpContext = connection.GetHttpContext();
        var userId = connection.User.FindFirstValue(ClaimTypes.Name);

        if (string.IsNullOrWhiteSpace(userId))
            return string.Empty;

        return userId;
    }
}

Program.cs

// ...
builder.Services.AddSingleton<IUserIdProvider, CustomUserIdProvider>();
// ...
app.UseAuthentication();
app.UseAuthorization();

SignalR.razor

protected override async Task OnInitializedAsync()
{
        hubConnection = new HubConnectionBuilder()
            .WithUrl(NavigationManager.ToAbsoluteUri("/notifyhub"))
        .Build();
        hubConnection.On<int, string>("ReceiveMessage", (id, message) =>
    {
        var encodedMsg = $"{id}: {message}";
        InvokeAsync(StateHasChanged);
    });

        await hubConnection.StartAsync();
}
private async Task Send()
{
        if (hubConnection is not null)
        {
            var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync();
            var user = authState.User;
            authMessage = $"{user.Identity.Name} is authenticated.";
            claims = user.Claims;
            surnameMessage =
                $"Surname: {user.FindFirst(c => c.Type == ClaimTypes.Surname)?.Value}";
            await hubConnection.SendAsync("Send", user.Identity.Name, 1, "Message envoyé");
        }
}

问题原因及解决方法

核心原因

Blazor Server采用SPA式客户端导航,而非传统HTTP页面刷新。首次加载时,SignalR连接基于初始请求的HttpContext建立,自动携带了认证Cookie;但导航后重新创建的HubConnection默认不会主动携带认证凭证,导致Hub端无法识别用户身份。

解决步骤

  1. 配置HubConnection携带认证凭证
    在构建HubConnection时,显式设置包含Cookie凭证,确保认证信息被传递:

    hubConnection = new HubConnectionBuilder()
        .WithUrl(NavigationManager.ToAbsoluteUri("/notifyhub"), options =>
        {
            options.Credentials = FetchCredentials.Include;
        })
        .WithAutomaticReconnect() // 可选:添加自动重连,提升连接稳定性
        .Build();
    
  2. 确认中间件顺序正确性
    确保Program.cs中中间件顺序符合.NET认证规范,必须按以下顺序配置:

    app.UseRouting();
    app.UseAuthentication();
    app.UseAuthorization();
    app.MapBlazorHub(); // 必须在认证中间件之后注册
    app.MapFallbackToPage("/_Host");
    
  3. 优化用户ID获取逻辑
    建议使用ClaimTypes.NameIdentifier作为用户唯一标识(而非ClaimTypes.Name),避免用户名重复导致的问题:

    public string? GetUserId(HubConnectionContext connection)
    {
        var userId = connection.User.FindFirstValue(ClaimTypes.NameIdentifier);
        return string.IsNullOrWhiteSpace(userId) ? string.Empty : userId;
    }
    
  4. 复用HubConnection避免重复创建
    将HubConnection封装为注入服务,避免每次组件初始化都创建新连接:

    // 封装Hub服务类
    public class NotifyHubService
    {
        public HubConnection HubConnection { get; }
    
        public NotifyHubService(NavigationManager navigationManager)
        {
            HubConnection = new HubConnectionBuilder()
                .WithUrl(navigationManager.ToAbsoluteUri("/notifyhub"), options =>
                {
                    options.Credentials = FetchCredentials.Include;
                })
                .WithAutomaticReconnect()
                .Build();
        }
    }
    
    // Program.cs中注册服务
    builder.Services.AddScoped<NotifyHubService>();
    
    // 组件中注入使用
    [Inject]
    private NotifyHubService HubService { get; set; }
    
    protected override async Task OnInitializedAsync()
    {
        if (HubService.HubConnection.State == HubConnectionState.Disconnected)
        {
            await HubService.HubConnection.StartAsync();
        }
        HubService.HubConnection.On<int, string>("ReceiveMessage", (id, message) =>
        {
            var encodedMsg = $"{id}: {message}";
            InvokeAsync(StateHasChanged);
        });
    }
    

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.22 14:29:10