YARP反向代理场景下如何调整请求头解决CORS跨域错误?
问题场景
当前部署了基于YARP的反向代理站点,入口域名为test.a.com,负责接收所有传入请求并转发到对应目标网站,默认规则下所有请求会转发到旧站点https://test-www.a.com/,基础转发功能运行正常。
YARP配置如下:
{ "Logging": { "LogLevel": { "Default": "Information", "Microsoft.AspNetCore": "Warning" } }, "AllowedHosts": "*", "ReverseProxy": { "Routes": { "server": { "ClusterId": "old-site", "Match": { "Path": "{**catch-all}" } }, "azure": { "ClusterId": "new-site", "Match": { "Path": "yarpb" } } }, "Clusters": { "old-site": { "Destinations": { "server": { "Address": "https://test-www.a.com/" } } }, "new-site": { "Destinations": { "yarpb": { "Address": "https://example.com/" } } } } } }
目前Umbraco CMS后台(访问路径test.a.com/umbraco)加载异常:后台运行时会从app_plugins目录拉取资源文件,部分资源拉取请求触发CORS跨域校验,原始HTML请求触发重定向时直接抛出跨域异常:
CORS跨域校验报错截图
重定向引发跨域异常截图
已尝试在代码中添加app.UseCors(x => x.AllowAnyOrigin());配置允许所有来源跨域,但未生效:从YARP运行日志看代理请求最终返回200响应,但浏览器端提示405错误,判断CORS中间件在YARP完成请求重定向后才执行,未对代理响应生效。
相关运行日志片段如下:
2022-06-21T17:48:02.6237461+02:00 INFO [Yarp.ReverseProxy.Forwarder.HttpForwarder] [Forwarding] Proxying to https://test-www.a.com/App_Plugins/RJP.MultiUrlPicker/MultiUrlPicker.html HTTP/2 RequestVersionOrLower no-streaming 2022-06-21T17:48:02.6255128+02:00 INFO [Yarp.ReverseProxy.Forwarder.HttpForwarder] [ResponseReceived] Received HTTP/2.0 response 301. 2022-06-21T17:48:02.6256100+02:00 INFO [ReverseProxy.Middleware.RequestResponseLoggerMiddleware] [LogRequest] https://test.a.com/App_Plugins/RJP.MultiUrlPicker/MultiUrlPicker.html proxied to https://test-www.a.com//App_Plugins/RJP.MultiUrlPicker/MultiUrlPicker.html 2022-06-21T17:48:02.6273081+02:00 INFO [Yarp.ReverseProxy.Forwarder.HttpForwarder] [ResponseReceived] Received HTTP/2.0 response 200.
项目原有Program.cs代码如下:
var builder = WebApplication.CreateBuilder(args); builder.Services.AddReverseProxy().LoadFromConfig(builder.Configuration.GetSection("ReverseProxy")); builder.Services.AddLogging(x => { x.AddJsonConsole(); x.AddFile($"logs/app-{DateTime.UtcNow:yyyyMMddHHmmss}.log", append: true); }); var app = builder.Build(); app.UseStaticFiles(); app.UseRouting(); app.UseCors(x => x.AllowAnyOrigin()); app.MapReverseProxy(proxyPipeline => { proxyPipeline.UseRequestResponseLogging(); }); app.Run();
问题根因
- 中间件执行顺序错误:ASP.NET Core中间件严格按注册顺序执行,原有代码中CORS中间件注册在YARP代理映射外层,YARP转发后拿到的上游301重定向响应会直接返回给客户端,不会经过外层CORS中间件处理,导致跨域头缺失。
- 路径拼接错误触发不必要重定向:从运行日志可见,转发后的目标地址出现双斜杠
https://test-www.a.com//App_Plugins/...,上游Umbraco站点识别到路径异常会返回301重定向到正确路径,重定向响应的Location头直接指向源站域名,浏览器收到后直接判定跨域。 - 响应头未做代理层转换:上游站点返回的跨域相关头、重定向地址头没有经过代理层修正就直接透传,和代理入口域名不匹配,触发浏览器跨域拦截。
- OPTIONS预检请求未正确处理:浏览器跨域请求前会发送OPTIONS预检请求,原有逻辑将该请求转发到源站,源站未配置对应跨域响应时会返回405错误,和浏览器端报错现象一致。
修复方案
1. 调整中间件顺序,将CORS逻辑嵌入YARP代理管道
不要仅在代理外层注册CORS中间件,需要把CORS处理加入YARP自身的代理管道,保证代理返回的所有响应(包括301重定向响应)都能被正确添加跨域头。
2. 配置YARP请求/响应转换规则
- 修复路径拼接时的双斜杠问题,避免触发上游不必要的301重定向
- 重写上游3xx响应的Location头,将源站域名替换为代理入口域名,避免跳转暴露源站地址
- 统一在代理层添加跨域响应头,覆盖上游可能返回的错误跨域配置
3. 全局拦截处理OPTIONS预检请求
在代理层直接响应OPTIONS预检请求,不要转发到源站,避免源站返回405错误。
修改后的完整Program.cs代码如下:
var builder = WebApplication.CreateBuilder(args); builder.Services.AddReverseProxy() .LoadFromConfig(builder.Configuration.GetSection("ReverseProxy")) // 注册请求/响应转换规则 .AddTransforms(transformBuilder => { // 移除转发路径开头的斜杠,避免和集群地址结尾的斜杠拼接成双斜杠 transformBuilder.AddPathRemovePrefix("/"); // 重写3xx重定向的Location头,替换源站域名为代理入口域名 transformBuilder.AddResponseHeader("Location", value => { if (value.StartsWith("https://test-www.a.com/", StringComparison.OrdinalIgnoreCase)) { return value.Replace("https://test-www.a.com/", "https://test.a.com/"); } return value; }, append: false); // 统一写入跨域响应头,始终覆盖上游返回的同名头 transformBuilder.AddResponseHeader("Access-Control-Allow-Origin", "*", append: false); transformBuilder.AddResponseHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS", append: false); transformBuilder.AddResponseHeader("Access-Control-Allow-Headers", "*", append: false); }); builder.Services.AddLogging(x => { x.AddJsonConsole(); x.AddFile($"logs/app-{DateTime.UtcNow:yyyyMMddHHmmss}.log", append: true); }); // 注册CORS服务 builder.Services.AddCors(options => { options.AddDefaultPolicy(policy => { policy.AllowAnyOrigin() .AllowAnyMethod() .AllowAnyHeader(); }); }); var app = builder.Build(); app.UseStaticFiles(); app.UseRouting(); // 外层CORS中间件处理非代理请求 app.UseCors(); // 全局拦截OPTIONS预检请求,直接返回200 app.Use(async (context, next) => { if (context.Request.Method.Equals("OPTIONS", StringComparison.OrdinalIgnoreCase)) { context.Response.StatusCode = StatusCodes.Status200OK; return; } await next(); }); app.MapReverseProxy(proxyPipeline => { // 代理管道内添加CORS中间件,保证所有代理响应都经过跨域处理 proxyPipeline.UseCors(); proxyPipeline.UseRequestResponseLogging(); }); app.Run();
内容的提问来源于stack exchange,提问作者J.Down
相关产品推荐
相关产品推荐

