Azure SignalR对接Xamarin时NegotiateAsync返回401未授权问题
环境与配置
服务器端配置(.NET 6)
public void ConfigureServices(IServiceCollection services) { services.AddSignalR(options => { options.EnableDetailedErrors = true; }) .AddAzureSignalR(options => { options.InitialHubServerConnectionCount = 1; options.ConnectionString = "xxxx" }); } protected virtual void ConfigureAuthentication(IServiceCollection services) { services .AddAuthentication(options => { options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme; }) .AddJwtBearer(options => { options.Authority = $"{AzureB2CConfig.Instance}/{AzureB2CConfig.Domain}/{AzureB2CConfig.SignUpSignInPolicyId}/v2.0/"; options.Audience = AzureB2CConfig.Audience; options.Events = new JwtBearerEvents { OnMessageReceived = context => { var authToken = context.Request.Headers["Authorization"].ToString().Replace("Bearer ", ""); var path = context.HttpContext.Request.Path; if (!string.IsNullOrEmpty(authToken) && (path.StartsWithSegments("/myhubs"))) { context.Token = authToken; } return Task.CompletedTask; } }; options.TokenValidationParameters = new TokenValidationParameters { LifetimeValidator = (before, expires, token, param) => { return expires > DateTime.UtcNow; }, ValidateAudience = false, ValidateIssuer = false, ValidateActor = false, ValidateLifetime = true, ValidateIssuerSigningKey = false }; }); services.AddAuthorization(options => { options.AddPolicy("AllowedUser", policy => { policy.Requirements.Add(new AllowedUserRequirement()); }); }); } public void Configure(IApplicationBuilder app) { app.UseAuthentication(); app.UseAuthorization(); app.UseFileServer(); app.UseEndpoints(routes => { routes.MapHub<MyHub>("/myhubs"); }); app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); }
客户端配置
var connection = new HubConnectionBuilder() .WithUrl(SignalRServerUrl, options => { options.AccessTokenProvider = authenticationService.GetAccessToken; // 返回有效的访问令牌 }) .WithAutomaticReconnect() .Build(); await Policy .Handle<Exception>() .WaitAndRetryAsync(1, x => TimeSpan.FromMilliseconds(500)) .ExecuteAsync(() => _connection.StartAsync()); // 触发401未授权错误
问题描述
/negotiate请求返回401(未授权),尝试多种方案并查阅相关技术文章均未解决,框架版本为.NET 6。
2022年10月20日更新:添加services.AddAuthorization()配置后,应用与自建服务器之间的认证已成功,但应用与Azure SignalR服务之间的认证仍失败,返回401未授权。询问是否存在缺失配置。
解决方案
针对Azure SignalR与客户端间的401问题,需检查以下关键配置点:
1. 验证Azure SignalR连接字符串有效性
确认options.ConnectionString中的值是从Azure Portal获取的完整有效连接字符串,包含正确的Endpoint和Access Key,避免因密钥错误或格式问题导致服务端无法与Azure SignalR建立信任连接。
2. 为Hub绑定授权策略
已定义AllowedUser策略,但未将其应用到Hub实例上。需在Hub类添加特性,强制仅符合策略的用户可访问:
[Authorize(Policy = "AllowedUser")] public class MyHub : Hub { // Hub业务方法实现 }
3. 优化JWT认证的Token获取逻辑
SignalR客户端默认会通过QueryString的access_token参数传递令牌,而非仅依赖Authorization头。需调整OnMessageReceived事件处理逻辑,同时支持两种传递方式:
OnMessageReceived = context => { // 同时处理QueryString和Header中的令牌 var token = context.Request.Query["access_token"].FirstOrDefault(); if (string.IsNullOrEmpty(token)) { token = context.Request.Headers["Authorization"].ToString().Replace("Bearer ", ""); } var path = context.HttpContext.Request.Path; if (!string.IsNullOrEmpty(token) && path.StartsWithSegments("/myhubs")) { context.Token = token; } return Task.CompletedTask; }
4. 确认客户端Token的有效性
使用JWT解析工具(如jwt.io)验证authenticationService.GetAccessToken()返回的令牌:
- 检查
exp声明,确保令牌未过期 - 确认令牌包含的声明满足
AllowedUser策略的验证要求 - 确保令牌能被服务器端的JWT验证逻辑通过
5. 检查中间件顺序
当前Configure方法中UseAuthentication()和UseAuthorization()已置于UseEndpoints之前,此顺序正确,无需调整,但需避免其他自定义中间件干扰认证流程。
内容的提问来源于stack exchange,提问作者Takeo Nishioka

