深入剖析.NETCORE中CORS(跨站资源共享)
前言
由于现代互联网的飞速发展,我们在开发现代 Web 应用程序中,经常需要考虑多种类型的客户端访问服务的情况;而这种情况放在15年前几乎是不可想象的,在那个时代,我们更多的是考虑怎么把网页快速友好的嵌套到服务代码中,经过服务器渲染后输出HTML到客户端,没有 iOS,没有 Android,没有 UWP。更多的考虑是 防止 XSS,在当时的环境下,XSS一度成为各个站长的噩梦,甚至网站开发的基本要求都要加上:必须懂防 XSS 攻击。
CORS 定义
言归正传,CORS(Cross-Origin Resource Sharing)是由 W3C 指定的标准,其目的是帮助在各个站点间的资源共享。CORS 不是一项安全标准,启用 CORS 实际上是让站点放宽了安全标准;通过配置 CORS,可以允许配置中的请求源执行允许/拒绝的动作。
在 .NETCore 中启用 CORS
在 .NETCore 中,已经为我们集成好 CORS 组件 Microsoft.AspNetCore.Cors,在需要的时候引入该组件即可,Microsoft.AspNetCore.Cors 的设计非常的简洁,包括两大部分的内容,看图:
从上图中我们可以看出,左边是入口,是我们常见的 AddCors/UseCors,右边是 CORS 的核心配置和验证,配置对象是 CorsPolicyBuilder 和 CorsPolicy,验证入口为 CorsService,中间件 CorsMiddleware 提供了拦截验证入口。
CorsService 是整个 CORS 的核心实现,客户端的请求流经中间件或者AOP组件后,他们在内部调用 CorsService 的相关验证方法,在 CorsService 内部使用配置好的 PolicyName 拉去相关策略进行请求验证,最终返回验证结果到客户端。
Microsoft.AspNetCore.Mvc.Cors
通常情况下,我们会在 Startup 类中的 ConfigureServices(IServiceCollection services) 方法内部调用 AddCors() 来启用 CROS 策略,但是,该 AddCors() 并不是上图中 CorsServiceCollectionExrensions 中的 AddCors 扩展方法。
实际上,在 ConfigureServices 中调用的 AddCors 是处于程序集 Microsoft.AspNetCore.Mvc.Cors ;在 Microsoft.AspNetCore.Mvc.Cors 内部的扩展方法 AddCors() 中,以 AOP 方式定义了对 EnableCorsAttribute/DisableCorsAttributeAttribute 的拦截检查。
具体做法是在程序集 Microsoft.AspNetCore.Mvc.Cors 内部,定义了类 CorsApplicationModelProvider ,当我们调用 AddCors 扩展方法的时候,将进一步调用 CorsApplicationModelProvider.OnProvidersExecuting(ApplicationModelProviderContext context) 方法,从而执行检查 EnableCorsAttribute/DisableCorsAttributeAttribute 策略。
所以,我们在 ConfigureServices 中调用的 AddCore,其实是在该程序集内部定义的类: MvcCorsMvcCoreBuilderExtensions 的扩展方法,我们看 MvcCorsMvcCoreBuilderExtensions 的定义
public static class MvcCorsMvcCoreBuilderExtensions
{
public static IMvcCoreBuilder AddCors(this IMvcCoreBuilder builder)
{
...
AddCorsServices(builder.Services);
...
}
public static IMvcCoreBuilder AddCors(this IMvcCoreBuilder builder,Action<CorsOptions> setupAction)
{
...
AddCorsServices(builder.Services);
...
}
public static IMvcCoreBuilder ConfigureCors(this IMvcCoreBuilder builder,Action<CorsOptions> setupAction)
{
...
}
// Internal for testing.
internal static void AddCorsServices(IServiceCollection services)
{
services.AddCors();
services.TryAddEnumerable(
ServiceDescriptor.Transient<IApplicationModelProvider, CorsApplicationModelProvider>());
services.TryAddTransient<CorsAuthorizationFilter, CorsAuthorizationFilter>();
}
}
重点就在上面的 AddCorsServices(IServiceCollection services) 方法中, 在方法中调用了 CORS 的扩展方法 AddCors()。
那么我们就要问, CorsApplicationModelProvider 是在什么时候被初始化的呢?
答案是在 startup 中 ConfigureServices(IServiceCollection services) 方法内调用 services.AddControllers() 的时候。在AddControllers() 方法内部,调用了 AddControllersCore 方法
private static IMvcCoreBuilder AddControllersCore(IServiceCollection services)
{
// This method excludes all of the view-related services by default.
return services
.AddMvcCore()
.AddApiExplorer()
.AddAuthorization()
.AddCors()
.AddDataAnnotations()
.AddFormatterMappings();
}
理解了 CORS 的执行过程,下面我们就可以开始了解应该怎么在 .NETCore 中使用 CORS 的策略了
CORS 启用的三种方式
在 .NETCore 中,可以通过以下三种方式启用 CORS
1、使用默认策略/命名策略的中间件的方式
2、终结点路由 + 命名策略
3、命名策略 + EnableCorsAttribute
通过上面的三种方式,可以灵活在程序中控制请求源的走向,但是,残酷的事实告诉我们,一般情况下,我们都是会对全站进行 CORS。所以,现实情况就是在大部分的 Web 应用程序中, CORS 已然成为皇帝的新装,甚至有点累赘。
CorsPolicyBuilder(CORS策略)
通过上面的 CORS 思维导图,我们已经大概了解了 CORS 的整个结构。由上图我们知道,CorsPolicyBuilder 位于命名空间 Microsoft.AspNetCore.Cors.Infrastructure 中。
在内部提供了两种基础控制策略:全开/半开。这两种策略都提供了基本的方法供开发者直接调用,非常的贴心。
全开
public CorsPolicyBuilder AllowAnyHeader();
public CorsPolicyBuilder AllowAnyMethod();
public CorsPolicyBuilder AllowAnyOrigin();
public CorsPolicyBuilder AllowCredentials();
半开
public CorsPolicyBuilder DisallowCredentials();
public CorsPolicyBuilder WithHeaders(params string[] headers);
public CorsPolicyBuilder WithMethods(params string[] methods);
public CorsPolicyBuilder WithOrigins(params string[] origins);
上面的策略定义从字面理解就可以知道其用途,实际上呢,他们的实现原理也是非常的简单。在 CorsPolicyBuilder 内部维护着一个 CorsPolicy 对象,当你使用全开/半开方式配置策略的时候,builder 会将配置写入内部 CorsPolicy 中存储备用。
比如半开 WithOrigins(params string[] origins);,通过迭代器将配置的源写入 _policy.Origins 中。
public CorsPolicyBuilder WithOrigins(params string[] origins)
{
foreach (var origin in origins)
{
var normalizedOrigin = GetNormalizedOrigin(origin);
_policy.Origins.Add(normalizedOrigin);
}
return this;
}
开始使用
在理解了配置的过程后,我们就可以进入真正的使用环节了,通过上面的学习我们知道,启用 CORS 有三种方式,咱们一步一步来。
使用默认策略/命名策略的中间件的方式
所谓的命名策略就是给你的策略起个名字,默认策略就是没有名字,所有的入口都使用同一个策略,下面的代码演示了命名策略
private readonly string CORS_ALLOW_ORGINS = "cors_allow_orgins";
public void ConfigureServices(IServiceCollection services)
{
services.AddCors(options =>
{
options.AddPolicy(CORS_ALLOW_ORGINS, policy =>
{
policy.WithOrigins("http://localhost:5500", "http://localhost:8099");
});
});
services.AddControllers().AddJsonOptions(options =>
{
options.JsonSerializerOptions.Converters.Add(new StringJsonConverter());
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseRouting();
app.UseCors(CORS_ALLOW_ORGINS);
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
上面的代码演示了如何在站点中全局终结点启用 CORS,首先声明了命名策略 cors_allow_orgins ,然后将其用 AddCors() 添加到 CORS 中,最后使用 UseCors() 启用该命名策略,需要注意的是,AddCors() 和 UseCors() 必须成对出现,并且要使用同一个命名策略。
终结点路由 + 命名策略
.NETCore 支持通过对单个路由设置 CORS 命名策略,从而可以实现在一个系统中,对不同的业务提供个性化的支持。终结点路由 + 命名策略的配置和上面的命名策略基本相同,仅仅是在配置路由的时候,只需要对某个路由增加 RequireCors 的配置即可
private readonly string CORS_ALLOW_ORGINS = "cors_allow_orgins";
public void ConfigureServices(IServiceCollection services)
{
services.AddCors(options =>
{
options.AddPolicy(CORS_ALLOW_ORGINS, policy =>
{
policy.WithOrigins("http://localhost:5500", "http://localhost:8099");
});
});
services.AddControllers().AddJsonOptions(options =>
{
options.JsonSerializerOptions.Converters.Add(new StringJsonConverter());
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseRouting();
app.UseCors();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute("weatherforecast", "{controller=WeatherForecast}/{action=Get}").RequireCors(CORS_ALLOW_ORGINS);
// endpoints.MapControllers();
});
}
上面的代码,指定了路由 weatherforecast 需要执行 CORS 策略 CORS_ALLOW_ORGINS。通过调用 RequireCors() 方法,传入策略名称,完成 CORS 的配置。RequireCors 方法是在程序集 Microsoft.AspNetCore.Cors 内部的扩展方法,具体是怎么启用策略的呢,其实就是在内部给指定的终结点路由增加了 EnableCorsAttribute ,这就是下面要说到的第三种启用 CORS 的方式。
来看看 RequireCors() 内部的代码
public static TBuilder RequireCors<TBuilder>(this TBuilder builder, string policyName) where TBuilder : IEndpointConventionBuilder
{
if (builder == null)
{
throw new ArgumentNullException(nameof(builder));
}
builder.Add(endpointBuilder =>
{
endpointBuilder.Metadata.Add(new EnableCorsAttribute(policyName));
});
return builder;
}
命名策略 + EnableCorsAttribute
最后一种启用 CORS 的方式是使用 EnableCorsAttribute 特性标记,和 RequireCors 方法内部的实现不同的是,这里说的 EnableCorsAttribute 是显式的指定到控制器上,在应用 EnableCorsAttribute 的时候,你可以应用到根控制器或者子控制器上,如果是对根控制器进行标记,被标记的根控制器和他的所有子控制器都将受指定 CORS 策略的影响;反之,如果只是对子控制器进行标记,CORS 策略也只对当前控制器产生影响。
CORS 的初始化
public void ConfigureServices(IServiceCollection services)
{
services.AddCors(options =>
{
options.AddPolicy("controller_cors", policy =>
{
policy.WithOrigins("http://localhost:5500", "http://localhost:8099");
});
options.AddPolicy("action_cors", policy =>
{
policy.WithOrigins("http://localhost:5500", "http://localhost:8099");
});
});
services.AddControllers().AddJsonOptions(options =>
{
options.JsonSerializerOptions.Converters.Add(new StringJsonConverter());
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseRouting();
app.UseCors();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
在上面的代码中,因为 EnableCorsAttribute 可以应用到类和属性上,所以我们定义了两个 CORS 策略,分别是 controller_cors 和 action_cors。接下来将这两种策略应用到 WeatherForecastController 上。
应用 EnableCorsAttribute 特性标记
[ApiController]
[Route("[controller]")]
[EnableCors("controller_cors")]
public class WeatherForecastController : ControllerBase
{
[EnableCors("action_cors")]
[HttpPost]
public string Users()
{
return "Users";
}
[DisableCors]
[HttpGet]
public string List()
{
return "List";
}
[HttpGet]
public string Index()
{
return "Index";
}
}
在上面的 WeatherForecastController 控制器中,我们将 controller_cors 标记到控制器上,将 action_cors 标记到 Action 名称为 Users 上面,同时,还对 List 应用了 DisableCors ,表示对 List 禁用 CORS 的策略,所以我们知道,在 CORS 中,有 AddCors/UseCors,也有 EnableCors/DisableCors ,都是成对出现的。
其它策略
我们还记得,在 .NETCore 中,一共有 4 种策略,分别是:Header、Method、Origin、Credentials,但是本文仅演示了 WithOrigins 这一种方式,相信通过这一种方式的演示,对大家在启用其它策略的时候,其思想也是一致的,所谓的标头、请求方式、凭据 等等,其基本法是不变的。
通过对 Microsoft.AspNetCore.Cors 的内部实现的剖析,我们了解到,其实现 CORS 的原理非常简单,结构清晰,就算不用系统自带的 CORS 组件,自行实现一个 CORS 策略,也是非常容易的。
参考资料:
(CORS) 启用跨域请求 ASP.NET Core
GitHub:
https://github.com/dotnet/aspnetcore/tree/master/src/Mvc/Mvc/src
https://github.com/dotnet/aspnetcore/tree/master/src/Mvc/Mvc.Cors/src
https://github.com/dotnet/aspnetcore/tree/master/src/Middleware/CORS/src
深入剖析.NETCORE中CORS(跨站资源共享)的更多相关文章
- SpringSecurity环境下配置CORS跨站资源共享规则
一.CORS简述 要说明CORS(Cross Origin Resourse-Sharing) 跨站资源共享,就必须先说同源策略.长话短说,同源策略就是向服务端发起请求的时候,以下三项必须与当前浏览器 ...
- 禁止chrome中CORS跨域资源共享错误
在开发中,可以通过命令行命令chrome --allow-file-access-from-files来 禁止CORS错误. 只在紧急情况下使用这个方法,比如你的老板正站在你身后, 并且所有事情都无法 ...
- CORS(跨站资源共享)介绍
起因 有同学在nginx站点配置中加了一行Access-Control-Allow-Origin *,导致微信中业务数据异常,抓包看http头有两个Access-Control-Allow-Origi ...
- 跨站资源共享CORS原理深度解析
我相信如果你写过前后端分离的web应用程序,或者写过一些ajax请求调用,你可能会遇到过CORS错误. CORS是什么? 它与安全性有关吗? 为什么要有CORS?它解决了什么目的? CORS是怎样运行 ...
- 在ASP.NET Web API中实现CORS(跨域资源共享)
默认情况下,是不允许网页从不同的域访问服务器资源的,访问遵循"同源"策略的原则. 会遇到如下的报错: XMLHttpRequest cannot load http://local ...
- CORS跨域资源共享
CORS(跨域资源共享)跨域问题及解决 当使用ajax跨域请求时,浏览器报错:XmlHttpRequest error: Origin null is not allowed by Access-Co ...
- CORS跨域资源共享你该知道的事儿
"唠嗑之前,一些客套话" CORS跨域资源共享,这个话题大家一定不陌生了,吃久了大转转公众号的深度技术好文,也该吃点儿小米粥溜溜胃里的缝儿了,今天咱们就再好好屡屡CORS跨域资源共 ...
- django上课笔记7-jQuery Ajax 和 原生Ajax-伪造的Ajax-三种Ajax上传文件方法-JSONP和CORS跨域资源共享
一.jQuery Ajax 和 原生Ajax from django.conf.urls import url from django.contrib import admin from app01 ...
- 跨域漏洞丨JSONP和CORS跨域资源共享
进入正文之前,我们先来解决个小问题,什么是跨域? 跨域:指的是浏览器不能执行其它网站的脚本,它是由浏览器的同源策略造成的,是浏览器的安全限制! 跨域常见的两种方式,分别是JSONP和CORS. 今天i ...
随机推荐
- Python表达式与生成式
Python表达式与生成式 前言 本章节中的所有知识点均为在不丧失代码可读性的前提下最大程度精简代码的一系列操作.其中涉及到一些性能问题(微乎其微)可以不做考虑. 三元表达式 三元表达式中有三个重要的 ...
- IDEA2019版中文汉化包
废话不多说,上才艺 E G M~~~~~ 2020版的IDEA大佬可以无视........ 1.打开IDEA文件目录 2.打开lib目录--将汉化版复制到该目录下 3.打开IDEA查看效果 高铁链 ...
- eclipse使用git提交代码
准备工作: 目的:eclipse使用git提交本地项目,提交至远程github上 eclipse版本:eclipse4.5 64位 jdk版本:jdk-1.7 64位 项目类型:maven web项 ...
- 从别人的代码中学习golang系列--01
自己最近在思考一个问题,如何让自己的代码质量逐渐提高,于是想到整理这个系列,通过阅读别人的代码,从别人的代码中学习,来逐渐提高自己的代码质量.本篇是这个系列的第一篇,我也不知道自己会写多少篇,但是希望 ...
- 硬刚 lodash 源码之路,compact & concat
前置 本篇随笔包含 _.compact 和 _.concat 及其依赖的工具函数. 你可能需要一些 JavaScript 基础知识才能看懂一些没有注释的细节. compact _.compact(ar ...
- HTML的<Object>标签怎么用?
<object>标签是一个HTML标签,用于在网页中显示音频,视频,图像,PDF和Flash等多媒体:它通常用于嵌入由浏览器插件处理的Flash页面元素,如Flash和Java项目.它还可 ...
- Java贪吃蛇小游戏
贪吃蛇 思路 首先构思游戏布局,计算合理的坐标系. 绘制静态数据(广告.初始小蛇.提示信息.棋盘) 添加键盘监听事件,改变游戏状态以及小蛇运动方向 添加定时器,让小蛇在一段时间内移动一定的距离 随机产 ...
- rem和px
做过一段时间的H5页面,但是对于rem与px的换算还是比较模糊,总是引用一段脚本,也没有深究过为什么,就稀里糊涂的用了,遇到一些细微的地方,总是不知道是什么原因导致的,我总是只要能完成效果就行,全然不 ...
- 「单调队列优化DP」P2034 选择数字
「单调队列优化DP」P2034 选择数字 题面描述: 给定一行n个非负整数a[1]..a[n].现在你可以选择其中若干个数,但不能有超过k个连续的数字被选择.你的任务是使得选出的数字的和最大. 输入格 ...
- 数学计算 LibreOJ - 2573
题目描述 小豆现在有一个数 x ,初始值为 1 . 小豆有 Q 次操作,操作有两种类型: 1 m: x=x×m ,输出 xmodM : 2 pos: x=x/ 第 pos 次操作所乘的数(保证第 po ...