.NET Core部署Azure App Service后SignalR报405方法不允许错误
问题描述
本地运行.NET Core Web API的SignalR配置完全正常,但部署到Azure App Service后,连接SignalR时收到405 Method Not Allowed错误。代码配置如下:
using AutoMapper; using Azure.Storage.Blobs; using DinkToPdf; using DinkToPdf.Contracts; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Authorization; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Hosting; using Microsoft.Identity.Client; using Microsoft.IdentityModel.Tokens; using Microsoft.OpenApi.Models; using System; using System.Reflection; using System.Text; var builder = WebApplication.CreateBuilder(args); builder.Services.AddCors(options => { options.AddPolicy("CorsPolicy", builder => builder.AllowAnyMethod() .SetIsOriginAllowed(_ => true) .AllowAnyHeader() .AllowCredentials()); }); builder.Services.AddScoped<IDapperWrapper, DapperWrapper>(); //Use below code for global authorization and add AllowAnonymous to login route. //otherwise use Authorize in the required controller and/or action builder.Services.AddControllers(options => { options.Filters.Add(new AuthorizeFilter()); }); builder.Services.AddApiVersioning(x => { x.DefaultApiVersion = new ApiVersion(1, 0); x.AssumeDefaultVersionWhenUnspecified = true; x.ReportApiVersions = true; }); builder.Services.AddAntiforgery(antiforgeryOptions => { antiforgeryOptions.HeaderName = "X-XSRF-TOKEN"; antiforgeryOptions.Cookie.HttpOnly = true; antiforgeryOptions.Cookie.SecurePolicy = CookieSecurePolicy.Always; }); #region --> JWT Authentication builder.Services.Configure<JWTSettings>(options => { builder.Configuration.GetSection("Jwt").Bind(options); var securityKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(options.Secret)); options.ValidFor = TimeSpan.FromMinutes(Convert.ToInt32(builder.Configuration["Jwt:ValidFor"])); options.SigningCredentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256); }); JWTSettings jwtSettings = builder.Configuration.GetSection("Jwt").Get<JWTSettings>(); builder.Services.AddJWTAuthentication(jwtSettings); #endregion builder.Services.AddSingleton<CustomIDataProtection>(); builder.Services.AddSingleton(typeof(IConverter), new SynchronizedConverter(new PdfTools())); builder.Services.AddSignalR(o => { o.EnableDetailedErrors = true; }); var app = builder.Build(); if (app.Environment.IsDevelopment()) { app.UseDeveloperExceptionPage(); } else { app.UseHsts(); } app.UseMiddleware<ExceptionMiddleware>(); app.UseHttpsRedirection(); app.UseAuthentication(); app.UseRouting(); app.UseCors("CorsPolicy"); app.UseAntiforgeryToken(); app.UseAuthorization(); app.Use(async (context, next) => { if (!context.Response.Headers.ContainsKey("X-Frame-Options")) { context.Response.Headers.Add("X-Frame-Options", "SAMEORIGIN"); } await next(); }); app.UseEndpoints(endpoints => { endpoints.MapControllerRoute( name: "api", pattern: "{controller}/{action}/{id?}"); endpoints.MapControllerRoute( name: "api", pattern: "{controller}/{action}/{name}"); endpoints.MapControllerRoute( name: "api", pattern: "{controller}/{action}/{date}"); endpoints.MapControllerRoute( name: "api", pattern: "{controller}/{action}/{email}"); }); if (bool.Parse(builder.Configuration["UseSwagger"])) { app.UseSwagger(); app.UseSwaggerUI(c => { c.SwaggerEndpoint("/swagger/v1/swagger.json", "API"); c.DefaultModelsExpandDepth(-1); }); } app.MapHub<HjsHub>("/hjsHub"); app.Run();
排查与解决步骤
一、代码层面调整
修正SignalR Hub映射顺序
当前代码中app.MapHub<HjsHub>("/hjsHub")放在了UseEndpoints块外部,可能导致路由匹配优先级异常,控制器路由会先拦截SignalR的握手请求(如OPTIONS方法),返回405。
修改方式:将Hub映射移至UseEndpoints内部:app.UseEndpoints(endpoints => { // 保留现有控制器路由配置 endpoints.MapControllerRoute( name: "api", pattern: "{controller}/{action}/{id?}"); // ...其他控制器路由 // 添加SignalR Hub映射 endpoints.MapHub<HjsHub>("/hjsHub"); });Antiforgery验证适配
代码中启用了Antiforgery,SignalR连接时需携带X-XSRF-TOKEN头:- 客户端需添加请求头:
const connection = new signalR.HubConnectionBuilder() .withUrl("/hjsHub", { headers: { "X-XSRF-TOKEN": document.cookie.split('; ').find(row => row.startsWith('XSRF-TOKEN=')).split('=')[1] } }) .build(); - 若无需对SignalR做Antiforgery验证,可给
HjsHub类添加[IgnoreAntiforgeryToken]特性。
- 客户端需添加请求头:
二、Azure App Service配置调整
启用WebSockets协议
SignalR默认依赖WebSockets传输,Azure App Service默认未启用该协议:- 登录Azure门户,进入目标App Service
- 打开配置 > 常规设置
- 将Web sockets设置为启用,保存后重启服务
统一CORS配置
Azure门户的CORS设置会覆盖代码中的配置,避免双重配置:- 进入App Service的CORS设置
- 若代码已处理CORS,选择禁用CORS;若需门户配置,添加客户端源地址并勾选允许凭据
检查HTTP版本
确保App Service使用HTTP/2(SignalR对其支持更优):- 在配置 > 常规设置中,将HTTP版本设置为2.0
排查URL重写规则
若配置了URL重写规则,确保/hjsHub路径未被拦截或重写,可在配置 > 路径映射中检查重写规则。
三、额外排查
- 启用SignalR详细错误日志,在Azure App Service的监控 > 日志中查看具体错误详情
- 直接访问
https://<你的App Service域名>/hjsHub/negotiate,观察返回的状态码与内容,定位请求拦截点
内容的提问来源于stack exchange,提问作者Mahesh Kantariya
相关产品推荐
相关产品推荐

