为什么从Blazor WebAssembly到 SignalR的认证Cookie无法传递?

前端开发 2026-07-11

我正在为一个基于浏览器的游戏平台构建一个配对服务,使用Blazor WebAssembly、Microsoft Entra外部身份和SignalR。为了将客户端连接到配对服务,我创建了 Hub 类的一个实例,然后添加了 [Authorize] 属性来进行保护,但Blazor组件无法启动与hub的连接。当 HubConnection.StartAsync() 被调用时,会抛出以下异常:

System.Text.Json.JsonReaderException: '\<' is an invalid start of a value. LineNumber: 2 | BytePositionInLine: 0.
   at System.Text.Json.ThrowHelper.ThrowJsonReaderException(Utf8JsonReader& json, ExceptionResource resource, Byte nextByte, ReadOnlySpan`1 bytes)
   at System.Text.Json.Utf8JsonReader.ConsumeValue(Byte marker)
   at System.Text.Json.Utf8JsonReader.ReadFirstToken(Byte first)
   at System.Text.Json.Utf8JsonReader.ReadSingleSegment()
   at System.Text.Json.Utf8JsonReader.Read()
   at Microsoft.AspNetCore.Internal.SystemTextJsonExtensions.CheckRead(Utf8JsonReader& reader)
   at Microsoft.AspNetCore.Http.Connections.NegotiateProtocol.ParseResponse(ReadOnlySpan`1 content)

当移除 [Authorize] 属性后,同一方法不会抛出异常,与hub的连接也已建立。

问题

  1. 这个错误的根本原因是什么?
  2. 如何解决?

项目结构

该方案包含两个项目:

  1. Blazor服务端(Web应用)
  2. Blazor客户端(WebAssembly)

Blazor服务端被配置为连接到身份服务器(Microsoft Entra External ID)以登录用户,并使用浏览器Cookie来存储访问令牌和身份令牌。它还包含配对集线器和配对服务。

Blazor客户端包含试图连接到SignalR集线器的页面组件。

Blazor服务端项目包含以下 Program.cs

public class Program
{
    public static void Main(string[] args)
    {
        var builder = WebApplication.CreateBuilder(args);

        // Add services to the container.
        builder.Services.AddRazorComponents()
            .AddInteractiveServerComponents()
            .AddInteractiveWebAssemblyComponents()
            .AddAuthenticationStateSerialization(options => options.SerializeAllClaims = true);

        builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
            .AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAd"))
            .EnableTokenAcquisitionToCallDownstreamApi()
            .AddDistributedTokenCaches();

        builder.Services.AddAuthorizationBuilder()
            .AddPolicy("RequireAuthenticatedUser", policy => policy.RequireAuthenticatedUser());

        builder.Services.AddSignalR();

        builder.Services.AddResponseCompression(options => {
            options.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat([ "application/octet-stream" ]);
        });

        var app = builder.Build();

        // Configure the HTTP request pipeline.
        if (app.Environment.IsDevelopment())
        {
            app.UseWebAssemblyDebugging();
        }
        else
        {
            app.UseResponseCompression();
            app.UseExceptionHandler("/Error");
            // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
            app.UseHsts();
        }

        app.UseStatusCodePagesWithReExecute("/not-found", createScopeForStatusCodePages: true);
        app.UseHttpsRedirection();

        app.UseAuthentication();
        app.UseAuthorization();
        app.UseAntiforgery();

        app.MapStaticAssets();
        app.MapRazorComponents<App>()
            .AddInteractiveServerRenderMode()
            .AddInteractiveWebAssemblyRenderMode()
            .AddAdditionalAssemblies(typeof(Client._Imports).Assembly);

        app.MapGroup("/authentication").MapAuthentication();

        app.MapHub<MatchHub>(MatchHub.Url);

        app.Run();
    }
}

SignalR集线器的实现如下:

[Authorize]
public class MatchHub : Hub<IMatchHub>
{
    public const string Url = "/match";

    private readonly IMatchService matchService;

    public MatchHub(IMatchService matchService)
    {
        this.matchService = matchService;
    }

    public async Task FindMatch()
    {
        if (Context.User is null)
        {
            return;
        }

        await matchService.FindMatch(new PlayerConnection(Context.ConnectionId, Context.User));
    }
}

Blazor客户端项目包含以下 Program.cs

class Program
{
    static async Task Main(string[] args)
    {
        var builder = WebAssemblyHostBuilder.CreateDefault(args);

        builder.Services.AddAuthorizationCore();
        builder.Services.AddCascadingAuthenticationState();
        builder.Services.AddAuthenticationStateDeserialization();

        await builder.Build().RunAsync();
    }
}

组件页面的实现如下:

@page "/lobby"

@using Company.Client.Services.Authentication
@using Microsoft.AspNetCore.Authorization
@using Microsoft.AspNetCore.SignalR.Client

@layout LobbyLayout
@attribute [Authorize]

@inject NavigationManager NavigationManager

<PageTitle>Lobby</PageTitle>

<div class="lobby">
    <button class="game">
        <div class="image">
            <img src="@src" alt="@alt" />
        </div>
        <div class="caption">
            <h1>game name</h1>
        </div>
    </button>
    <button class="play">play</button>
</div>

@code {
    private string src => $"/images/games/game-1/game-billboard.png";
    private string alt => "game name";

    private bool finding = false;
    private HubConnection? hubConnection;

    protected override async Task OnInitializedAsync()
    {
        hubConnection = new HubConnectionBuilder()
            .WithUrl(NavigationManager.ToAbsoluteUri("/match"))
            .WithAutomaticReconnect()
            .Build();

        await hubConnection.StartAsync();
    }
}

尝试解决的方法

这就是我迄今为止尝试的:

其他类似问题

  • 异常:'<'' 是无效的值起始字符:尽管此问题报告了类似的异常,但它并非由我的代码所使用的相同API引发,也不在Blazor WebAssembly组件共享cookie凭据给受保护的SignalR hub的上下文中。

解决方案

我不明白为何,但不对 hubConnection.StartAsync(); 使用await就解决了这个问题。现在连接已按预期建立。

⚠️ 失败:

protected override async Task OnInitializedAsync()
{
    hubConnection = new HubConnectionBuilder()
        .WithUrl(NavigationManager.ToAbsoluteUri("/match"))
        .WithAutomaticReconnect()
        .Build();

    await hubConnection.StartAsync();
}

✅ 可行:

protected override async Task OnInitializedAsync()
{
    hubConnection = new HubConnectionBuilder()
        .WithUrl(NavigationManager.ToAbsoluteUri("/match"))
        .WithAutomaticReconnect()
        .Build();

    hubConnection.StartAsync();
}

✅ 可行:

protected override void OnInitialized()
{
    hubConnection = new HubConnectionBuilder()
        .WithUrl(NavigationManager.ToAbsoluteUri("/match"))
        .WithAutomaticReconnect()
        .Build();

    hubConnection.StartAsync();
}

✅ 可行:

private bool started = false;

protected override async Task OnAfterRenderAsync(bool firstRender)
{
    if (firstRender && !started)
    {
        hubConnection = new HubConnectionBuilder()
            .WithUrl(NavigationManager.ToAbsoluteUri("/match"))
            .WithAutomaticReconnect()
            .Build();

        await hubConnection.StartAsync();

        started = true;
    }
}
站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章