using System.Net;
using NewLife.Log;
namespace NewLife.Http;
/// <summary>HTTP中间件委托</summary>
/// <param name="context">Http上下文</param>
/// <param name="next">下一个中间件或处理器的调用委托</param>
/// <returns>异步任务</returns>
public delegate Task HttpMiddlewareDelegate(IHttpContext context, Func<Task> next);
/// <summary>CORS 跨域中间件</summary>
/// <remarks>
/// <para>自动处理 OPTIONS 预检请求和设置 CORS 响应头。</para>
/// <para>
/// 响应头取值只来自配置,不随请求源变化,因此无需 <c>Vary: Origin</c>;
/// 若将来改为按请求源回显,必须同时补上该头部,否则共享缓存会把某个源的响应发给其它源。
/// </para>
///
/// <code>
/// server.Use(new CorsMiddleware
/// {
/// AllowOrigin = "*",
/// AllowMethods = "GET, POST, PUT, DELETE",
/// AllowHeaders = "Content-Type, Authorization",
/// AllowCredentials = false,
/// MaxAge = 3600
/// });
/// </code>
/// </remarks>
public class CorsMiddleware
{
#region 属性
/// <summary>允许的源。默认 *</summary>
public String AllowOrigin { get; set; } = "*";
/// <summary>允许的方法。默认 GET, POST, PUT, DELETE, OPTIONS</summary>
public String AllowMethods { get; set; } = "GET, POST, PUT, DELETE, OPTIONS";
/// <summary>允许的请求头。默认 Content-Type, Authorization, X-Requested-With</summary>
public String AllowHeaders { get; set; } = "Content-Type, Authorization, X-Requested-With";
/// <summary>暴露的响应头</summary>
public String? ExposeHeaders { get; set; }
/// <summary>是否允许携带凭据</summary>
public Boolean AllowCredentials { get; set; }
/// <summary>预检缓存时间(秒)</summary>
public Int32 MaxAge { get; set; } = 3600;
#endregion
/// <summary>中间件入口</summary>
/// <param name="context">Http上下文</param>
/// <param name="next">下一个中间件</param>
/// <returns></returns>
public Task Invoke(IHttpContext context, Func<Task> next)
{
var req = context.Request;
// 设置 CORS 响应头
SetCorsHeaders(context);
// 处理 OPTIONS 预检请求
if (req?.Method == "OPTIONS")
{
context.Response.StatusCode = HttpStatusCode.NoContent;
return TaskEx.CompletedTask;
}
return next();
}
private void SetCorsHeaders(IHttpContext context)
{
var res = context.Response;
if (!AllowOrigin.IsNullOrEmpty())
{
// 通配源与凭据同用被 Fetch 规范禁止(浏览器直接拒收该响应),属于配置错误。
// 此处失败关闭:不发通配源并警示,而不是按请求源回显——回显等于把“任意源 + 凭据”放行,
// 属 CORS 反射型漏洞,对 Cookie 鉴权的后台即 CSRF 与数据泄露面。要用凭据请把 AllowOrigin 设为明确的源
if (AllowCredentials && AllowOrigin.EqualIgnoreCase("*"))
XTrace.WriteLine("[CorsMiddleware] 配置无效:AllowOrigin=* 与 AllowCredentials=true 不能同时使用,已忽略 Access-Control-Allow-Origin。请把 AllowOrigin 设置为明确的源。");
else
res.Headers["Access-Control-Allow-Origin"] = AllowOrigin;
}
if (!AllowMethods.IsNullOrEmpty())
res.Headers["Access-Control-Allow-Methods"] = AllowMethods;
if (!AllowHeaders.IsNullOrEmpty())
res.Headers["Access-Control-Allow-Headers"] = AllowHeaders;
if (!ExposeHeaders.IsNullOrEmpty())
res.Headers["Access-Control-Expose-Headers"] = ExposeHeaders;
if (AllowCredentials)
res.Headers["Access-Control-Allow-Credentials"] = "true";
if (MaxAge > 0)
res.Headers["Access-Control-Max-Age"] = MaxAge + "";
}
}
|