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

SignalR在IIS服务器运行异常,本地正常:401未授权问题排查

问题:SignalR部署到IIS后出现401未授权错误

问题背景

已在IIS服务器上启用WebSocket,使用SignalR实现下载进度条实时更新功能,本地主机运行正常,但部署到服务器后出现401(未授权)错误,无法建立连接。

错误信息

blazor.web.js:1  [2024-07-08T21:19:28.598Z] Error: System.Net.Http.HttpRequestException: Response status code does not indicate success: 401 (Unauthorized).

Uncaught (in promise) Error: Cannot send data if the connection is not in the 'Connected' State.

完整堆栈跟踪

blazor.web.js:1  [2024-07-08T21:19:28.598Z] Error: System.Net.Http.HttpRequestException: Response status code does not indicate success: 401 (Unauthorized).
   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 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 Microsoft.AspNetCore.SignalR.Client.HubConnection.StartAsync(CancellationToken cancellationToken)
   at SecureFileShare.Client.Components.ProgressUIComponent.OnInitializedAsync() in C:\BHIDEV22\WebDev\SecureFileShare\SecureFileShare.Client\Components\ProgressUIComponent.razor:line 70
   at Microsoft.AspNetCore.Components.ComponentBase.RunInitAndSetParametersAsync()
   at Microsoft.AspNetCore.Components.RenderTree.Renderer.GetErrorHandledTask(Task taskToHandle, ComponentState owningComponentState)
log @ blazor.web.js:1
blazor.web.js:1 [2024-07-08T21:19:28.599Z] Information: Connection disconnected.
btest/:1  The file at 'http://bhub/btest/api/download' was loaded over an insecure connection. This file should be served over HTTPS.
blazor.web.js:1  Uncaught (in promise) Error: Cannot send data if the connection is not in the 'Connected' State.
    at Nn.send (blazor.web.js:1:85376)
    at gn._sendMessage (blazor.web.js:1:58807)
    at gn._sendWithProtocol (blazor.web.js:1:58897)
    at gn.send (blazor.web.js:1:59005)
    at Fo.beginInvokeDotNetFromJS (blazor.web.js:1:139654)
    at y.invokeDotNetMethodAsync (blazor.web.js:1:4322)
    at S.invokeMethodAsync (blazor.web.js:1:5830)
    at HTMLDivElement.<anonymous> (blazor.bootstrap.js:445:30)
    at Object.trigger (event-handler.js:289:15)
    at modal.js:196:20

相关代码

客户端连接代码(错误发生在第70行StartAsync())

protected override async Task OnInitializedAsync()
{
    _hubConnection = new HubConnectionBuilder()
        .WithUrl(Navigation.ToAbsoluteUri("/chathub"), options =>
        {
            options.UseDefaultCredentials = true;
        })
        .Build();

    _hubConnection.On<int>("ReceiveProgress", p =>
    {
        progress = p;
        InvokeAsync(StateHasChanged);
    });

    await _hubConnection.StartAsync();
}

Program.cs配置代码

var builder = WebApplication.CreateBuilder(args);


//For windows authentication 
//--------------------- 
//First add nuget package Microsoft.AspNetCore.Authentication.Negotiat
builder.Services.AddAuthentication(NegotiateDefaults.AuthenticationScheme)
   .AddNegotiate();

builder.Services.AddAuthorization(options =>
{
    options.FallbackPolicy = options.DefaultPolicy;
});

// Add services to the container.
builder.Services.AddRazorComponents()
    .AddInteractiveServerComponents()
    .AddInteractiveWebAssemblyComponents();

//Inject IHttpContextAccessor
builder.Services.AddHttpContextAccessor();

//injects
builder.Services.AddScoped<UserService>();
builder.Services.TryAddEnumerable(
    ServiceDescriptor.Scoped<CircuitHandler, UserCircuitHandler>());
//

builder.Services.AddControllers();

//form file upload size
builder.Services.Configure<FormOptions>(options =>
{
    options.MultipartBodyLengthLimit = long.MaxValue; // Set an appropriate limit
});

//Enabling Kestrel Support for the Large Files
builder.WebHost.ConfigureKestrel(serverOptions =>
{
    serverOptions.Limits.MaxRequestBodySize = long.MaxValue;
});


//bootstrap
builder.Services.AddBlazorBootstrap();

//Inject repo
builder.Services.AddScoped<ISfsEmailDetailRepo, SfsEmailDetailRepo>();

builder.Services.AddDbContextFactory<SecureFileShareContext>(opt =>
    opt.UseSqlServer(builder.Configuration.GetConnectionString("SecureFileShare"))
    );
//

//for signalR
builder.Services.AddSignalR();

builder.Services.AddSingleton<IUserIdProvider, NameUserIdProvider>();

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

builder.Services.AddAuthorization(options =>
{
    options.FallbackPolicy = new AuthorizationPolicyBuilder()
        .RequireAuthenticatedUser()
        .Build();
});


var app = builder.Build();

//for singalR
app.UseResponseCompression();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseWebAssemblyDebugging();
}
else
{
    app.UseExceptionHandler("/Error", createScopeForErrors: true);
    // 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.UseHttpsRedirection();

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

app.UseStaticFiles();
app.UseAntiforgery();

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

app.MapControllers();

//SInglarR
app.MapHub<ChatHub>("/chathub");

app.Run();

Hub代码

public class ChatHub : Hub
{
    public async Task SendMessage(string user, string message)
    {
        await Clients.All.SendAsync("ReceiveMessage", user, message);
    }
}

下载控制器更新UI方法

private async Task AddFileToArchive(ZipArchive archive, string filePath, string entryName, long totalSize)
{
    var entry = archive.CreateEntry(entryName);

    using (var entryStream = entry.Open())
    using (var fileStream = System.IO.File.OpenRead(filePath))
    {
        var buffer = new byte[60000];
        int bytesRead;

        while ((bytesRead = await fileStream.ReadAsync(buffer, 0, buffer.Length)) > 0)
        {
            await entryStream.WriteAsync(buffer, 0, bytesRead);
            bytesWritten += bytesRead;

            int percentComplete = (int)((double)bytesWritten / totalSize * 100);
            await _hubContext.Clients.All.SendAsync("ReceiveProgress", percentComplete);
        }
    }
}

排查方向与解决方案

1. 修复Program.cs中的重复授权配置

代码中存在两次AddAuthorization调用,第二次会覆盖第一次的配置,导致预期的授权策略可能不生效。合并为一次配置:

builder.Services.AddAuthorization(options =>
{
    options.FallbackPolicy = new AuthorizationPolicyBuilder()
        .RequireAuthenticatedUser()
        .Build();
});

删除第一次的AddAuthorization配置,确保全局策略统一。

2. 明确SignalR Hub的授权规则

给ChatHub添加[Authorize]属性,确保只有认证用户能访问:

[Authorize]
public class ChatHub : Hub
{
    // ... 现有代码
}

或者在映射Hub时显式要求授权:

app.MapHub<ChatHub>("/chathub").RequireAuthorization();

3. 检查IIS站点的认证设置

  • 打开IIS管理器,找到目标站点 → 认证
  • 启用Windows认证,禁用匿名认证(如果应用要求用户必须登录)
  • 双击Windows认证,确认Negotiate和NTLM提供者均处于启用状态

4. 配置Kerberos认证(域环境下)

如果服务器在域环境中,使用Kerberos认证需要为应用池账户注册SPN:

  • 打开命令提示符(管理员权限),执行:
    setspn -S HTTP/bhub DOMAIN\你的应用池账户名
    
  • 确保应用池使用域账户运行,而非内置账户(如ApplicationPoolIdentity),否则SPN注册无法正常生效

5. 客户端连接优化

  • 保留options.UseDefaultCredentials = true,确保客户端发送当前用户的Windows凭据
  • 添加错误捕获逻辑,处理未授权场景:
    try
    {
        await _hubConnection.StartAsync();
    }
    catch (HttpRequestException ex) when (ex.StatusCode == System.Net.HttpStatusCode.Unauthorized)
    {
        // 提示用户重新认证或处理授权失败逻辑
        Console.WriteLine("连接失败:未授权,请检查账户权限");
    }
    

6. 确认WebSocket在IIS中的状态

  • 检查服务器已安装WebSocket协议(服务器管理器 → 添加角色功能 → Web服务器 → 应用开发 → WebSocket协议)
  • 打开站点的配置编辑器,定位到system.webServer/webSocket,确保enabled属性设置为True

7. 切换到HTTPS(可选但推荐)

错误日志提示HTTP连接不安全,建议配置HTTPS证书并启用UseHttpsRedirection():

app.UseHttpsRedirection();

避免因混合内容导致的潜在认证问题。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 02:37:33