概述

什么是跨域

在前后端分离开发方式中,跨域是我们经常会遇到的问题。所谓的跨域,就是出于安全考虑,A域名向B域名发出Ajax请求,浏览器会拒绝,抛出类似下图的错误。

JSONP

JSONP不是标准跨域协议,更像是聪明程序员投机取巧的办法。这种方式的原理就是js是没有跨域限制的,你想想你引用bootstrap.js是不是网络地址放进来就可以用了。

实际上,所有src属性都不限制跨域的,比如img标签使用跨域图片是不会有问题的。

过程大体分下面四步。

  • 首先约定数据格式和回调函数名
  • A网站引用B网站的js
  • B网站用约定好的回调函数将数据包裹起来,在A引用的js里返回
  • A网站在回调函数中获取数据

这个方案的优点是兼容性比较好,古老版本的IE都可以支持,毕竟只是基于js的一个技巧,并没有新的技术或协议。

缺点比较明显,只支持GET,理解起来比较别扭,调用失败不会返回http状态码,安全性存在一定问题。

CORS

CORS的全称是Cross Origin Resource Sharing,翻译过来就是跨域资源共享。

跨域问题本质就是浏览器处于安全考虑,阻止了客户端跨域请求。但说到底,客户端请求安不安全还不是服务端说了算的,服务端都说我们家大米你们随便吃,浏览器还阻止,这不是碍事吗,你个物业还当自己业主啦?

但是浏览器也不能随便放行,毕竟网上冲浪的不仅有正经客人,还有小偷,真出问题了还得吐槽物业稀烂。浏览器说,服务端,这个客户端要去你家吃大米,你得告诉我你同不同意啊,服务端说我咋告诉你啊,我总不能来个人就冲着岗亭喊 I'M OK吧。浏览器说那我们搞个协议吧,整个互联网小区都按这个规范来,你们就按这个格式回复我。

这个协议就是CORS了。

下图描述了简单请求的流程。

graph LR;
A(客户端)-->B(不带Orgin跨域请求);
B-->C(浏览器拒绝);
A-->D(带Origin跨域请求);
D-->E(服务端返回白名单);
E-->F(白名单内);
E-->G(白名单外);
F-->H(浏览器放行);
G-->C

关于CORS简单请求,复杂请求,以及详细内容参考下面文章,不再赘述。

http://www.ruanyifeng.com/blog/2016/04/cors.html

CORS的缺点就是IE10以下不支持,如果你的项目需要兼容这些浏览器的话需要注意。

怎么实现CORS

CORS说白了其实就是在响应头里加东西,你可以在运维环节比如nginx加,可以在代码里加,常见的做法是中间件统一处理。AspNetCore为我们提供了CORS中间件。

AspNetCore_CORS中间件的使用

使用CORS中间件两句代码就够了,在Startup文件中

//注入CORS相关的服务,配置跨域策略
public void ConfigureServices(IServiceCollection services)
{
//策略1,允许所有域名跨域访问
config.AddPolicy("policy1", policy => {
policy.AllowAnyOrigin().
AllowAnyMethod().
AllowAnyOrigin().
AllowAnyMethod();
//注意:AllowAnyOrigin和AllowCredential不能同时出现,否则会报错
//AllowCredential即是否允许客户端发送cookie,基于安全原因,CORS协议规定不允许AllowOrigin为通配符的情况下设置允许发送cookie
//.AllowCredentials();
}); //策略2,仅允许特定域名、方法、请求头访问
config.AddPolicy("policy2",policy=> {
//只允许https://www.holdengong.com跨域访问
policy.WithOrigins("https://www.holdengong.com")
//只允许get,post方法
.WithMethods("GET", "POST")
//请求头中只允许有额外的头Authorization
.WithHeaders("Authorization")
//对于复杂请求,浏览器会首先发送预检请求(OPTIONS),服务端返回204,并在响应头中返回跨域设置
//此处可以设置预检请求的有效时长,即30分钟内不会再检查是否允许跨域
.SetPreflightMaxAge(TimeSpan.FromMinutes(30));
});
} //使用CORS中间件, 指定使用CorsPolicy
public void Configure(IApplicationBuilder app)
{
//使用policy1
app.UseCors("policy1");
}

注意:AllowAnyOrigin和AllowCredential不能同时配置,否则会报错。如果要允许客户端发送cookie的话,只能使用WithOrgin来执行允许跨域白名单

微软使用的策略设计模式,方便我们灵活使用跨域策略。比如,开发环境允许localhost跨域访问,方便开发调试,正式环境只允许指定域名访问。

源码解析

核心对象

services.TryAdd(ServiceDescriptor.Transient<ICorsService, CorsService>());

services.TryAdd(ServiceDescriptor.Transient<ICorsPolicyProvider, DefaultCorsPolicyProvider>());

services.Configure(setupAction);
  • CorsOptions:主要定义了字典PolicyMap,键是策略名称,值是跨域策略。用户可以在注入的时候往这个对象里面加跨域策略。然后提供了一些新增策略的操作方法。
// DefaultCorsPolicyProvider returns a Task<CorsPolicy>. We'll cache the value to be returned alongside
// the actual policy instance to have a separate lookup.
internal IDictionary<string, (CorsPolicy policy, Task<CorsPolicy> policyTask)> PolicyMap { get; }
= new Dictionary<string, (CorsPolicy, Task<CorsPolicy>)>(StringComparer.Ordinal);
  • ICorsService:有两个方法,EvaluatePolicy--评估策略,主要做一些校验、记录日志和分流预检请求和真实请求的工作; PopulateResult--执行策略,将结果填充到CorsResult对象中。

public class CorsService : ICorsService
{
private readonly CorsOptions _options;
private readonly ILogger _logger; /// <summary>
/// Creates a new instance of the <see cref="CorsService"/>.
/// </summary>
/// <param name="options">The option model representing <see cref="CorsOptions"/>.</param>
/// <param name="loggerFactory">The <see cref="ILoggerFactory"/>.</param>
public CorsService(IOptions<CorsOptions> options, ILoggerFactory loggerFactory)
{
if (options == null)
{
throw new ArgumentNullException(nameof(options));
} if (loggerFactory == null)
{
throw new ArgumentNullException(nameof(loggerFactory));
} _options = options.Value;
_logger = loggerFactory.CreateLogger<CorsService>();
} /// <summary>
/// Looks up a policy using the <paramref name="policyName"/> and then evaluates the policy using the passed in
/// <paramref name="context"/>.
/// </summary>
/// <param name="context"></param>
/// <param name="policyName"></param>
/// <returns>A <see cref="CorsResult"/> which contains the result of policy evaluation and can be
/// used by the caller to set appropriate response headers.</returns>
public CorsResult EvaluatePolicy(HttpContext context, string policyName)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
} var policy = _options.GetPolicy(policyName);
return EvaluatePolicy(context, policy);
} /// <inheritdoc />
public CorsResult EvaluatePolicy(HttpContext context, CorsPolicy policy)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
} if (policy == null)
{
throw new ArgumentNullException(nameof(policy));
} if (policy.AllowAnyOrigin && policy.SupportsCredentials)
{
throw new ArgumentException(Resources.InsecureConfiguration, nameof(policy));
} var requestHeaders = context.Request.Headers;
var origin = requestHeaders[CorsConstants.Origin]; var isOptionsRequest = string.Equals(context.Request.Method, CorsConstants.PreflightHttpMethod, StringComparison.OrdinalIgnoreCase);
var isPreflightRequest = isOptionsRequest && requestHeaders.ContainsKey(CorsConstants.AccessControlRequestMethod); if (isOptionsRequest && !isPreflightRequest)
{
_logger.IsNotPreflightRequest();
} var corsResult = new CorsResult
{
IsPreflightRequest = isPreflightRequest,
IsOriginAllowed = IsOriginAllowed(policy, origin),
}; if (isPreflightRequest)
{
EvaluatePreflightRequest(context, policy, corsResult);
}
else
{
EvaluateRequest(context, policy, corsResult);
} return corsResult;
} private static void PopulateResult(HttpContext context, CorsPolicy policy, CorsResult result)
{
var headers = context.Request.Headers;
if (policy.AllowAnyOrigin)
{
result.AllowedOrigin = CorsConstants.AnyOrigin;
result.VaryByOrigin = policy.SupportsCredentials;
}
else
{
var origin = headers[CorsConstants.Origin];
result.AllowedOrigin = origin;
result.VaryByOrigin = policy.Origins.Count > 1;
} result.SupportsCredentials = policy.SupportsCredentials;
result.PreflightMaxAge = policy.PreflightMaxAge; // https://fetch.spec.whatwg.org/#http-new-header-syntax
AddHeaderValues(result.AllowedExposedHeaders, policy.ExposedHeaders); var allowedMethods = policy.AllowAnyMethod ?
new[] { result.IsPreflightRequest ? (string)headers[CorsConstants.AccessControlRequestMethod] : context.Request.Method } :
policy.Methods;
AddHeaderValues(result.AllowedMethods, allowedMethods); var allowedHeaders = policy.AllowAnyHeader ?
headers.GetCommaSeparatedValues(CorsConstants.AccessControlRequestHeaders) :
policy.Headers;
AddHeaderValues(result.AllowedHeaders, allowedHeaders);
} public virtual void EvaluateRequest(HttpContext context, CorsPolicy policy, CorsResult result)
{
PopulateResult(context, policy, result);
} public virtual void EvaluatePreflightRequest(HttpContext context, CorsPolicy policy, CorsResult result)
{
PopulateResult(context, policy, result);
} /// <inheritdoc />
public virtual void ApplyResult(CorsResult result, HttpResponse response)
{
if (result == null)
{
throw new ArgumentNullException(nameof(result));
} if (response == null)
{
throw new ArgumentNullException(nameof(response));
} if (!result.IsOriginAllowed)
{
// In case a server does not wish to participate in the CORS protocol, its HTTP response to the
// CORS or CORS-preflight request must not include any of the above headers.
return;
} var headers = response.Headers;
headers[CorsConstants.AccessControlAllowOrigin] = result.AllowedOrigin; if (result.SupportsCredentials)
{
headers[CorsConstants.AccessControlAllowCredentials] = "true";
} if (result.IsPreflightRequest)
{
_logger.IsPreflightRequest(); // An HTTP response to a CORS-preflight request can include the following headers:
// `Access-Control-Allow-Methods`, `Access-Control-Allow-Headers`, `Access-Control-Max-Age`
if (result.AllowedHeaders.Count > 0)
{
headers.SetCommaSeparatedValues(CorsConstants.AccessControlAllowHeaders, result.AllowedHeaders.ToArray());
} if (result.AllowedMethods.Count > 0)
{
headers.SetCommaSeparatedValues(CorsConstants.AccessControlAllowMethods, result.AllowedMethods.ToArray());
} if (result.PreflightMaxAge.HasValue)
{
headers[CorsConstants.AccessControlMaxAge] = result.PreflightMaxAge.Value.TotalSeconds.ToString(CultureInfo.InvariantCulture);
}
}
else
{
// An HTTP response to a CORS request that is not a CORS-preflight request can also include the following header:
// `Access-Control-Expose-Headers`
if (result.AllowedExposedHeaders.Count > 0)
{
headers.SetCommaSeparatedValues(CorsConstants.AccessControlExposeHeaders, result.AllowedExposedHeaders.ToArray());
}
} if (result.VaryByOrigin)
{
headers.Append("Vary", "Origin");
}
} private static void AddHeaderValues(IList<string> target, IList<string> headerValues)
{
if (headerValues == null)
{
return;
} for (var i = 0; i < headerValues.Count; i++)
{
target.Add(headerValues[i]);
}
} private bool IsOriginAllowed(CorsPolicy policy, StringValues origin)
{
if (StringValues.IsNullOrEmpty(origin))
{
_logger.RequestDoesNotHaveOriginHeader();
return false;
} _logger.RequestHasOriginHeader(origin);
if (policy.AllowAnyOrigin || policy.IsOriginAllowed(origin))
{
_logger.PolicySuccess();
return true;
}
_logger.PolicyFailure();
_logger.OriginNotAllowed(origin);
return false;
}
  • ICorsPolicyProvider: 很简单,只有一个方法,GetPolicyAsync--根据policyName取出跨域策略。

中间件

CorsMiddleware
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.Extensions.Primitives; namespace Microsoft.AspNetCore.Cors.Infrastructure
{
/// <summary>
/// Default implementation of <see cref="ICorsService"/>.
/// </summary>
public class CorsService : ICorsService
{
private readonly CorsOptions _options;
private readonly ILogger _logger; /// <summary>
/// Creates a new instance of the <see cref="CorsService"/>.
/// </summary>
/// <param name="options">The option model representing <see cref="CorsOptions"/>.</param>
/// <param name="loggerFactory">The <see cref="ILoggerFactory"/>.</param>
public CorsService(IOptions<CorsOptions> options, ILoggerFactory loggerFactory)
{
if (options == null)
{
throw new ArgumentNullException(nameof(options));
} if (loggerFactory == null)
{
throw new ArgumentNullException(nameof(loggerFactory));
} _options = options.Value;
_logger = loggerFactory.CreateLogger<CorsService>();
} /// <summary>
/// Looks up a policy using the <paramref name="policyName"/> and then evaluates the policy using the passed in
/// <paramref name="context"/>.
/// </summary>
/// <param name="context"></param>
/// <param name="policyName"></param>
/// <returns>A <see cref="CorsResult"/> which contains the result of policy evaluation and can be
/// used by the caller to set appropriate response headers.</returns>
public CorsResult EvaluatePolicy(HttpContext context, string policyName)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
} var policy = _options.GetPolicy(policyName);
return EvaluatePolicy(context, policy);
} /// <inheritdoc />
public CorsResult EvaluatePolicy(HttpContext context, CorsPolicy policy)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
} if (policy == null)
{
throw new ArgumentNullException(nameof(policy));
} if (policy.AllowAnyOrigin && policy.SupportsCredentials)
{
throw new ArgumentException(Resources.InsecureConfiguration, nameof(policy));
} var requestHeaders = context.Request.Headers;
var origin = requestHeaders[CorsConstants.Origin]; var isOptionsRequest = string.Equals(context.Request.Method, CorsConstants.PreflightHttpMethod, StringComparison.OrdinalIgnoreCase);
var isPreflightRequest = isOptionsRequest && requestHeaders.ContainsKey(CorsConstants.AccessControlRequestMethod); if (isOptionsRequest && !isPreflightRequest)
{
_logger.IsNotPreflightRequest();
} var corsResult = new CorsResult
{
IsPreflightRequest = isPreflightRequest,
IsOriginAllowed = IsOriginAllowed(policy, origin),
}; if (isPreflightRequest)
{
EvaluatePreflightRequest(context, policy, corsResult);
}
else
{
EvaluateRequest(context, policy, corsResult);
} return corsResult;
} private static void PopulateResult(HttpContext context, CorsPolicy policy, CorsResult result)
{
var headers = context.Request.Headers;
if (policy.AllowAnyOrigin)
{
result.AllowedOrigin = CorsConstants.AnyOrigin;
result.VaryByOrigin = policy.SupportsCredentials;
}
else
{
var origin = headers[CorsConstants.Origin];
result.AllowedOrigin = origin;
result.VaryByOrigin = policy.Origins.Count > 1;
} result.SupportsCredentials = policy.SupportsCredentials;
result.PreflightMaxAge = policy.PreflightMaxAge; // https://fetch.spec.whatwg.org/#http-new-header-syntax
AddHeaderValues(result.AllowedExposedHeaders, policy.ExposedHeaders); var allowedMethods = policy.AllowAnyMethod ?
new[] { result.IsPreflightRequest ? (string)headers[CorsConstants.AccessControlRequestMethod] : context.Request.Method } :
policy.Methods;
AddHeaderValues(result.AllowedMethods, allowedMethods); var allowedHeaders = policy.AllowAnyHeader ?
headers.GetCommaSeparatedValues(CorsConstants.AccessControlRequestHeaders) :
policy.Headers;
AddHeaderValues(result.AllowedHeaders, allowedHeaders);
} public virtual void EvaluateRequest(HttpContext context, CorsPolicy policy, CorsResult result)
{
PopulateResult(context, policy, result);
} public virtual void EvaluatePreflightRequest(HttpContext context, CorsPolicy policy, CorsResult result)
{
PopulateResult(context, policy, result);
} /// <inheritdoc />
public virtual void ApplyResult(CorsResult result, HttpResponse response)
{
if (result == null)
{
throw new ArgumentNullException(nameof(result));
} if (response == null)
{
throw new ArgumentNullException(nameof(response));
} if (!result.IsOriginAllowed)
{
// In case a server does not wish to participate in the CORS protocol, its HTTP response to the
// CORS or CORS-preflight request must not include any of the above headers.
return;
} var headers = response.Headers;
headers[CorsConstants.AccessControlAllowOrigin] = result.AllowedOrigin; if (result.SupportsCredentials)
{
headers[CorsConstants.AccessControlAllowCredentials] = "true";
} if (result.IsPreflightRequest)
{
_logger.IsPreflightRequest(); // An HTTP response to a CORS-preflight request can include the following headers:
// `Access-Control-Allow-Methods`, `Access-Control-Allow-Headers`, `Access-Control-Max-Age`
if (result.AllowedHeaders.Count > 0)
{
headers.SetCommaSeparatedValues(CorsConstants.AccessControlAllowHeaders, result.AllowedHeaders.ToArray());
} if (result.AllowedMethods.Count > 0)
{
headers.SetCommaSeparatedValues(CorsConstants.AccessControlAllowMethods, result.AllowedMethods.ToArray());
} if (result.PreflightMaxAge.HasValue)
{
headers[CorsConstants.AccessControlMaxAge] = result.PreflightMaxAge.Value.TotalSeconds.ToString(CultureInfo.InvariantCulture);
}
}
else
{
// An HTTP response to a CORS request that is not a CORS-preflight request can also include the following header:
// `Access-Control-Expose-Headers`
if (result.AllowedExposedHeaders.Count > 0)
{
headers.SetCommaSeparatedValues(CorsConstants.AccessControlExposeHeaders, result.AllowedExposedHeaders.ToArray());
}
} if (result.VaryByOrigin)
{
headers.Append("Vary", "Origin");
}
} private static void AddHeaderValues(IList<string> target, IList<string> headerValues)
{
if (headerValues == null)
{
return;
} for (var i = 0; i < headerValues.Count; i++)
{
target.Add(headerValues[i]);
}
} private bool IsOriginAllowed(CorsPolicy policy, StringValues origin)
{
if (StringValues.IsNullOrEmpty(origin))
{
_logger.RequestDoesNotHaveOriginHeader();
return false;
} _logger.RequestHasOriginHeader(origin);
if (policy.AllowAnyOrigin || policy.IsOriginAllowed(origin))
{
_logger.PolicySuccess();
return true;
}
_logger.PolicyFailure();
_logger.OriginNotAllowed(origin);
return false;
}
}
}

首先,中间件会判断方法节点是否有实现以下两个接口,如果有的话优先执行节点设置。

IDisableCorsAttribute: 禁用跨域
ICorsPolicyMetadata:使用指定跨域策略

然后,然后判断请求头是否有Origin,没有的话直接略过CORS中间件去下游管道。

if (!context.Request.Headers.ContainsKey(CorsConstants.Origin))
{
return _next(context);
}

然后,中间件执行ICorsService的ApplyResult方法,将跨域策略写到响应头中。

原文地址:holdengong.com

AspNetCore源码解析_1_CORS中间件的更多相关文章

  1. 【原创】express3.4.8源码解析之中间件

    前言 注意:旧文章转成markdown格式. 中间件(middleware)的概念来自于TJ的connect库,express就是建立在connect之上. 就如同connect的意思是 连接 一样, ...

  2. AspNetCore3.1源码解析_2_Hsts中间件

    title: "AspNetCore3.1源码解析_2_Hsts中间件" date: 2020-03-16T12:40:46+08:00 draft: false --- 概述 在 ...

  3. AspNetCore3.1_Secutiry源码解析_4_Authentication_JwtBear

    title: "AspNetCore3.1_Secutiry源码解析_4_Authentication_JwtBear" date: 2020-03-22T16:29:29+08: ...

  4. .Net Core 中间件之主机地址过滤(HostFiltering)源码解析

    一.介绍 主机地址过滤中间件相当于一个白名单,标记哪些主机地址能访问接口. 二.使用 新建WebAPI项目,修改Startup中的代码段如下所示.下面表示允许主机名为“localhost”的主机访问( ...

  5. TongWEB与JOnAS 对比,国产中间件战斗机东方通TongWEB源码解析

    转自网址: http://bbs.51cto.com/thread-489819-1-1.html 首先需要声明的是,本人出于技术爱好的角度,以下的文字只是对所看到的一些情况的罗列,偶尔附加个人的一些 ...

  6. .Net Core 认证系统之Cookie认证源码解析

    接着上文.Net Core 认证系统源码解析,Cookie认证算是常用的认证模式,但是目前主流都是前后端分离,有点鸡肋但是,不考虑移动端的站点或者纯管理后台网站可以使用这种认证方式.注意:基于浏览器且 ...

  7. AspNetCore3.1_Secutiry源码解析_1_目录

    文章目录 AspNetCore3.1_Secutiry源码解析_1_目录 AspNetCore3.1_Secutiry源码解析_2_Authentication_核心项目 AspNetCore3.1_ ...

  8. AspNetCore3.1_Secutiry源码解析_8_Authorization_授权框架

    目录 AspNetCore3.1_Secutiry源码解析_1_目录 AspNetCore3.1_Secutiry源码解析_2_Authentication_核心流程 AspNetCore3.1_Se ...

  9. 源码解析.Net中Host主机的构建过程

    前言 本篇文章着重讲一下在.Net中Host主机的构建过程,依旧延续之前文章的思路,着重讲解其源码,如果有不知道有哪些用法的同学可以点击这里,废话不多说,咱们直接进入正题 Host构建过程 下图是我自 ...

随机推荐

  1. XGBoost使用篇(未完成)

    1.截止到本文(20191104)sklearn没有集成xgboost算法,需要单独安装xgboost库,然后导入使用 xgboost官网安装说明 Pre-built binary wheel for ...

  2. z-index优先级小结

    z-index是深度属性,设置元素在z轴上面的堆叠顺序. 强调:z-index必须和定位元素position:absollute|relative|fixed一起使用,否则无效 1.z-index属性 ...

  3. 从NIPS2014大会看机器学习新趋势

    微软杰出科学家 John Platt 本文译自:Machine Learning Trends fromNIPS 2014 编者按:John Platt是微软的杰出科学家,也是微软在机器学习领域的领军 ...

  4. 输入一个url之后到底发生了什么 - Hurry

    背景 最近学习到 nginx 方向代理发现,nginx 可以将你的请求以 http 块的 server 形式代理到请求的域名或者 ip 地址. 一个简单的 nigx 配置如下: 12345678 se ...

  5. [LC] 17. Letter Combinations of a Phone Number

    Given a string containing digits from 2-9 inclusive, return all possible letter combinations that th ...

  6. LG_2869_[USACO07DEC]美食的食草动物Gourmet Grazers

    题目描述 Like so many others, the cows have developed very haughty tastes and will no longer graze on ju ...

  7. mysql表关系

    表与表之间的关系 """ 把所有数据都存放于一张表的弊端 1.组织结构不清晰 2.浪费硬盘空间 3.扩展性极差 """ # 上述的弊端产生原 ...

  8. box-sizing: border-box

    如果在元素上设置了 box-sizing: border-box; 则 padding(内边距) 和 border(边框) 也包含在 width 和 height 中

  9. 杂记:OSX 安装openssl

    因为工作中要用到openssl中提供的MD5.SHA等摘要算法,通过brew install openssl安装的openssl在C文件中找不到相应的头文件.按照网上的教程各种修改之后还是找不到相应的 ...

  10. Pwnable.tw start

    Let's start the CTF:和stdin输入的字符串在同一个栈上,再准确点说是他们在栈上同一个地址上,gdb调试看得更清楚: 调试了就很容易看出来在堆栈上是同一块地址.发生栈溢出是因为:r ...