1. 程式人生 > 實用技巧 >跨域訪問

跨域訪問

什麼是跨域

瀏覽器對於javascript的同源策略的限制 。

跨域原因 示例
域名不同 aaa.com和bbb.com
域名相同,埠不同 aaa.com:81和aaa.com:82
二級域名不同 a.aaa.com和b.aaa.com
協議不同 http和https

為什麼有跨域問題

跨域不一定會有跨域問題。

跨域問題是瀏覽器對於ajax請求的一種安全限制,因此跨域問題是針對ajax的一種限制,一個頁面發起的ajax請求,只能是於當前頁同域名的路徑,這能有效的阻止跨站攻擊。

解決跨域問題的方案

jsonp

最早的解決方案,利用script標籤可以跨域的原理實現,存在一定的侷限性(需要服務的支援,只能發起GET請求)。

nginx反向代理

利用nginx反向代理把跨域為不跨域,支援各種請求方式。

缺點:需要在nginx進行額外配置,語義不清晰

CORS

規範化的跨域請求解決方案,安全可靠。

  • 優勢:
    • 在服務端進行控制是否允許跨域,可自定義規則。
    • 支援各種請求方式。
  • 劣勢:
    • 會產生額外請求的問題。

jsonp解決跨域問題

jquery的jsonp方式跨域請求

jsonp方式不支援POST方式跨域請求,就算指定成POST方式,會自動轉為GET方式;而後端如果設定成POST方式了,那就請求不了了。

 1 $(function(){
 2       var name='cc';
 3       var
looks='handsome'; 4       $.ajax({ 5             url: "http://localhost:9090/student", 6 type: "GET", 7             data:{ 8               name:name, 9               looks:looks 10             }, 11             cache:false,  12 dataType: "jsonp", //指定伺服器返回的資料型別
13             jsonp:'theFunction',//用以獲得jsonp回撥函式名的引數名(預設為:callback) 14      15             jsonpCallback: "showData", //指定回撥函式名稱 16             success: function (data) { 17                 console.info("呼叫success"); } 18            }); 19       }) 

基於script標籤實現跨域

凡是擁有src這個屬性的標籤都可以跨域例如<script><img><iframe>。

 1 <!DOCTYPE html>
 2 <html>
 3 <head>
 4 <meta charset="UTF-8">
 5 <title>Insert title here</title>
 6 
 7 <script type="text/javascript">
 8     var message = function(data) {
 9         alert(data[1].title);
10     };
11 </script>
12 
13 <script type="text/javascript" src="http://web.cn/js/message.js"></script>
14 </head>
15 <body>
16 <div id='testdiv'></div>
17 </body>
18 </html>

vue.js

1 this.$http.jsonp('http://www.aaa.com:8080/login', {
2     params: {},
3     jsonp: 'onBack'
4 }).then((res) => {
5     console.log(res); 
6 })

Nginx反向代理解決跨域問題

配置Nginx:/nginx-1.18.0/conf/nginx.conf

 1 worker_processes  1;
 2 
 3 events {
 4     worker_connections  1024;
 5 }
 6 
 7 
 8 http {
 9     include       mime.types;
10     default_type  application/octet-stream;
11 
12     sendfile        on;
13     keepalive_timeout  65;
14 
15     server {
16         listen       80;
17         server_name  manage.aaa.com;
18 
19        location / {
20             proxy_pass http://127.0.0.1:8001;
21             proxy_connect_timeout 600;
22             proxy_read_timeout 600;
23         }
24         
25         location /api {
26             proxy_pass http://127.0.0.1:8002;
27             proxy_connect_timeout 600;
28             proxy_read_timeout 600;
29         }
30 
31         error_page   500 502 503 504  /50x.html;
32         location = /50x.html {
33             root   html;
34         }
35     }
36 }

CORS解決跨域問題

什麼是CORS

CORS是一個W3C標準,全稱是"跨域資源共享"(Cross-origin resource sharing)。

它允許瀏覽器向跨源伺服器,發出XMLHttpRequest請求,從而克服了AJAX只能同源使用的限制。

CORS需要瀏覽器和伺服器同時支援。目前,所有瀏覽器都支援該功能,IE瀏覽器不能低於IE10。

瀏覽器端:目前,所有瀏覽器都支援該功能(IE10以下不行)。整個CORS通訊過程,都是瀏覽器自動完成,不需要使用者參與。

服務端:CORS通訊與AJAX沒有任何差別,因此你不需要改變以前的業務邏輯。只不過,瀏覽器會在請求中攜帶一些頭資訊,我們需要以此判斷是否允許其跨域,然後在響應頭中加入一些資訊即可。這一般通過過濾器完成即可。

CORS的原理

瀏覽器會將ajax請求分為兩類,其處理方案略有差異:簡單請求、特殊請求。

簡單請求

只要同時滿足以下兩

當瀏覽器發現發起的ajax請求是簡單請求時,會在請求頭中攜帶一個欄位:Origin大條件,就屬於簡單請求。

(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)。

當瀏覽器發現發起的ajax請求是簡單請求時,會在請求頭中攜帶一個欄位:Origin。

Origin中會指出當前請求屬於哪個域(協議+域名+埠)。服務會根據這個值決定是否允許其跨域。

如果伺服器允許跨域,需要在返回的響應頭中攜帶下面資訊:

1 // 白名單(可接受的域),是一個具體域名或者*(代表任意域名),注意:需要攜帶cookie時不能設定為*
2 Access-Control-Allow-Origin: *
3 // 是否允許攜帶cookie,預設情況下,cors不會攜帶cookie,除非這個值是true
4 Access-Control-Allow-Credentials: false
5 Content-Type: text/html; charset=utf-8

要想操作cookie,需要滿足3個條件:

  • 服務的響應頭中需要攜帶Access-Control-Allow-Credentials並且為true。
  • 瀏覽器發起ajax需要指定withCredentials 為true。
  • 響應頭中的Access-Control-Allow-Origin一定不能為*,必須是指定的域名。

特殊請求

不符合簡單請求的條件,會被瀏覽器判定為特殊請求,,例如請求方式為PUT。

預檢請求:特殊請求會在正式通訊之前,增加一次HTTP查詢請求,稱為"預檢"請求(preflight)。

瀏覽器先詢問伺服器,當前網頁所在的域名是否在伺服器的許可名單之中,以及可以使用哪些HTTP動詞和頭資訊欄位。只有得到肯定答覆,瀏覽器才會發出正式的XMLHttpRequest請求,否則就報錯。

一個“預檢”請求的樣板:

1 OPTIONS /cors HTTP/1.1
2 Origin: http://manage.leyou.com
3 Access-Control-Request-Method: PUT
4 Access-Control-Request-Headers: X-Custom-Header
5 Host: api.leyou.com
6 Accept-Language: en-US
7 Connection: keep-alive
8 User-Agent: Mozilla/5.0...

與簡單請求相比,除了Origin以外,多了兩個頭:Access-Control-Request-Method(接下來會用到的請求方式,比如PUT)、Access-Control-Request-Headers(會額外用到的頭資訊)。

預檢請求的響應(服務的收到預檢請求,如果許可跨域,會發出響應):

 1 HTTP/1.1 200 OK
 2 Date: Mon, 01 Dec 2008 01:15:39 GMT
 3 Server: Apache/2.0.61 (Unix)
 4 Access-Control-Allow-Origin: http://manage.leyou.com
 5 Access-Control-Allow-Credentials: true
 6 Access-Control-Allow-Methods: GET, POST, PUT
 7 Access-Control-Allow-Headers: X-Custom-Header
 8 Access-Control-Max-Age: 1728000
 9 Content-Type: text/html; charset=utf-8
10 Content-Encoding: gzip
11 Content-Length: 0
12 Keep-Alive: timeout=2, max=100
13 Connection: Keep-Alive
14 Content-Type: text/plain

除了Access-Control-Allow-Origin和Access-Control-Allow-Credentials以外,這裡又額外多出3個頭:Access-Control-Allow-Methods(允許訪問的方式)、Access-Control-Allow-Headers(允許攜帶的頭)、Access-Control-Max-Age(本次許可的有效時長,單位是秒,過期之前的ajax請求就無需再次進行預檢了)。

如果瀏覽器得到上述響應,則認定為可以跨域,後續就跟簡單請求的處理是一樣的了。

java程式碼實現CORS

服務端可以通過攔截器統一實現,不必每次都去進行跨域判定的編寫。

事實上,SpringMVC已經幫我們寫好了CORS的跨域過濾器:CorsFilter ,內部已經實現了剛才所講的判定邏輯,我們直接用就好了。

 1 package com.test.config;
 2 
 3 import org.springframework.context.annotation.Bean;
 4 import org.springframework.context.annotation.Configuration;
 5 import org.springframework.web.cors.CorsConfiguration;
 6 import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
 7 import org.springframework.web.filter.CorsFilter;
 8 
 9 /**
10  * @Program: test
11  * @Package: com.test.config
12  * @ClassName: CorsConfiguration
13  * @Description: Cors解決跨域問題
14  * @Author: tankang
15  * @Create: 2020-09-08 23:54
16  */
17 @Configuration
18 public class CorsConfiguration {
19     @Bean
20     public CorsFilter corsFilter(){
21         // 初始化cors配置物件
22         CorsConfiguration corsConfiguration = new CorsConfiguration();
23         corsConfiguration.addAllowedOrigin("http://manage.aaa.com");// 允許跨域的域名,如果需要攜帶cookie,不能寫*,*代表允許所有域名
24         corsConfiguration.setAllowCredentials(true);// 允許攜帶cookie
25         corsConfiguration.addAllowedMethod("*");// *代表所有的請求方法,get/post/put/delete ...
26         corsConfiguration.addAllowedHeader("*");// 允許攜帶任何頭資訊
27         // 初始化cors配置源物件
28         UrlBasedCorsConfigurationSource urlBasedCorsConfigurationSource = new UrlBasedCorsConfigurationSource();
29         urlBasedCorsConfigurationSource.registerCorsConfiguration("/**", corsConfiguration);
30         // 返回corsFilter例項,引數cors配置源物件
31         return new CorsFilter(urlBasedCorsConfigurationSource);
32     }
33 }