【從零開始搭建本身的.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

相關文章
相關標籤/搜索