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
相关产品推荐
相关产品推荐

