系列目錄 一. 創建項目並集成swagger 1.1 創建 1.2 完善 二. 搭建項目整體架構 三. 集成輕量級ORM框架——SqlSugar 3.1 搭建環境 3.2 實戰篇:利用SqlSugar快速實現CRUD 3.3 生成實體類 四. 集成JWT授權驗證 五. 實現CORS跨域 源碼下載:h ...
系列目錄
三. 集成輕量級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="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跨域。
但是最常用的跨域方式應該是兩種:JSONP和CORS跨域。
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