【从零开始搭建自己的.NET Core Api框架】(五)由浅入深详解CORS跨域机制并快速实现
系列目录
一. 创建项目并集成swagger
三. 集成轻量级ORM框架——SqlSugar
五. 实现CORS跨域
源码下载:https://github.com/WangRui321/RayPI_V2.0
(新增的跨域部分的代码还没有更新上去,但一共就只有15行代码,需要的完全可以自己编写。等晚上有时间再去git更新下,地址不变~)
1. 根
先从一个最最根本的问题开始,我们为什么要“跨域”?
答案很简单,用一句话就可以概括:跨域的唯一目的,就是要绕过“同源策略”的限制。
1.1 同源策略
根据百度百科:
同源策略(Same origin policy)是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,则浏览器的正常功能可能都会受到影响。可以说整个Web都是构建在同源策略基础之上的,浏览器只是针对同源策略的一种实现。
同源策略是由Netscape提出的一个著名的安全策略,所谓同源是指协议、域名、端口相同。现在所有支持JavaScript的浏览器都会使用这个策略。
可以看出,同源策略是一种Web安全策略,它限制了一个域内发起的请求只能访问它所处当前域内的资源,而不能访问其他域内的资源。比如,有两个网站,A和B,在同源策略下,网站A的js就不可以访问网站B的资源(比如接口)。
与其说我们“运用”同源策略,不如说是我们“遵守”同源策略。就像交通规则一样,它虽然限制了每个人的自由,但同时也保证了每个人的相对安全。
1.1.1 怎么判断两个资源是否同源?
判断是否同源有三个要素,我们暂且称它们为“同源三要素”:
1)协议
比如http(超文本传输协议)或https(安全套接字层超文本传输协议)。如果协议不同,则一定不同源。
2)域名
比如www.cnblogs.com。如果域名不同,则一定不同源。
3)端口
比如www.raywang.com:8080和www.raywang:8081,它们一个为8080端口,一个为8081端口,所以它们不同源。
如果两个资源时同源的,那么它们必须同时满足这个条件都相同。举几个例子:
https://www.raywang.com与http://www.raywang.com不同源,因为它们协议不同。
https://www.raywang.com与http://www.baidu.com不同源,因为它们域名不同。
https://www.raywang.com:8080与https://www.raywang.com:8081不同源,因为它们端口不同。
1.1.2 我们为什么要跨域?
以api设计模式开发的系统,前端页面和后端接口一般都是分离了。在开发初期,后端接口可能被发布到某台服务器上,而前端页面可能被搭建在开发人员本地的iis中,就算是项目上线后,页可能是前端页面和后台服务发布到两台不同的服务器上。当然,这些情况都是不符合同源策略的。
拿我们正在搭建的web api为例,我这里写了一个测试用的小网页,代码如下:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title></title>
<script src="js/jquery-2.1.4.js"></script>
<script>
function GetToken() {
var tokenModel = {
id: $("#tid").val(),
name: $("#tname").val(),
sub: $("#tsub").val()
};
$.ajax({
url: "http://localhost:3607/api/System/Token",
type: "get",
dataType: "json",
data: tokenModel,
async: false,
success: function (d) {
alert(JSON.stringify(d));
$("#jwt").val(d);
},
error: function (d) {
alert(JSON.stringify(d));
$("#jwt").val(JSON.stringify(d));
}
});
}
function GetStudent() {
var s = { name: $("#sname").val() };
$.ajax({
url: "http://localhost:3607/api/Client/Student/GetByName",
type: "get",
dataType: "json",
data: s,
async: false,
headers: { "Authorization": "Bearer " + $("#jwt").val().trim() },
success: function (d) {
alert(JSON.stringify(d));
$("#student").val(JSON.stringify(d));
},
error: function (d) {
alert(JSON.stringify(d));
$("#student").val(JSON.stringify(d));
}
});
}
</script>
</head>
<body>
<div style="width:350px; margin:100px auto 0;">
I D:<input type="text" id="tid" value="" /><br />
Name:<input type="text" id="tname" value="张三" /><br />
Sub :<input type="text" id="tsub" value="Client" /><br />
<input type="button" value="获取Token" onclick="GetToken()" /><br />
<br />
<p>token:</p>
<textarea id="jwt" style="width:300px; height:200px; "></textarea>
<br />
学生姓名:<input type="text" id="sname" value="张三" /> <input type="button" value="点击查询" onclick="GetStudent()" />
<br />
<textarea id="student" style="width:300px;height:200px;"></textarea>
</div>
</body>
</html>
我把它放到RayPI下的wwwroot下,

运行之后,浏览器访问是这样的:

其中“获取Token”按钮会调用接口http://localhost:3607/api/System/Token,“点击查询”按钮会调用接口http://localhost:3607/api/Client/Student/GetByName,结果如下:

此时,页面http://localhost:3607/index.html和接口http://localhost:3607/api/System/Token,http://localhost:3607/api/Client/Student/GetByName是同源的(因为同在一个项目中),所以它们之间可以互相访问传递资源(json),没有任何问题。
下面我们将这个网页单独拿出来发布到我本地的iis中,域名取为http://localhost:8083/index.html


点击按钮,结果如下

显然,此时这个前端页面与后端接口是非同源的,所以它不能访问接口资源。
1.2 跨域的解决方案
解决跨域的方法很多,比如运用代理跨域,window.name+iframe跨域。
但是最常用的跨域方式应该是两种:JSONP和CORS跨域。
RayPI选择的跨域方式是后者。
1.3 CORS跨域
CORS,即Cross-Origin Resource Sharing,跨源资源共享。
3.3.1 跨域请求的类型
CORS将跨域请求分成以下两种:
- 简单请求
- 复杂请求
一个简单的请求大致满足如下条件:
- HTTP方法是下列之一
HEADGETPOST
- HTTP头包含
AcceptAccept-LanguageContent-LanguageLast-Event-IDContent-Type,但仅能是下列之一application/x-www-form-urlencodedmultipart/form-datatext/plain
任何一个不满足上述要求的请求,即被认为是复杂请求。
3.3.2 一次CORS跨域的完整流程
一个完整的CORS跨域请求的流程是这样的:

先别慌,图片看着多,其实是三块内容,我们把它分为左、中、右三部分,从最左边开始看起。
1)左:基础
一个http请求被发起之后,浏览器会根据是否是跨域请求(判断标准就是上面说的同源策略三要素),决定是否在http请求的头部添加“Origin”字段,并将发起请求的域名附加在该字段后面。
服务器接收到客服端的http请求后,会先尝试读取“Origin”字段,如果不存在,说明该请求不是跨域请求,直接将该请求放行,把结果返回给客户端;
如果头部存在“Origin”字段,说明该请求来自和当前服务器不同源的一个客户端,是一个跨域请求。这时先判断该“Origin”字段后的域名和请求方式(request method)是否合法,如果不合法,就直接返回403错误码。如果是合法的,再根据上面说的原则判断该请求是简单请求还是复杂请求。如果是简单请求,就进入图片中间简单请求流程,如果是复杂请求,就进入图片最右边复杂请求流程。
2)中:简单请求
服务器端根据自己设置的CORS跨域规则,配置相应的Access-Control-Allow-Origin和Access-Control-Allow-Methods等响应头信息。
当收到客户端的请求后,服务端将验证客户端请求头中的信息是否符合设置的CORS规则,如果符合,则将请求的资源连同跨域响应头(Access-Control-Allow-Origin等)返回给客户端。
客服端(浏览器)收到Response Headers后,会验证这些响应头信息,判断是否通过了跨域请求,如果通过,则返回状态码200,并成功获取到跨域资源:

如果服务端收到客户端的请求后,发现客户端请求头中的信息不符合设置的CORS规则,这时则不会讲配置的跨域响应头(Access-Control-Allow-Origin等)返回给客户端。
客户端收到Response Headers后,验证跨域响应头信息,没有发现相应的跨域响应头,说明跨域请求不通过,将不会返回资源。(这里理论上应该返回错误码403的,但是Chrome显示的是200状态码,我也不知道是为啥。。。)

可以通过浏览器的Console查看具体的验证失败原因:

3)右:复杂请求
一个复杂请求不仅有包含通信内容的请求,同时也包含预请求(preflight request)。
浏览器发现是复杂请求的时候,并不会直接发起原请求,而是先发送Preflight requests(预先验证请求),Preflight requests是一个OPTION请求,用于询问服务器是否允许当前域名下的页面发起跨域请求。
如下例,点击“点击查询”后,需要向接口传递token验证(接口的授权验证是前一章讲的内容),所该请求是一个复杂跨域请求。如下图:

OPTIONS请求头部中一般会包含以下头部:Origin、Access-Control-Request-Method、Access-Control-Request-Headers。
服务器收到OPTIONS请求后,设置Access-Control-Allow-Origin、Access-Control-Allow-Method、Access-Control-Allow-Headers头部与浏览器沟通来判断是否允许这个请求。
如果Preflight requests验证通过,浏览器才会发送真正的跨域请求。

如果Preflight requests验证失败,浏览器则不会发送真正的跨域请求。(理论上应该返回403错误码,但是这里还是返了200。。。)

可以通过浏览器的Console查看具体的验证失败原因:

2. 道
原理了然,下面就开始实现了。
方法有两个,程序实现和服务器实现。
2.1 程序实现
打开主项目下的Startup.cs文件,编辑ConfigureServices函数

代码如下:
#region CORS
services.AddCors(c =>
{
c.AddPolicy("AllowAnyOrigin", policy =>
{
policy.AllowAnyOrigin()//允许任何源
.AllowAnyMethod()//允许任何方式
.AllowAnyHeader()//允许任何头
.AllowCredentials();//允许cookie
});
c.AddPolicy("AllowSpecificOrigin", policy =>
{
policy.WithOrigins("http://localhost:8083")
.WithMethods("GET", "POST", "PUT", "DELETE")
.WithHeaders("authorization");
});
});
#endregion
这里添加了两个策略,AllowAnyOrigin策略几乎直接完全无视了“同源策略”的限制,所以个人建议尽量不要这么写。AllowSpecificOrigin策略我暂时只放了一个测试用的源,如有需要可根据情况更改。
下面只需要在控制器头上(或某个函数头上)添加标识:
[EnableCors("AllowSpecificOrigin")]

2.2 服务器实现
这里我用的IIS作为例子,方法如下:
在iis新建网站,添加RayPI,点击IIS下的“HTTP响应标头”

右侧店家添加,依次添加如下表头信息:

Access-Control-Allow-Origin : * Access-Control-Allow-Methods : GET,POST,PUT,DELETE,HEAD,OPTIONS Access-Control-Allow-Headers : authorization
3. 果
F5运行程序,从http://localhost:8083/进行测试,结果如下

跨域访问成功~
参考内容:
https://blog.csdn.net/u014344668/article/details/54948546
https://blog.csdn.net/badboyer/article/details/51261083
【从零开始搭建自己的.NET Core Api框架】(五)由浅入深详解CORS跨域机制并快速实现的更多相关文章
- 【从零开始搭建自己的.NET Core Api框架】(七)授权认证进阶篇
系列目录 一. 创建项目并集成swagger 1.1 创建 1.2 完善 二. 搭建项目整体架构 三. 集成轻量级ORM框架——SqlSugar 3.1 搭建环境 3.2 实战篇:利用SqlSuga ...
- 【从零开始搭建自己的.NET Core Api框架】(四)实战!带你半个小时实现接口的JWT授权验证
系列目录 一. 创建项目并集成swagger 1.1 创建 1.2 完善 二. 搭建项目整体架构 三. 集成轻量级ORM框架——SqlSugar 3.1 搭建环境 3.2 实战篇:利用SqlSuga ...
- 【从零开始搭建自己的.NET Core Api框架】(一)创建项目并集成swagger:1.1 创建
系列目录 一. 创建项目并集成swagger 1.1 创建 1.2 完善 二. 搭建项目整体架构 三. 集成轻量级ORM框架——SqlSugar 3.1 搭建环境 3.2 实战篇:利用SqlSuga ...
- 【从零开始搭建自己的.NET Core Api框架】(六)泛型仓储的作用
系列目录 一. 创建项目并集成swagger 1.1 创建 1.2 完善 二. 搭建项目整体架构 三. 集成轻量级ORM框架——SqlSugar 3.1 搭建环境 3.2 实战篇:利用SqlSuga ...
- 【从零开始搭建自己的.NET Core Api框架】(三)集成轻量级ORM——SqlSugar:3.1 搭建环境
系列目录 一. 创建项目并集成swagger 1.1 创建 1.2 完善 二. 搭建项目整体架构 三. 集成轻量级ORM框架——SqlSugar 3.1 搭建环境 3.2 实战篇:利用SqlSuga ...
- 【从零开始搭建自己的.NET Core Api框架】(二)搭建项目的整体架构
系列目录 一. 创建项目并集成swagger 1.1 创建 1.2 完善 二. 搭建项目整体架构 三. 集成轻量级ORM框架——SqlSugar 3.1 搭建环境 3.2 实战篇:利用SqlSuga ...
- 从零开始搭建自己的.NET Core Api框架-1目录
https://www.cnblogs.com/RayWang/p/9216820.html 系列目录 一. 创建项目并集成swagger 1.1 创建 1.2 完善 二. 搭建项目整体架构 三. ...
- Web API 实现JSONP或者安装配置Cors跨域
前言 照理来说本节也应该讲Web API原理,目前已经探讨完了比较底层的Web API消息处理管道以及Web Host寄宿管道,接下来应该要触及控制器.Action方法,以及过滤器.模型绑定等等,想想 ...
- ABP .Net Core API和Angular前端APP独立部署跨域问题(No Access-Control-Allow-Origin)
前言: 通过ABP官网(https://aspnetboilerplate.com)下载ASP.NET Core 2.x + Angular模板项目是按ReStful风格架构Web API和angul ...
随机推荐
- 马拉车算法——求回文子串个数zoj4110
zoj的测评姬好能卡时间.. 求回文子串的个数:只要把p[i]/2就行了: 如果s_new[i]是‘#’,算的是没有中心的偶回文串 反之是奇回文串 /* 给定两个字符串s,t 结论:s,t不相同的第一 ...
- Centos设置防火墙与开放访问端口
一. jeuxs在启动后可能会出现启动jexus成功,但是访问失败.但是在服务器内部访问没问题. 列出所有端口 netstat -ntlp 查看已经开放的端口: firewall-cmd --list ...
- C# Aspose.Cells 如何设置单元格样式
//Instantiating a Workbook object Workbook workbook = new Workbook(); //Adding a new worksheet to th ...
- vs2017更新出错:The entire Box execution exiting with result code: 0x0
在将vs2017 15.7.4更新至15.9.5出现“The entire Box execution exiting with result code: 0x0”错误,也就是文件解压下载开始安装后, ...
- Mysql和mongo安装配置
mysql配置 1.下载镜像 docker pull mysql/mysql-server 2.运行容器 docker run -d -p 3306:3306 --name [Name] [Image ...
- js判断是微信、QQ内置浏览器打开页面
var ua = navigator.userAgent.toLowerCase(); if(ua.match(/MicroMessenger/i)=="micromessenger&quo ...
- 三大家族(offset、scroll、client)
offset.scroll.client三大家族 offset家族 offsetWidth 与 offsetHeight offset 偏移 用于获取元素自身的位置和大小 offsetWidth和of ...
- notes for lxf(三)
纯函数式编程是没有变量的,只要输入确定输出就确定 指高度抽象的编程范式 特点 函数本身可以作为参数传入 或者允许返回一个函数 Higher-order function 一个函数可以接收另一个函数作为 ...
- 关于二进制枚举-计蒜客-得到整数X
某君有 n个互不相同的正整数,现在他要从这 n 个正整数之中无重复地选取任意个数,并仅通过加法凑出整数 X.求某君有多少种不同的方案来凑出整数 X. 输入格式 第一行,输入两个整数 n,X(1≤n≤2 ...
- JS与CSS那些特别小的知识点区别
1:target与currentTarget的区别 currentTarget指向的事件绑定的元素,target指向的是你点击的元素 2:attr与jprop在jQuery在API当中的区别 2.1: ...