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

IIS部署的Blazor Server SignalR应用证书绕过及403错误排查

问题:Blazor Server部署IIS后SignalR Hub连接403错误

我在Windows Server 2019的IIS上部署了基于.NET Core的Blazor Server应用,采用智能卡证书认证登录。IIS仅启用匿名认证,其余认证方式均禁用,应用登录功能正常,但加载初始化SignalR Hub连接的页面时直接崩溃,事件查看器显示403(Forbidden)错误。本地Visual Studio 2022的IIS Express环境下,应用及SignalR Hub连接均正常。我已安装IIS的WebSocket协议,并尝试修改HomeComponent.razor中的HubConnection配置(指定WebSocket传输、绕过证书校验等),但问题仍未解决。

相关代码

Program.cs

var builder = WebApplication.CreateBuilder(args);
string connString = builder.Configuration.GetConnectionString("DefaultConnection");

// Add services to the container.
builder.Services.AddRazorPages();
builder.Services.AddServerSideBlazor();
builder.Services.AddHttpContextAccessor();
builder.Services.AddSession(options =>
{
    options.Cookie.IsEssential = true;
});
builder.Services.AddDistributedMemoryCache();
builder.Services.AddDbContextFactory<ApplicationDbContext>(options => options.UseSqlServer(connString));
builder.Services.AddServerSideBlazor().AddCircuitOptions(options => { options.DetailedErrors = true; });
builder.Services.AddSignalRCore();
builder.Services.AddScoped<ICertificateValidationService, CertificateValidationService>();
builder.Services.AddScoped<IUserManagement, UserManagement>();

builder.Services.AddAuthentication(
        CertificateAuthenticationDefaults.AuthenticationScheme)
    .AddCertificate(options =>
    {
        options.AllowedCertificateTypes = CertificateTypes.All;
        options.Events = new CertificateAuthenticationEvents
        {
            OnCertificateValidated = context =>
            {
                var validationService = context.HttpContext.RequestServices
                    .GetRequiredService<ICertificateValidationService>();

                if (validationService.ValidateCertificate(context.ClientCertificate))
                {
                    var claims = new[]
                    {
                        new Claim(
                            ClaimTypes.NameIdentifier,
                            context.ClientCertificate.Subject,
                            ClaimValueTypes.String, context.Options.ClaimsIssuer),
                        new Claim(
                            ClaimTypes.Name,
                            context.ClientCertificate.Subject,
                            ClaimValueTypes.String, context.Options.ClaimsIssuer)
                    };

                    context.Principal = new ClaimsPrincipal(
                        new ClaimsIdentity(claims, context.Scheme.Name));
                    context.Success();
                }

                return Task.CompletedTask;
            }
        };
    });

var app = builder.Build();

// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
    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();
}
else
{
    app.UseDeveloperExceptionPage();
}

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

app.UseSession();

app.UseHttpsRedirection();

app.UseStaticFiles();

app.UseRouting();

app.MapBlazorHub();
app.UseEndpoints(endpoints =>
{
    endpoints.MapHub<EditingNotificationHub>("/editingHub");
});
app.MapFallbackToPage("/_Host");

app.Run();

HomeComponent.razor修改后代码

protected override async Task OnInitializedAsync()
    {
        hubConnection = new HubConnectionBuilder().WithUrl(_navigationManager.ToAbsoluteUri("/editingHub"), options => {
            options.UseDefaultCredentials = true;
            options.Transports = HttpTransportType.WebSockets;
            options.HttpMessageHandlerFactory = (msg) =>
            {
                if (message is HttpClientHandler clientHandler)
                {
                    clientHandler.ServerCertificateCustomValidationCallback +=
                (sender, certificate, chain, sslPolicyErrors) => { return true; };
                    return message;
                }

                return message;
            };
            options.WebSocketConfiguration = wsc => wsc.RemoteCertificateValidationCallback = (sender, certificate, chain, policyErrors) => true;
        }).WithAutomaticReconnect().Build();

        hubConnection.On<int, bool, int>("ReceiveMessage", (senderID, editingStatus, prid) =>
        {
            notification = new EditingNotification() { UserID = senderID, Editing = editingStatus, PRID = prid };
            if(prid == PRNum)
            {
                if (editingStatus)
                {
                    userEditing = editingStatus;
                    userEditingMessage = "is editing.";
                }
                else
                {
                    userEditingMessage = "has finished editing, please reload the page.";
                }
            }
            InvokeAsync(StateHasChanged);
        });
        await hubConnection.StartAsync();
    }

事件查看器错误日志

Category: Microsoft.AspNetCore.Components.Server.Circuits.CircuitHost
EventId: 111
SpanId: 083e8e84ca56fc4b
TraceId: dfa84b4238d669b811b5aca6e1cc31c7
ParentId: 0000000000000000
RequestId: 80002230-0001-c600-b63f-84710c7967bb
RequestPath: /_blazor
TransportConnectionId: z1Bby_rgcRzSoz45wN1wIg

Unhandled exception in circuit 'NCCj9qdxaYD99Y9ZlZswPHWLazRLu_r5xk_4BA_RBP0'.

Exception: 
System.Net.Http.HttpRequestException: Response status code does not indicate success: 403 (Forbidden).
   at System.Net.Http.HttpResponseMessage.EnsureSuccessStatusCode()
   at Microsoft.AspNetCore.Http.Connections.Client.HttpConnection.NegotiateAsync(Uri url, HttpClient httpClient, ILogger logger, CancellationToken cancellationToken)
   at Microsoft.AspNetCore.Http.Connections.Client.HttpConnection.GetNegotiationResponseAsync(Uri uri, CancellationToken cancellationToken)
   at Microsoft.AspNetCore.Http.Connections.Client.HttpConnection.SelectAndStartTransport(TransferFormat transferFormat, CancellationToken cancellationToken)
   at Microsoft.AspNetCore.Http.Connections.Client.HttpConnection.StartAsyncCore(TransferFormat transferFormat, CancellationToken cancellationToken)
   at System.Threading.Tasks.ForceAsyncAwaiter.GetResult()
   at Microsoft.AspNetCore.Http.Connections.Client.HttpConnection.StartAsync(TransferFormat transferFormat, CancellationToken cancellationToken)
   at Microsoft.AspNetCore.Http.Connections.Client.HttpConnectionFactory.ConnectAsync(EndPoint endPoint, CancellationToken cancellationToken)
   at Microsoft.AspNetCore.Http.Connections.Client.HttpConnectionFactory.ConnectAsync(EndPoint endPoint, CancellationToken cancellationToken)
   at Microsoft.AspNetCore.SignalR.Client.HubConnection.StartAsyncCore(CancellationToken cancellationToken)
   at Microsoft.AspNetCore.SignalR.Client.HubConnection.StartAsyncInner(CancellationToken cancellationToken)
   at System.Threading.Tasks.ForceAsyncAwaiter.GetResult()
   at Microsoft.AspNetCore.SignalR.Client.HubConnection.StartAsync(CancellationToken cancellationToken)
   at WORKS.Pages.PurchaseRequestDetailsView.PurchaseRequestDetailsView.OnInitializedAsync() in C:\Users\613163\Desktop\WORKS\WORKS\Pages\PurchaseRequestDetailsView\PurchaseRequestDetailsView.razor:line 320
   at Microsoft.AspNetCore.Components.ComponentBase.RunInitAndSetParametersAsync()
   at Microsoft.AspNetCore.Components.RenderTree.Renderer.GetErrorHandledTask(Task taskToHandle, ComponentState owningComponentState)

目前不清楚遗漏了哪些配置或代码,寻求解决思路。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.17 22:03:08