跨域問題的由來、測試和VUE解決跨域問題

一、跨域問題是怎樣造成的

1.1  跨域問題的由來

很多人都或多或少了解過跨域問題,尤其在現如今前後端分離大行其道的時候。
例如你在本地開發一個前端項目,這個項目是通過 node 運行的,端口是9528,而服務端是通過 spring boot 提供的,端口號是7001。
當你調用一個服務端接口時,很可能得到類似下面這樣的一個錯誤:

然後你在發送請求的地方debug,在出現異常的地方你將得到這樣的結果:

異常對象很詭異,返回的 response 是 undefined 的,並且 message 消息中只有一個"Network Error"。
看到這裏你應該要知道,你遇到跨域問題了。
但是你需要明確的一點是,這個請求已經發出去了,服務端也接收到並處理了,但是返回的響應結果不是瀏覽器想要的結果,所以瀏覽器將這個響應的結果給攔截了,這就是爲什麼你看到的response是undefined。

1.2 瀏覽器的同源策略

那瀏覽器爲什麼會將服務端返回的結果攔截掉呢?
這就需要我們瞭解瀏覽器基於安全方面的考慮,而引入的 同源策略(same-origin policy) 了。
早在1995年,Netscape 公司就在瀏覽器中引入了“同源策略”。
最初的 “同源策略”,主要是限制Cookie的訪問,A網頁設置的 Cookie,B網頁無法訪問,除非B網頁和A網頁是“同源”的。
那麼怎麼確定兩個網頁是不是“同源”呢,所謂“同源”就是指"協議+域名+端口"三者相同,即便兩個不同的域名指向同一個ip地址,也非同源。

那麼爲什麼要做這個同源的限制呢?因爲如果沒有同源策略的保護,瀏覽器將沒有任何安全可言。
老李是一個釣魚愛好者,經常在 我要買(51mai.com) 的網站上買各種釣魚的工具,並且通過 銀行(yinhang.com) 以賬號密碼的方式直接支付。
這天老李又在 51mai.com 上買了一根魚竿,輸入銀行賬號密碼支付成功後,在支付成功頁看到一個叫 釣魚(diaoyu.com) 的網站投放的一個"免費領取魚餌"的廣告。
老李什麼都沒想就點擊了這個廣告,跳轉到了釣魚的網站,殊不知這真是一個 “釣魚” 網站,老李銀行賬戶裏面錢全部被轉走了。
以上就是老李的錢被盜走的過程:

  • 老李購買魚竿,並登錄了銀行的網站輸入賬號密碼進行了支付,瀏覽器在本地緩存了銀行的Cookie
  • 老李點擊釣魚網站,釣魚網站使用老李登錄銀行之後的Cookie,僞造成自己是老李進行了轉賬操作。

這個過程就是著名的CSRF(Cross Site Request Forgery),跨站請求僞造,正是由於可能存在的僞造請求,導致了瀏覽器的不安全。

1.3 同源策略限制哪些行爲

同源策略是一個安全機制,他本質是限制了從一個源加載的文檔或腳本如何與來自另一個源的資源進行交互,這是一個用於隔離潛在惡意文件的重要安全機制。

隨着互聯網的發展,"同源策略"越來越嚴格,不僅限於Cookie的讀取。目前,如果非同源,共有三種行爲受到限制

(1) Cookie、LocalStorage 和 IndexDB 無法讀取。
(2) DOM 無法獲得。
(3) 請求的響應被攔截。

雖然這些限制是必要的,但是有時很不方便,合理的用途也會受到影響,所以爲了能夠獲取非“同源”的資源,就有了跨域資源共享

1.4 跨域資源共享

根據上述介紹可知文章開頭的請求被攔截的原因,請求的源和服務端的源不是“同源”,而服務端又沒有設置允許的跨域資源共享,所以請求的響應被瀏覽器給攔截掉了。

CORS 是一個 W3C 標準,全稱是"跨域資源共享"(Cross Origin Resource Sharing),它允許瀏覽器向跨源服務器,發出 XMLHttpRequest 請求,從而克服了只能發送同源請求的限制

1.5 CORS實現機制和請求類型

1.5.1 跨域資源共享的實現機制

當一個資源(origin)通過腳本向另一個資源(host)發起請求,而被請求的資源(host)和請求源(origin)是不同的源時(協議、域名、端口不全部相同),瀏覽器就會發起一個 跨域 HTTP 請求 ,並且瀏覽器會自動將當前資源的域添加在請求頭中一個叫 Origin 的 Header 中。
當然了,有三個標籤本身就是允許跨域加載資源的:

<img src=XXX>
<link href=XXX>
<script src=XXX>

比如某個網站的首頁 http://domain-a.com/index.html 通過 <img src="http://domain-b.com/image.jpg" /> 來加載其他域上的圖片,除此之外還有諸如通過 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 中指定。

1.5.2 CORS的兩種請求類型

CORS有兩種類型的請求,分別是:簡單請求(simple request)和非簡單請求(not-so-simple request)
只要同時滿足以下兩大條件,就屬於簡單請求。

(1) 請求方法是以下三種方法之一:

HEAD
GET
POST

(2) HTTP的頭信息不超出以下幾種字段:

Accept
Accept-Language
Content-Language
Last-Event-ID
Content-Type:只限於三個值 application/x-www-form-urlencoded 、multipart/form-data、text/plain

凡是不同時滿足上面兩個條件,就屬於非簡單請求,瀏覽器對這兩種請求的處理,是不一樣的。

爲什麼會有兩種不同類型的請求呢?
CORS 規範要求,對那些可能對服務器數據產生副作用的 HTTP 請求方法(特別是 GET 以外的 HTTP 請求,或者搭配某些 MIME 類型的 POST 請求),瀏覽器必須首先使用 OPTIONS 方法發起一個預檢請求(preflight request),從而獲知服務端是否允許該跨域請求。

服務器確認允許之後,瀏覽器才能發起實際的 HTTP 請求。在預檢請求的返回中,服務器端也可以通知客戶端,是否需要攜帶身份憑證(包括 Cookies 和 HTTP 認證相關的數據)。

非簡單請求就要求瀏覽器先發送一個預檢請求,預檢通過後再發送實際的請求。

1.6如何解決跨院問題

實現了CORS就解決了跨域問題,解決跨域問題主要有以下幾種思路:

1.6.1通過JSONP

利用 <script> 標籤沒有跨域限制的漏洞,網頁可以得到從其他來源動態產生的 JSON 數據。JSONP請求一定需要對方的服務器做支持纔可以。

JSONP 和 AJAX 相同,都是客戶端向服務器端發送請求,從服務器端獲取數據的方式。但 AJAX 屬於同源策略,JSONP 屬於非同源策略(支持跨域請求)。JSONP優點是簡單兼容性好,可用於解決主流瀏覽器的跨域數據訪問的問題。缺點是僅支持 GET 方法具有侷限性,不安全可能會遭受XSS攻擊。

1.6.2利用反向代理服務器

同源策略是瀏覽器需要遵循的標準,而如果是服務器向服務器請求就無需遵循同源策略

所以通過反向代理服務器可以有效的解決跨域問題,代理服務器需要做以下幾個步驟:

1.接受客戶端的請求
2.將請求轉發給實際的服務器
3.將服務器的響應結果返回給客戶端

Nginx就是類似的反向代理服務器,可以通過配置Nginx代理來解決跨域問題。
使用nginx服務器解決跨域問題,有兩種理解,一是將瀏覽器向服務器請求轉爲服務器向服務器的請求,二是從瀏覽器角度將不同源的應用訪問轉化同源的應用訪問。具體實現方式可以參考下一章《vue跨域問題的解決方案》

1.6.3配置服務器支持CORS

最安全的還是服務端來設置允許哪些來源的請求,即服務端在接收到請求之後,對允許的請求源設置 Access-Control-Allow-Origin 的響應頭。
以Springboot爲例,可以通過CorsFilter設置全局跨域配置,代碼如下:

@Configuration
public 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;
    }
}

配置文件全部代碼

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import java.util.ArrayList;
import java.util.List;

@Configuration
public class CrossDomainConfiguration implements WebMvcConfigurer {
    private CorsConfiguration buildConfig() {
        CorsConfiguration corsConfiguration = new CorsConfiguration();
        //sessionid 多次訪問一致
        corsConfiguration.setAllowCredentials(true);

        // 允許訪問的客戶端域名
        List<String> allowedOriginPatterns = new ArrayList<>();
        allowedOriginPatterns.add("*");
        corsConfiguration.setAllowedOriginPatterns(allowedOriginPatterns);
        // 允許任何域名使用,版本高於2.4.0不能使用
//        corsConfiguration.addAllowedOrigin("*");
        // 允許任何頭
        corsConfiguration.addAllowedHeader("*");
        // 允許任何方法(post、get等)
        corsConfiguration.addAllowedMethod("*");
        corsConfiguration.setMaxAge(3600 * 24L);
        return corsConfiguration;
    }

    @Bean
    public CorsFilter corsFilter() {
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        // 對接口配置跨域設置
        source.registerCorsConfiguration("/**", buildConfig());
        return new CorsFilter(source);
    }
}

 

二、Vue中跨域問題的解決方案

根據上述1.6.2節中跨域問題的解決方案,可以在不修改後端應用的情況下用來解決vue本地和生產環境中的跨域問題。核心都是通過轉發,將不同源的訪問變爲同源的訪問。具體如下

開發環境

2.1 開發環境

以vue3爲例,需要在配置文件vue.config.js中增加代理服務

  devServer: {
    proxy: {
      '/api': {
          // 此處的寫法,目的是爲了 將 /api 替換成 https://www.baidu.com/
          target: 'https://test.com/',
          // 允許跨域
          changeOrigin: true,
          ws: true,
          pathRewrite: {
              '^/api': ''
          }
      }
   }
}

其實/api就是接口實際請求的前綴

比如之前請求接口的時候 url 是這樣的:
通常來說我們都會把域名寫在一個公共請求方法裏面,我們只需要傳入要調用的接口的後綴名稱
比如 請求 test.com/mobile/index/index 我們只需要傳入: mobile/index/index
那麼公共的域名就是 test.com/
而你要做的,就是在請求接口的公共域名,把test.com改爲api/即可!

注意這個方式只能在開發環境中使用。

2.2生產環境

生產環境可以考慮使用vue的nginx web服務器增加轉發配置,同樣是通過服務器轉發,將不同源應用訪問轉換爲同源應用訪問。

  server {
    listen 9526;
    server_name my.test.com;
	
    location /api {
      #rewrite ^.+(?<!js|css|png|map)$ /index.html break;
      proxy_pass   http://55.13.108.41:44040/;   #通過nnginx轉發,將不同源應用訪問轉換爲同源應用訪問
    }

由於該轉發配置是配置在服務器上的,所以在生產環境和開發環境中同樣都支持。

三、如何測試跨域

    一般測試跨域的時候都是單獨寫一個項目,在瀏覽器Web應用中用不同的端口來調被測應用,這樣較爲繁瑣。後來再postman中找測試跨域的功能,但沒有找到。偶然發現瀏覽器自己就可以測試跨域,方法也很簡單, 隨便打開一個網站後打開【開發者工具】,裏面的【Console】可以直接輸入js代碼測試:

輸入下面的代碼:

var token= "LtSFVqKxvpS1nPARxS2lpUs2Q2IpGstidMrS8zMhNV3rT7RKnhLN6d2FFirkVEzVIeexgEHgI/PtnynGqjZlyGkJa4+zYIXxtDMoK/N+AB6wtsskYXereH3AR8kWErwIRvx+UOFveH3dgmdw1347SYjbL/ilGKX5xkoZCbfb1f0=,LZkg22zbNsUoHAgAUapeBn541X5OHUK7rLVNHsHWDM/BA4DCIP1f/3Bnu4GAElQU6cds/0fg9Li5cSPHe8pyhr1Ii/TNcUYxqHMf9bHyD6ugwOFTfvlmtp6RDopVrpG24RSjJbWy2kUOOjjk5uv6FUTmbrSTVoBEzAXYKZMM2m4=,R4QeD2psvrTr8tkBTjnnfUBw+YR4di+GToGjWYeR7qZk9hldUVLlZUsEEPWjtBpz+UURVmplIn5WM9Ge29ft5aS4oKDdPlIH8kWNIs9Y3r9TgH3MnSUTGrgayaNniY9Ji5wNZiZ9cE2CFzlxoyuZxOcSVfOxUw70ty0ukLVM/78=";
var xhr = new XMLHttpRequest();
xhr.open('GET', 'http://127.0.0.1:8081/thc_bpm/actionLog/test');
xhr.setRequestHeader("x-access-token",token);
xhr.send(null);
xhr.onload = function(e) {
    var xhr = e.target;
    console.log(xhr.responseText);
}

沒有token驗證的可以去掉token設置
輸入完後直接按回車鍵就可以查看跨域測試的返回結果:

如上,根據查信息  【Access to XMLHttpRequest at 'http://YY' from 'http://XX' has been blocked by CORS policy:
No 'Access-Control-Allow-Origin' header is present on the requested resource.
GET http://YY net::ERR_FAILED
】可以判斷跨域訪問失敗。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章