【.NET Core 3.0】框架之十二 || 跨域 与 Proxy
本文有配套視頻:
https://www.bilibili.com/video/av58096866/?p=8
?一、為什么會出現跨域的問題
跨域問題由來已久,主要是來源于瀏覽器的”同源策略”。
? 何為同源?只有當協議、端口、和域名都相同的頁面,則兩個頁面具有相同的源。只要網站的 協議名protocol、 主機host、 端口號port 這三個中的任意一個不同,網站間的數據請求與傳輸便構成了跨域調用,會受到同源策略的限制。?
同源策略限制從一個源加載的文檔或腳本如何與來自另一個源的資源進行交互。這是一個用于隔離潛在惡意文件的關鍵的安全機制。瀏覽器的同源策略,出于防范跨站腳本的攻擊,禁止客戶端腳本(如JavaScript)對不同域的服務進行跨站調用(通常指使用XMLHttpRequest請求)。
發生跨域的三個必要條件:
瀏覽器限制: 即瀏覽器對跨域行為進行檢測和阻止;
觸發跨域的三要素之一: 即 協議,域名和端口三個條件滿足其一;
發起的是xhr請求: 即發起的是XMLHttpRequest類型的請求;
所以說我們在web中,我們無法去獲取跨域的請求,常見的就是無法通過js獲取接口。
這里要說下我的以前使用的經驗:在同源系統下,前端js去調用后端接口,然后后端C#去調取跨域接口,這是我以前采用的辦法,但是前后端分離,這個辦法肯定就是不行了,因為那時候的MVC僅僅是頁面上的前和后,還是一個項目,現在卻是不同域名或端口的兩個項目。
但是只要我們合理使用同源策略,就可以達到跨域訪問的目的。
?二、JsonP
首先需要建立了一個前端項目,用 IIS 代理一下,用來模擬前后端分離后的前端訪問部分,具體如下步驟:
1、模擬前端訪問頁面
在 wwwroot 文件夾下,新建一個 CorsPost.html 靜態頁面,使用Jquery來發送請求。
設計了2種跨域方法,一個是 JSONP 的,一個是 CORS 的:
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Blog.Core</title> <script src="https://cdn.bootcss.com/jquery/1.10.2/jquery.min.js"></script> <style> div { margin: 10px; word-wrap: break-word; } </style> <script> $(document).ready(function () { $("#jsonp").click(function () { $.getJSON("http://localhost:8081/api/Login/jsonp?callBack=?", function (data) { $("#data-jsonp").html("數據: " + data.value); }); }); $("#cors").click(function () { $.get("http://localhost:8081/api/Login/Token", function (data, status) { console.log(data); $("#status-cors").html("狀態: " + status); $("#data-cors").html("數據: " + data? data.token:"失敗"); }); }); $("#cors-post").click(function () { let postdata = { "bID": 10, "bsubmitter": "222", "btitle": "33333", "bcategory": "4444", "bcontent": "5555", "btraffic": 0, "bcommentNum": 0, "bUpdateTime": "2018-11-08T02:36:26.557Z", "bCreateTime": "2018-11-08T02:36:26.557Z", "bRemark": "string" }; $.ajax({ type: 'post', url: 'http://localhost:8081/api/Values', contentType: 'application/json', data: JSON.stringify(postdata), success: function (data, status) { console.log(data); $("#status-cors-post").html("狀態: " + status); $("#data-cors-post").html("數據: " + JSON.stringify(data)); } }); }); }); </script> </head> <body> <h3>通過JsonP實現跨域請求</h3> <button id="jsonp">發送一個 GET </button> <div id="status-jsonp"></div> <div id="data-jsonp"></div> <hr /> <h3>通過CORS實現跨域請求,另需要在服務器段配置CORE</h3> <button id="cors">發送一個 GET </button> <div id="status-cors"></div> <div id="data-cors"></div> <hr /> <button id="cors-post">發送一個 POST </button> <div id="status-cors-post"></div> <div id="data-cors-post"></div> <hr /> </body> </html>?
注意:這里一定要注意jsonp的前端頁面請求寫法,要求很嚴謹
?
2、請求頁面部署
1、其實只需要當前Blog.Core 項目配置了靜態文件中間件,直接訪問就可以,
比如我的在線地址:http://xxxxx:8081/corspost.html,但是這樣起不到跨域的目的,因為這樣前臺和后臺,還是公用的一個 8081 端口,方法不推薦。
?
2、單獨部署:將這個頁面部署到自己的IIS中,拷貝到文件里,直接在iis添加該文件,訪問剛剛的Html文件目錄就行,推薦。
?
3、設計后臺接口
在我們的項目 LoginController 中,設計Jsonp接口,Core調用的接口我們已經有了,就是之前獲取Token的接口GetJWTStr
/// <summary> /// 獲取JWT的方法4:給 JSONP 測試 /// </summary> /// <param name="callBack"></param> /// <param name="id"></param> /// <param name="sub"></param> /// <param name="expiresSliding"></param> /// <param name="expiresAbsoulute"></param> /// <returns></returns> [HttpGet] [Route("jsonp")] public void Getjsonp(string callBack, long id = 1, string sub = "Admin", int expiresSliding = 30, int expiresAbsoulute = 30) { TokenModelJwt tokenModel = new TokenModelJwt { Uid = id, Role = sub }; string jwtStr = JwtHelper.IssueJwt(tokenModel); string response = string.Format("\"value\":\"{0}\"", jwtStr); string call = callBack + "({" + response + "})"; Response.WriteAsync(call); }注意:這里一定要注意jsonp的接口寫法,要求很嚴謹
?
?
4、測試
點擊 “通過JsonP實現跨域請求”按鈕,發現已經有數據了,證明Jsonp跨域已經成功,你可以換成自己的域名試一試,但是Cors的還不行
?
現在咱們就說說這種JSONP跨域的優劣有哪些:
優勢:
1、操作很簡單;
2、支持老式瀏覽器;
劣勢:
1、這種方式只能發生get請求;
2、確定jsonp的請求是否失敗并不容易,大多數框架的實現都是結合超時時間來判定;
3、不太安全,可能也會受到攻擊
從上邊咱們可以看出來,雖然JSONP操作起來很簡單,幾乎和我們的 Ajax 請求沒有什么區別,但是弊端也特別大,目前市場上并沒有很好的流通起來,那有沒有更通用的,更安全的跨域方案呢,沒錯,就是今天的重頭戲 —— CORS。
?
?三、CORS
?
這個方法是目前我個人感覺,最簡單,最安全的方法,詳細步驟如下:
1、前端ajax調用
前端的代碼在jsonp的時候已經寫好,請往上看第二大節的第一步驟,
后端接口也是一個很簡單的 /api/Login/Token 接口
剩下的就是配置跨域了,很簡單!
?
2、配置 CORS 跨域
在 startup.cs 啟動文件的 ConfigureServices 中添加
services.AddCors(c?=> { // 配置策略 c.AddPolicy("LimitRequests", policy => { // 支持多個域名端口,注意端口號后不要帶/斜桿:比如localhost:8000/,是錯的 // http://127.0.0.1:1818 和 http://localhost:1818 是不一樣的,盡量寫兩個 policy .WithOrigins("http://127.0.0.1:1818", "http://localhost:8080", "http://localhost:8021", "http://localhost:8081", "http://localhost:1818") .AllowAnyHeader()//允許任意頭 .AllowAnyMethod();//允許任意方法 }); });?
基本注釋都有,大家都能看的懂,這里說一下,有三個小點需要了解:
注意:
1、在定義策略 LimitRequests 的時候,源域名應該是客戶端 vue 項目的請求的端口域名,不是當前API的域名端口。
2、上邊我們是在 configureService 里配置的策略,其實我們在下一步的中間件也可以配置策略,這里就不細說了,防止混淆。
CORS的配置一定要放在AutoFac前面,否則builder.Populate(services);后,你再進行配置會沒有效果。
?
3、啟動中間件
在啟動文件 的 中間件管道配置 Configure 種,添加啟用Cors中間件服務,但是千萬要注意順序。
?
public void Configure(IApplicationBuilder app) { ... app.UseStaticFiles(); app.UseRouting(); app.UseCors();//添加 Cors 跨域中間件 app.UseAuthentication(); app.UseAuthorization(); app.UseEndpoints(endpoints => { endpoints.MapControllers(); });?
?
注意:如果你使用了 app.UserMvc() 或者 app.UseHttpsRedirection()這類的中間件,一定要把 app.UseCors() 寫在它們的上邊,先進行跨域,再進行 Http 請求,否則會提示跨域失敗。
因為這兩個都是涉及到 Http請求的,如果你不跨域就直接轉發或者mvc,那肯定報錯。
?
4、運行調試,一切正常
?
至此,跨域的問題已經完成辣,我們通過分離后的,前端的項目工程,來訪問api,已經成功了,這里會有兩個常見的問題,這里簡單列舉一下:
5、IIS 部署常見的跨域錯誤
?
1、如果遇到了跨域失敗的提示,比如這樣:
?
這個并不一定是沒有配置好導致的跨域失敗,還有可能是接口有錯誤,比如 500了,導致的接口異常,所以就提示訪問有錯誤。
2、可能部署到服務器的時候,會出現 Put 和 Delete 謂詞不能用的問題。
這個很簡單,是因為 IIS 不支持,添加進去進行了,在發布好的 web.config 文件里:
①刪除IIS安裝的WebDav模塊,選擇你的項目,右邊有個“模塊”,雙擊它;找到WebDavModule,刪除它。
<modules runAllManagedModulesForAllRequests="true" runManagedModulesForWebDavRequests="true"> <remove name="WebDAVModule" /> </modules> <handlers> <remove name="WebDAV" /> </handlers>現在咱們繼續聊聊 CORS 的優劣有哪些:
優勢:
1、支持所有的 Http 謂詞請求;
2、支持多種輸出格式,主要是json;
3、可用在生產環境;
4、同時配置多個前端項目;
劣勢:
1、配置太偏重后端;
2、會暴露后端api域名或端口;
從上邊咱們可以看出來,CORS 優點還是很多的,我們平時的開發基本也是使用的這個,應用范圍也特別的廣泛,但是也是有一兩個小問題的,就比如我們平時開發的時候,可能時不時前端vue項目就會修改端口,那就只能讓后端工程師來修改配置了。
亦或者,雖然接口數據很正常被獲取,但是接口地址還是不想暴露出去,欸?!那咋辦,有辦法,就說說今天的第二個重頭戲 —— Proxy 代理!
?
?四、webpack 的?proxy 代理
1、Vue-Cli 3.0 新增全局配置文件 vue.config.js
?
vue項目搭建的時候,會有一個全局config配置文件,在 vue-cli 2.0 腳手架中,很明顯的把它放到了 config 的一個文件夾中,是這樣的,我們在 index.js 中可以端口號的配置,打包之后路徑的配置,圖片的配置 等等
但是 vue-cli 3.0 腳手架中,去掉了 config 這個文件夾,那我們如何配置呢,我們可以在項目根目錄新建一個 vue.config.js 文件,像之前的很多繁瑣配置,都可以在這個文件里配置啦。官方說明,vue.config.js 是一個可選的配置文件,如果項目的 (和 package.json 同級的) 根目錄中存在這個文件,那么它會被 @vue/cli-service 自動加載。你也可以使用 package.json 中的 vue 字段,但是注意這種寫法需要你嚴格遵照 JSON 的格式來寫。
?我們就在根目錄下新建該文件,然后添加內容:
?
module.exports = { // 基本路徑 baseUrl: "/", // 輸出文件目錄 outputDir: "dist", // eslint-loader 是否在保存的時候檢查 lintOnSave: true, // webpack配置 // see https://github.com/vuejs/vue-cli/blob/dev/docs/webpack.md chainWebpack: () => {}, configureWebpack: () => {}, // 生產環境是否生成 sourceMap 文件 productionSourceMap: true, // css相關配置 css: { // 是否使用css分離插件 ExtractTextPlugin extract: true, // 開啟 CSS source maps? sourceMap: false, // css預設器配置項 loaderOptions: {}, // 啟用 CSS modules for all css / pre-processor files. modules: false }, // use thread-loader for babel & TS in production build // enabled by default if the machine has more than 1 cores parallel: require("os").cpus().length > 1, // PWA 插件相關配置 // see https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-pwa pwa: {}, // webpack-dev-server 相關配置 devServer: { open: true, //配置自動啟動瀏覽器 host: "127.0.0.1",//主機 port: 6688, // 端口號自定義 https: false,//是否開啟https安全協議 hotOnly: false, // https:{type:Boolean} proxy: null, // 設置代理 before: app => {} }, // 第三方插件配置 pluginOptions: { // ... } };相應的注釋都有,主要是配置 devServer 節點,從名字上也能看出來,這個是 dev 開發環境的服務配置,常用來配置我們的端口號 port ,還有一個就是 proxy 的設置代理。
2、配置 proxy?本地代理
?將上邊的 proxy: null 注釋掉,然后修改代理設置:
?
這樣,我們就把接口地址代理到了本地,那代理到本地,如何調用呢,請往下看。
3、修改接口api地址,http.js文件
還記得我們在 src 文件夾下有一個 api/http.js 文件么,這個就是配置我們的 http 請求相關的,其他的都不變,我們只需要把域名去掉即可,或者寫上本項目的域名:
var root = "/api/";//配置 proxy 代理的api地址,
其實說白了,就是在項目啟動的時候,在node服務器中,是把所有的??
/api開頭的接口字符串,也就是這樣的http://localhost:6688/api的都指向了
http://xxxx:8081 域名,這樣就實現了跨域
其他任何都不需要變,接口的使用還是原來的使用方法,這樣,我們在本地開發的時候,就可以獲取到后端api數據了,不用再去 .net core 中設置跨域CORS了,是不是很方便。
說句簡單的:就是把后端的端口,給代理到了當前的前端端口,實現了跨域,就好像 node 服務,作為要給代理人的身份,來處理。
4、本地瀏覽效果
?
記得我們修改 vue.config.js 后要重啟下服務,然后就可以看到項目成功獲取數據,并代理到本地:
?
可以看到,我們已經把遠程接口數據 123.206.33.109 成功的代理到了本地 localhost:6688 ,是不是很簡單!
?
5、build 打包發布 IIS
?那我們本地開發好了,是不是一切都穩妥了呢,我們可以試一試,通過 build 打包,生成 dist 文件夾,然后將文件夾拷貝到服務器,并配置 IIS ,這個很簡單,就和配置普通靜態頁面是一樣的,
我們發現雖然頁面可以瀏覽(可以渲染,證明我們的 vue 已經生效),但是卻獲取不到數據,這證明我們上邊的 proxy 代理,只是適用本地dev開發環境中:
?
雖然這個本地代理的方法很簡單,特別適合我們獨立開發,在跨域這一塊,完全不用和后端做處理,但是服務器生產環境是不行的,那怎么辦,既然本地的 node 服務可以代理,那打包后的 html 靜態項目,有沒有一個人站出來,充當代理的角色呢,哎!還真有,就是Nginx;
?
?五、基于Nginx 的反向代理
這篇文章僅僅是如何使用 Nginx 作為一個反向代理服務器,具體的深入原理以及負載均衡器等等,我會在以后的微服務系列中說到(不知不覺又給自己玩了一個坑?)。
1、Nginx的代理工作原理
反向代理(Reverse Proxy)方式是指以代理服務器來接受 Internet上 的連接請求,然后將請求轉發給內部網絡上的服務器;并將從服務器上得到的結果返回給 Internet 上請求連接的客戶端,此時代理服務器對外就表現為一個服務器。
通常的代理服務器,只用于代理內部網絡對 Internet 外部網絡的連接請求,客戶機必須指定代理服務器,并將本來要直接發送到 Web 服務器上的 http 請求發送到代理服務器中。不支持外部網絡對內部網絡的連接請求,因為內部網絡對外部網絡是不可見的。當一個代理服務器能夠代理外部網絡上的主機, 訪問內部網絡時,這種代理服務的方式稱為反向代理服務。此時代理服務器對外就表現為一個Web服務器,外部網絡就可以簡單把它當作一個標準的 Web 服務器 而不需要特定的配置。不同之處在于,這個服務器沒有保存任何網頁的真實數據,所有的靜態網頁或者CGI程序,都保存在內部的Web服務器上。因此對反向代 理服務器的攻擊并不會使得網頁信息遭到破壞,這樣就增強了Web服務器的安全性。
總結來說呢,就是我們通過 nginx 反向代理服務器處理我們的請求,具體的數據處理還是交給 IIS,然后得到處理過的數據以后,我們再發送給 Internet 請求的客戶端,這樣就不會存在跨域的問題了。
?
2、Nginx 下載與使用
?
下載地址:http://nginx.org/en/download.html
?
我選擇下載穩定版 1.14 ,下載好后解壓,然后就看到根目錄下的 Nginx.exe ,直接雙擊即可開啟服務,然后就會在任務管理器查看到已經啟動的 Nginx 代理服務。
因為默認的是80端口,大家的端口應該都已經被占用,所以我們需要修改端口
打開 config 文件夾下的 nginx.conf 文件,然后修改端口號
這個很簡單,這個時候記得要重啟 Nginx 服務,你可以采用命令的形式,不過我有時候會發現無效,我一般使用的時候,在任務管理器中把所有的 nginx 進程全部結束,然后雙擊 nginx.exe 開啟。
這個時候我們直接訪問 localhost:8077 就發現已經啟動成功了,只不過是 nginx 的歡迎頁。
這個時候你一定好奇,為什么僅僅配置下,就能訪問該端口呢,不信的話,你可以在 cmd 中 通過 netstat -an 命令來查看 8077 端口是否被使用
?
發現已經被監聽使用,如果還不相信,你可以創建一個 IIS 項目,然后配置 8077 端口,發現會報錯,這也就是說明了,8077端口已經被占用,準確來說是被 Nginx 占用的,所以,Nginx 和 IIS一樣都是可以作為反向代理服務器來使用,從而可以通過監聽端口來代理我們的項目的:?
3、將上文打包后的 dist 文件,配置 Nginx 代理
1、將我們上邊 build 后的 dist 文件,放到咱們下載的 nginx 下的 html文件夾
?
2、配置代理
還是我們的 config/nginx.conf 文件,打開并配置 本地代理 ,注意注釋是用 # 號,不是 //
?
server { listen 8077;#監聽端口,也就是我們的項目端口 server_name localhost;#服務器主機 location / { root html;#監聽文件夾,默認是nginx下的html文件夾,也可以自定義物理路徑 E:\\Nginx\\test index index.html index.htm;#默認首次啟動的文件 } #配置本地代理規則 location /api {#名字取一個 api rewrite ^.+apb/?(.*)$ /$1 break; #路徑重寫機制(無用,但是你也可以自己定義,對路由進行變化) include uwsgi_params; proxy_pass http://123.206.33.109:8081; #api接口的域名地址 }?
相應的注釋已經寫好了,自己看看就明白了,和上邊 node 的proxy代理是一個邏輯。
4、本地以及服務器發布預覽
這個時候,你再訪問 localhost:8077 就能看到我們的項目內容了,訪問頁面也能看到我們的數據了,代理成功!?
?
?
這個時候僅僅是本地,那服務器行不行呢,我們只需要將我們的 nginx 文件夾拷貝到服務器,并且雙擊 nginx.exe 啟動代理服務,然后就可以啦!
是不是很簡單,只需要把http.js 的baseurl 修改一下,
完全不用修改我們任何的調用接口地址,也不用修改前端代理,更不用修改后端配置,就可以實現跨域。
?
5、刷新后出現 404
如果是IIS部署
1、如果你是 IIS 部署,就使用 hash 模式;
2、如果用IIS,也想用 history 模式,可以配置 URL重寫:https://router.vuejs.org/zh/guide/essentials/history-mode.html#后端配置例子
?
如果是Nginx部署:
1、不過如果用 nginx 的話,可以利用 404 頁面的機制,將 index.html 頁面 copy 一份,重命名成 404.html 即可;
?
2、如果不想添加一個 404 文件的話 ,就直接修改下 nginx 的配置文件(推薦)
官方解釋:https://router.vuejs.org/zh/guide/essentials/history-mode.html#%E5%90%8E%E7%AB%AF%E9%85%8D%E7%BD%AE%E4%BE%8B%E5%AD%90
?
?
?六、結語
?
0、不跨域 —— 前后端寫在一起,我還真的有一個項目是把Vue 和 .net 整合到一起了,不說明 ?;
?七、Github
https://github.com/anjoy8/Blog.Core
總結
以上是生活随笔為你收集整理的【.NET Core 3.0】框架之十二 || 跨域 与 Proxy的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: C#刷遍Leetcode面试题系列连载(
- 下一篇: .NET 时间轴:从出生到巨人