您現在的位置是:首頁 > 旅遊
跨域問題是怎樣造成的?
network error是什麼意思
跨域問題的由來
相信很多人都或多或少了解過跨域問題,尤其在現如今前後端分離大行其道的時候。
你在本地開發一個前端專案,這個專案是透過 node 執行的,埠是9528,而服務端是透過 spring boot 提供的,埠號是7001。
當你呼叫一個服務端介面時,很可能得到類似下面這樣的一個錯誤:
然後你在傳送請求的地方debug,在出現異常的地方你將得到這樣的結果:
異常物件很詭異,返回的 response 是 undefined 的,並且 message 訊息中只有一個“Network Error”。
看到這裡你應該要知道,你遇到跨域問題了。
但是你需要明確的一點是,這個請求已經發出去了,服務端也接收到並處理了,但是返回的響應結果不是瀏覽器想要的結果,所以瀏覽器將這個響應的結果給攔截了,這就是為什麼你看到的response是undefined。
瀏覽器的同源策略
那瀏覽器為什麼會將服務端返回的結果攔截掉呢?
這就需要我們瞭解瀏覽器基於安全方面的考慮,而引入的 同源策略(same-origin policy) 了。
早在1995年,Netscape 公司就在瀏覽器中引入了“同源策略”。
最初的 “同源策略”,主要是限制Cookie的訪問,A網頁設定的 Cookie,B網頁無法訪問,除非B網頁和A網頁是“同源”的。
那麼怎麼確定兩個網頁是不是“同源”呢,所謂“同源”就是指“協議+域名+埠”三者相同,即便兩個不同的域名指向同一個ip地址,也非同源。
沒有同源策略的保護
那麼為什麼要做這個同源的限制呢?因為如果沒有同源策略的保護,瀏覽器將沒有任何安全可言。
老李是一個釣魚愛好者,經常在
我要買(51mai.com)
的網站上買各種釣魚的工具,並且透過
銀行(yinhang.com)
以賬號密碼的方式直接支付。
這天老李又在
51mai.com
上買了一根魚竿,輸入銀行賬號密碼支付成功後,在支付成功頁看到一個叫
釣魚(diaoyu.com)
的網站投放的一個“免費領取魚餌”的廣告。
老李什麼都沒想就點選了這個廣告,跳轉到了釣魚的網站,殊不知這真是一個 “釣魚” 網站,老李銀行賬戶裡面錢全部被轉走了。
以上就是老李的錢被盜走的過程:
1。老李購買魚竿,並登入了銀行的網站輸入賬號密碼進行了支付,瀏覽器在本地快取了銀行的Cookie
2。老李點選釣魚網站,釣魚網站使用老李登入銀行之後的Cookie,偽造成自己是老李進行了轉賬操作。
這個過程就是著名的CSRF(Cross Site Request Forgery),跨站請求偽造,正是由於可能存在的偽造請求,導致了瀏覽器的不安全。
那麼如何防止CSRF攻擊呢,可以參考這篇文章:如何防止CSRF攻擊?
同源策略限制哪些行為
上面說了 **同源策略 **是一個安全機制,他本質是限制了從一個源載入的文件或指令碼如何與來自另一個源的資源進行互動,這是一個用於隔離潛在惡意檔案的重要安全機制。
隨著網際網路的發展,“同源策略”越來越嚴格,不僅限於Cookie的讀取。目前,如果非同源,共有三種行為受到限制。
(1) Cookie、LocalStorage 和 IndexDB 無法讀取。
(2) DOM 無法獲得。
(3) 請求的響應被攔截。
雖然這些限制是必要的,但是有時很不方便,合理的用途也會受到影響,所以為了能夠獲取非“同源”的資源,就有了跨域資源共享。
跨域資源共享
看到這裡你應該明白,為什麼文章開頭的請求會被攔截了,原因就是請求的源和服務端的源不是“同源”,而服務端又沒有設定允許的跨域資源共享,所以請求的響應被瀏覽器給攔截掉了。
CORS 是一個 W3C 標準,全稱是“跨域資源共享”(Cross Origin Resource Sharing),它允許瀏覽器向跨源伺服器,發出 XMLHttpRequest 請求,從而克服了只能傳送同源請求的限制。
CORS實現機制
那跨域資源共享機制是怎樣實現的呢?
當一個資源(origin)透過指令碼向另一個資源(host)發起請求,而被請求的資源(host)和請求源(origin)是不同的源時(協議、域名、埠不全部相同),瀏覽器就會發起一個
跨域 HTTP 請求
,並且瀏覽器會自動將當前資源的域新增在請求頭中一個叫 Origin 的 Header 中。
當然了,有三個標籤本身就是允許跨域載入資源的:
比如某個網站的首頁 http://domain-a。com/index。html 透過 來載入其他域上的圖片,除此之外還有諸如透過 CDN 節點引入css和js檔案的方式。
出於安全原因,瀏覽器限制從指令碼內發起的跨域 HTTP 請求。 例如,XMLHttpRequest 和 Fetch API 遵循同源策略。 也就是說使用這些 API 的 Web 應用程式只能從載入應用程式的同一個域請求 HTTP 資源,除非響應報文中包含了正確 CORS 響應頭。
透過在響應報文中設定額外的 HTTP 響應頭來告訴瀏覽器,執行在某個 origin 上的 Web 應用被准許訪問來自不同源伺服器上的資源,此時瀏覽器就不會將該響應攔截掉了。
那這些額外的 HTTP 響應頭是什麼呢?
響應頭是否必須含義
Access-Control-Allow-Origin
是該欄位表示,服務端接收哪些來源的域的請求
Access-Control-Allow-Credentials
否是否可以向服務端傳送Cookie,預設是 false
Access-Control-Expose-Headers
否可以向請求額外暴露的響應頭
其中只有
Access-Control-Allow-Origin
是必須的,該響應頭的值可以是請求的 Origin 的值,也可以是 * ,表示服務端接收所有來源的請求。
當瀏覽器發起 CORS 請求時,預設只能獲得6個響應頭的值:
Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma
如果還需要返回其他的響應頭給前端,則可以透過在
Access-Control-Expose-Headers
中指定。
CORS的兩種請求型別
CORS有兩種型別的請求,分別是:簡單請求(simple request)和非簡單請求(not-so-simple request)
只要同時滿足以下兩大條件,就屬於簡單請求。
(1) 請求方法是以下三種方法之一:
HEADGETPOST
(2) HTTP的頭資訊不超出以下幾種欄位:
AcceptAccept-LanguageContent-LanguageLast-Event-IDContent-Type:只限於三個值
application/x-www-form-urlencoded
、
multipart/form-data
、
text/plain
凡是不同時滿足上面兩個條件,就屬於非簡單請求,瀏覽器對這兩種請求的處理,是不一樣的。
為什麼會有兩種不同型別的請求呢?
CORS 規範要求,對那些可能對伺服器資料產生副作用的 HTTP 請求方法(特別是
GET
以外的 HTTP 請求,或者搭配某些 MIME 型別的
POST
請求),瀏覽器必須首先使用
OPTIONS
方法發起一個預檢請求(preflight request),從而獲知服務端是否允許該跨域請求。
伺服器確認允許之後,瀏覽器才能發起實際的 HTTP 請求。在預檢請求的返回中,伺服器端也可以通知客戶端,是否需要攜帶身份憑證(包括 Cookies 和 HTTP 認證相關的資料)。
非簡單請求就要求瀏覽器先發送一個預檢請求,預檢通過後再發送實際的請求。
怎樣實現CORS
知道了CORS的實現機制之後,我們就可以解決遇到的CORS的問題了。
1。透過JSONP
利用 <script> 標籤沒有跨域限制的漏洞,網頁可以得到從其他來源動態產生的 JSON 資料。JSONP請求一定需要對方的伺服器做支援才可以。
JSONP 和 AJAX 相同,都是客戶端向伺服器端傳送請求,從伺服器端獲取資料的方式。但 AJAX 屬於同源策略,JSONP 屬於非同源策略(支援跨域請求)。JSONP優點是簡單相容性好,可用於解決主流瀏覽器的跨域資料訪問的問題。
缺點是僅支援 GET 方法具有侷限性,不安全可能會遭受XSS攻擊。
2。利用反向代理伺服器
同源策略是瀏覽器需要遵循的標準,而如果是伺服器向伺服器請求就無需遵循同源策略
所以透過反向代理伺服器可以有效的解決跨域問題,代理伺服器需要做以下幾個步驟:
1。接受客戶端的請求
2。將請求轉發給實際的伺服器
3。將伺服器的響應結果返回給客戶端
Nginx就是類似的反向代理伺服器,可以透過配置Nginx代理來解決跨域問題。
3。服務端支援CORS
最安全的還是服務端來設定允許哪些來源的請求,即服務端在接收到請求之後,對允許的請求源設定 Access-Control-Allow-Origin 的響應頭。
透過@CrossOrigin註解
這裡以 Spring Boot 為例,可以透過 @CrossOrigin 註解來指定哪些類或者方法支援跨越,如下列程式碼所示:
/** * 在類上加註解 */@CrossOrigin({“http://127。0。0。1:9528”, “http://localhost:9528”})@RestControllerpublic class UserController { }
@RestControllerpublic class UserController { @Resource private UserFacade userFacade; /** * 在方法上加註解 */ @GetMapping(ApiConstant。Urls。GET_USER_INFO) @CrossOrigin({“http://127。0。0。1:9528”, “http://localhost:9528”}) public PojoResult
透過CorsRegistry設定全域性跨域配置
@Configuration@EnableWebMvcpublic class WebConfig extends WebMvcConfigurerAdapter { @Override public void addCorsMappings(CorsRegistry registry) { registry。addMapping(“/**”) 。allowedOrigins(“http://127。0。0。1:9528”, “http://localhost:9528”); }}
如果你使用的是 Spring Boot,推薦的做法是隻定義一個 WebMvcConfigurer 的Bean:
@Configurationpublic class MyConfiguration { @Bean public WebMvcConfigurer corsConfigurer() { return new WebMvcConfigurerAdapter() { @Override public void addCorsMappings(CorsRegistry registry) { registry。addMapping(“/**”) 。allowedOrigins(“http://127。0。0。1:9528”, “http://localhost:9528”); } }; }}
以上兩種方式在沒有定義攔截器(Interceptor)的時候,使用一切正常,但是如果你有一個全域性的攔截器用來檢測使用者的登入態,例如下面的簡易程式碼:
public class AuthenticationInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object object) throws Exception { // 從 http 請求頭中取出 token String token = httpServletRequest。getHeader(“token”); // 檢查是否登入 if (token == null) { throw new InvalidTokenException(ResultCode。INVALID_TOKEN。getCode(), “登入態失效,請重新登入”); } return true; }}
當自定義攔截器返回true時,一切正常,但是當攔截器丟擲異常(或者返回false)時,後續的CORS設定將不會生效。
為什麼攔截器丟擲異常時,CORS不生效呢?可以看下這個issue:
when interceptor preHandler throw exception, the cors is broken
有個人提交了一個issue,說明如果在自定義攔截器的preHandler方法中丟擲異常的話,透過 CorsRegistry 設定的全域性 CORS 配置就失效了,但是Spring Boot 的成員不認為這是一個Bug。
然後提交者舉了個具體的例子:
他先定義了CorsRegistry,並添加了一個自定義的攔截器,攔截器中丟擲了異常
然後他發現AbstractHandlerMapping在新增CorsInterceptor的時候,是將 Cors 的攔截器加在攔截器鏈的最後:
那就會造成上面說的問題,在自定義攔截器中丟擲異常之後,CorsInterceptor 攔截器就沒有機會執行向 response 中設定 CORS 相關響應頭了。
issue的提交者也給出瞭解決的方案,就是將用來處理 Cors 的攔截器 CorsInterceptor 加在攔截器鏈的第一個位置:
這樣的話請求來了之後,第一個就會為 response 設定相應的 CORS 響應頭,後續如果其他自定義攔截器丟擲異常,也不會有影響了。
感覺是一個可行的解決方案,但是 Spring Boot 的成員認為這不是 Spring Boot 的Bug,而是 Spring Framework 的 Bug,所以將這個issue關閉了。
透過CorsFilter設定全域性跨域配置
既然透過攔截器設定全域性跨域配置會有問題,那我們還有另外一種方案,透過過濾器 CorsFilter 的方式來設定,程式碼如下:
@Configurationpublic class MyConfiguration { @Bean public FilterRegistrationBean corsFilter() { UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); CorsConfiguration config = new CorsConfiguration(); config。setAllowCredentials(true); config。addAllowedOrigin(“http://127。0。0。1:9528”); config。addAllowedOrigin(“http://localhost:9528”); config。addAllowedHeader(“*”); config。addAllowedMethod(“*”); source。registerCorsConfiguration(“/**”, config); FilterRegistrationBean bean = new FilterRegistrationBean(new CorsFilter(source)); bean。setOrder(0); return bean; }}
為什麼過濾器可以而攔截器不行呢?
因為過濾器依賴於 Servlet 容器,基於函式回撥,它可以對幾乎所有請求進行過濾。而攔截器是依賴於 Web 框架(如Spring MVC框架),基於反射透過AOP的方式實現的。
在觸發順序上如下圖所示:
因為過濾器在觸發上是先於攔截器的,但是如果有多個過濾器的話,也需要將 CorsFilter 設定為第一個過濾器才行。
原文:https://my。oschina。net/u/3216837/blog/3196454
推薦文章
- 三國殺:打著燈籠都找不到的忠臣有哪些?這些“燈籠忠”死遠點!
光是這個桃子只能自己用的debuff就完全杜絕謀呂蒙當忠的所有可能了,天知道為什麼遊戲裡還有那麼多奇葩忠臣會選這麼個玩意...
- 急性腰扭傷怎麼辦?別急,按這三個穴位,緩解腰扭傷造成的疼痛
除了針灸治療,穴位按摩作為中醫上的一種治療手段,對於很多急性損傷疾病都能夠起到較好的緩解作用,而對於緩解腰急性損傷有很多穴位,但較常應用、且相對有效的則為以下三種:1、腎俞穴腎俞穴對於腰急性扭傷而引起的疼痛,能夠起到較好的緩解作用,其位置主...
- 5月上旬開始,好事連連,貴人運旺,喜獲大豐收的四大生肖
生肖屬龍的人,從來都是心胸寬廣的,他們從來都是不怕吃虧,而且他們願意投入,而且付出的代價也會更大...