前言

由于现代互联网的飞速发展,我们在开发现代 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. java读写Excel模板文件,应用于负载均衡多个服务器

    首先,需要大家明白一点,对于多服务器就不能用导出文件用a标签访问链接方式去导出excel文件了,原因相信大家也明白,可能也做过尝试. 现在开始第一步:get请求,productPath 为你的项目路径 ...

  2. 如何去除List集合中重复的元素

    1.通过循环进行删除 public static void removeDuplicate(List list) { for ( int i = 0 ; i < list.size() - 1 ...

  3. 理解与使用Javascript中的回调函数

    在Javascript中,函数是第一类对象,这意味着函数可以像对象一样按照第一类管理被使用.既然函数实际上是对象:它们能被“存储”在变量中,能作为函数参数被传递,能在函数中被创建,能从函数中返回. 因 ...

  4. springboot 整合retry(重试机制)

    当我们调用一个接口可能由于网络等原因造成第一次失败,再去尝试就成功了,这就是重试机制,spring支持重试机制,并且在Spring Cloud中可以与Hystaix结合使用,可以避免访问到已经不正常的 ...

  5. 如何针对Thymeleaf模板抽取公共页面

    对于公共页面(导航栏nav.页头head.页尾footer)的抽取有三种方式:        1)基于iframe进行抽取,这种方式很有效,但比较老了,另外为了页面的自适应性,还得做不少工作:     ...

  6. JVM源码分析之深入分析Object类finalize()方法的实现原理

      原创申明:本文由公众号[猿灯塔]原创,转载请说明出处标注 ​“365篇原创计划”第十篇. 今天呢!灯塔君跟大家讲: 深入分析Object类finalize()方法的实现原理 finalize 如果 ...

  7. 每日一题 - 剑指 Offer 46. 把数字翻译成字符串

    题目信息 时间: 2019-07-02 题目链接:Leetcode tag: 动态规划 难易程度:中等 题目描述: 给定一个数字,我们按照如下规则把它翻译为字符串:0 翻译成 "a" ...

  8. IE6浏览器有哪些常见的bug,以及解决IE6常用bug的方法

    1.IE6不支持min-height,解决办法使用css hack: .target { min-height: 100px; height: auto !important; height: 100 ...

  9. css如何设置首行文字缩进?

    在HTML网页中通常一段文字都需要设置首行缩进两个文字,使页面更加美观,我们可以通过css实现这一效果,下面我们就来看一下使用css设置首行文字缩进的方法. css可以使用text-indent属性来 ...

  10. Django---进阶12

    目录 Auth模块 方法总结 如何扩展auth_user表 项目开发流程 表设计 作业 Auth模块 """ 其实我们在创建好一个django项目之后直接执行数据库迁移命 ...