前言

由于现代互联网的飞速发展,我们在开发现代 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(跨站资源共享)的更多相关文章

  1. SpringSecurity环境下配置CORS跨站资源共享规则

    一.CORS简述 要说明CORS(Cross Origin Resourse-Sharing) 跨站资源共享,就必须先说同源策略.长话短说,同源策略就是向服务端发起请求的时候,以下三项必须与当前浏览器 ...

  2. 禁止chrome中CORS跨域资源共享错误

    在开发中,可以通过命令行命令chrome --allow-file-access-from-files来 禁止CORS错误. 只在紧急情况下使用这个方法,比如你的老板正站在你身后, 并且所有事情都无法 ...

  3. CORS(跨站资源共享)介绍

    起因 有同学在nginx站点配置中加了一行Access-Control-Allow-Origin *,导致微信中业务数据异常,抓包看http头有两个Access-Control-Allow-Origi ...

  4. 跨站资源共享CORS原理深度解析

    我相信如果你写过前后端分离的web应用程序,或者写过一些ajax请求调用,你可能会遇到过CORS错误. CORS是什么? 它与安全性有关吗? 为什么要有CORS?它解决了什么目的? CORS是怎样运行 ...

  5. 在ASP.NET Web API中实现CORS(跨域资源共享)

    默认情况下,是不允许网页从不同的域访问服务器资源的,访问遵循"同源"策略的原则. 会遇到如下的报错: XMLHttpRequest cannot load http://local ...

  6. CORS跨域资源共享

    CORS(跨域资源共享)跨域问题及解决 当使用ajax跨域请求时,浏览器报错:XmlHttpRequest error: Origin null is not allowed by Access-Co ...

  7. CORS跨域资源共享你该知道的事儿

    "唠嗑之前,一些客套话" CORS跨域资源共享,这个话题大家一定不陌生了,吃久了大转转公众号的深度技术好文,也该吃点儿小米粥溜溜胃里的缝儿了,今天咱们就再好好屡屡CORS跨域资源共 ...

  8. 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 ...

  9. 跨域漏洞丨JSONP和CORS跨域资源共享

    进入正文之前,我们先来解决个小问题,什么是跨域? 跨域:指的是浏览器不能执行其它网站的脚本,它是由浏览器的同源策略造成的,是浏览器的安全限制! 跨域常见的两种方式,分别是JSONP和CORS. 今天i ...

随机推荐

  1. laravel Excel 导入

    <?php namespace App\Modules\Live\Http\Controllers; use Illuminate\Http\Request; use Maatwebsite\E ...

  2. 使用TimerTask创建定时任务

    使用TimerTask创建定时任务,打包之后应用于linux系统 step1:创建java项目 step2:代码实现 定时任务实现类CreateTask.java是打印操作者的名字 配置准换类Conf ...

  3. 关于 urlencode 的使用和 json 模块的介绍

    先附上一段 “百度翻译” 的爬虫代码 # python爬虫实现百度翻译 # urllib和request POST参数提交 from urllib import request,parse impor ...

  4. Python之浅谈生成器

    目录 三元表达式 列表推导式 字典生成式 生成器 生成器表达式 匿名函数 三元表达式 a=0 b=6 print (a)if a>b else print(b) 三元表达式只能写if的双分支结构 ...

  5. 洛谷 P2212 【[USACO14MAR]Watering the Fields S】

    一道最小生成树模板题,这里用的Kruskal算法,把每两点就加一条边,跑一遍最小生成树即可. #include <bits/stdc++.h> using namespace std; s ...

  6. Django迁移命令无法生成mysql表

    数据库迁移问题:在执行python manage.py makemigrations迁移命令之后,正常输出并生成迁移文件,但执行python manage.py migrate之后显示,No migr ...

  7. python基础知识练习1

    1.要求:输入A.B.C获得方程的解. 分析:通过input函数接收A,B,C的值.通过公式计算出detal的值,再根据条件进行判断,输出所需要的值: def args_input(): try: A ...

  8. Netty 源码解析(五): Netty 的线程池分析

    今天是猿灯塔“365篇原创计划”第五篇. 接下来的时间灯塔君持续更新Netty系列一共九篇 Netty 源码解析(一): 开始 Netty 源码解析(二): Netty 的 Channel Netty ...

  9. 【Windows10】如何使用Segoe MDL2 Assets图标

    众所周知,在Windows 10中,微软引入了汉堡菜单,方便Android和ios的开发者移植程序,而不需要单独为Windows设计一套UI.但有人可能发现在symbol icon里根本找不到所谓的汉 ...

  10. mybitis下choose..when. otherwise条件不起作用

    我的代码如下: <select id="findList" resultType="TyArticle"> SELECT <include r ...