【从零开始搭建本身的.NET Core Api框架】(五)由浅入深详解CORS跨域机制并快速实现

系列目录html

.  建立项目并集成swagger
前端

  1.1 建立jquery

  1.2 完善git

二. 搭建项目总体架构github

三. 集成轻量级ORM框架——SqlSugarweb

  3.1 搭建环境ajax

  3.2 实战篇:利用SqlSugar快速实现CRUDjson

  3.3 生成实体类后端

四. 集成JWT受权验证设计模式

五. 实现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="1" /><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>
View Code

 

我把它放到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跨域。

可是最经常使用的跨域方式应该是两种:JSONPCORS跨域。

RayPI选择的跨域方式是后者。

 1.3 CORS跨域

CORS,即Cross-Origin Resource Sharing,跨源资源共享。

 3.3.1 跨域请求的类型

CORS将跨域请求分红如下两种:

  • 简单请求
  • 复杂请求

一个简单的请求大体知足以下条件:

  • HTTP方法是下列之一
    • HEAD
    • GET
    • POST
  • HTTP头包含
    • Accept
    • Accept-Language
    • Content-Language
    • Last-Event-ID
    • Content-Type,但仅能是下列之一
      • application/x-www-form-urlencoded
      • multipart/form-data
      • text/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

相关文章
相关标签/搜索