跨域訪問-預請求及跨域常見問題

預請求

參考:https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Access_control_CORS#預請求javascript

簡而言之,在跨域而且嘗試添加一些特殊頭及自定義頭的狀況下,因爲瀏覽器的安全機制,會加多一次OPTIONS預請求(詢問請求),與跨域服務器協商能夠設置的頭部信息,能夠容許的HTTP協議等等信息。php

以以下圖一次跨域請求爲例。html

圖中代碼以下前端

 1 var settings = {
 2     type: "POST",
 3     url: 'http://www.movesun.com/cors.php?allow_method=PUT',
 4     contentType: "application/json",
 5     dataType:"json",
 6     data : {
 7         "name" : "lvyahui"
 8     },
 9     xhrFields : {
10         // withCredentials : true
11     },
12     success: function(resp) {
13         console.log(resp);
14     }
15     ,
16     headers: {
17         appkey:"87a8ea08-dbaa-11e6-b3f9-7056818a4db5",
18         "X_forwarded-for":"10.104.239.XXX"
19     }
20 };
21 $.ajax(settings);

能夠看到,這段代碼在movesun.com網站下,嘗試向www.movesun.com發送跨域POST 請求,而且有自定義頭(Content-Type設置了application/json類型也是緣由之一),所以瀏覽器在發送真實post請求以前,發起了一個OPTIONS請求詢問。java

請求之因此能夠成功,是由於後端服務器正常處理了OPTIONS請求,而且響應了正確的跨域響應頭,後端代碼cors.php以下nginx

 1 <?php
 2 
 3 if('OPTIONS' ===  $_SERVER['REQUEST_METHOD']){
 4     header('Access-Control-Allow-Origin:*');
 5     header('Access-Control-Allow-Headers:appkey,X_forwarded-for,Content-Type');
 6     header('Access-Control-Allow-Methods:' . (isset($_GET['allow_method']) ? $_GET['allow_method'] : 'OPTIONS'));
 7     //header('Access-Control-Allow-Credentials:true');
 8     exit(0);
 9 }
10 
11 header('Access-Control-Allow-Origin:*');
12 
13 echo json_encode(array(
14     'code'  =>  0,
15     'msg'   =>  '',
16     'data'  =>  array()
17 ));exit(0);

能夠看到服務器判斷請求類型爲OPTIONS時,指定了以下幾個Http響應頭ajax

一、Access-Control-Allow-Origin  : 跨域服務器容許的來源地址(跟請求的Origin進行匹配),能夠是*或者某個確切的地址,不容許多個地址。固然後臺代碼能夠動態判斷來源地址進行動態設置,這主要是由於有時須要容許任意來源訪問,而且要攜帶Cookie,此時須要明確指定地址(緣由在文後常見問題中說明),下面這段PHP代碼和Java代碼(注意Java代碼中Cookie沒有取端口,由於Cookie端口不一樣也算同域,能夠訪問到)就是取來源地址並響應chrome

 1 if (isset($_SERVER['HTTP_REFERER'])) {
 2     $urls = parse_url($_SERVER['HTTP_REFERER']);
 3     $url = $urls['scheme'] . '://' . $urls['host'];
 4     if (isset($urls['port'])) {
 5         $url .= ':' . $urls['port'];
 6     }
 7 } else {
 8     $url = '*';
 9 }
10 
11 header("Access-Control-Allow-Origin: " . $url);
 1 public void filter(ContainerRequestContext requestContext) throws IOException {
 2     String origin = requestContext.getHeaderString("Origin");
 3     if(origin != null && !origin.trim().equals("")
 4             // postMan 請求的protocol 是 chrome-extension://
 5             && origin.startsWith("http://")){
 6         URL url  = new URL(origin);
 7         String strUrl = url.getProtocol() + "://" + url.getHost();
 8         if(url.getPort() > 0){
 9             strUrl += ":" + url.getPort();
10         }
11         originUrl = strUrl;
12         if(!cookieDomainAuto
13                 && (sysConfig.getCookieDomain() == null || sysConfig.getCookieDomain().equals(""))){
14             cookieDomainAuto = true;
15         }
16         if(cookieDomainAuto){
17             // 動態判斷 cookie domain
18             if(url.getHost().matches(PATTERN_IP)){
19                 // IP
20                 sysConfig.setCookieDomain(url.getHost());
21             } else {
22                 int start = url.getHost().lastIndexOf('.',url.getHost().lastIndexOf('.') - 1);
23                 String domain;
24                 if(start > 0){
25                     domain = url.getHost().substring(start + 1);
26                 }else{
27                     domain = url.getHost();
28                 }
29                 // domain
30                 sysConfig.setCookieDomain(domain);
31             }
32         }
33     }
34 }
Java動態設置Allow-Origin與Cookie Domain

二、Access-Control-Allow-Methods:跨域服務器容許的請求方法。經測試發現,不論Access-Control-Allow-Methods設置爲簡單請求仍是複雜請求類型,全部的簡單的請求(GET,HEAD,POST)也是能夠正常請求的。json

三、Access-Control-Allow-Headers:跨域服務器容許客戶端添加或自定義哪些http 頭。後端

下面是這兩次請求的報文

OPTIONS請求報文

 1 OPTIONS http://www.movesun.com/cors.php HTTP/1.1
 2 Host: www.movesun.com
 3 Proxy-Connection: keep-alive
 4 Pragma: no-cache
 5 Cache-Control: no-cache
 6 Access-Control-Request-Method: POST
 7 Origin: http://movesun.com
 8 User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.75 Safari/537.36
 9 Access-Control-Request-Headers: appkey, content-type, x_forwarded-for
10 Accept: */*
11 Referer: http://movesun.com/
12 Accept-Encoding: gzip, deflate, sdch
13 Accept-Language: zh-CN,zh;q=0.8
14 
15 
16 HTTP/1.1 200 OK
17 Date: Fri, 10 Mar 2017 05:48:07 GMT
18 Server: Apache
19 Access-Control-Allow-Origin: *
20 Access-Control-Allow-Headers: appkey,X_forwarded-for,Content-Type
21 Access-Control-Allow-Methods: POST
22 Vary: User-Agent,Accept-Encoding
23 Content-Encoding: gzip
24 Content-Length: 20
25 Content-Type: text/html
26 X-Cache: MISS from SK-SQUIDDEV-11
27 X-Cache-Lookup: MISS from SK-SQUIDDEV-11:8080

POST請求報文

 1 POST http://www.movesun.com/cors.php HTTP/1.1
 2 Host: www.movesun.com
 3 Proxy-Connection: keep-alive
 4 Content-Length: 12
 5 Pragma: no-cache
 6 Cache-Control: no-cache
 7 Origin: http://movesun.com
 8 User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.75 Safari/537.36
 9 Content-Type: application/json
10 Accept: application/json, text/javascript, */*; q=0.01
11 X_forwarded-for: 10.104.239.194
12 appkey: 87a8ea08-dbaa-11e6-b3f9-7056818a4db5
13 Referer: http://movesun.com/
14 Accept-Encoding: gzip, deflate
15 Accept-Language: zh-CN,zh;q=0.8
16 name=lvyahui

從報文中能夠看出,OPTIONS請求後臺能夠拿到URL中的GET參數,也就是說,若是真實請求是GET請求,則後端在處理來詢問的OPTIONS請求時,就能夠獲取到全部查詢參數了。如mozilla官網所寫,筆者調試發現,一些跨域請求,即使拋出了錯誤的狀況下,請求也真的到了後臺服務器,只是響應被瀏覽器攔截了。

另外,有時不想在後臺代碼中處理OPTIONS請求,則能夠在nginx server節點下作以下配置,表示攔截處理全部OPTIONS請求。

 1 location ^~ / {
 2     if ($request_method = OPTIONS ) {
 3         add_header Content-Length 0;
 4         add_header Content-Type text/plain;
 5         add_header 'Access-Control-Allow-Origin' '*';
 6         add_header 'Access-Control-Allow-Methods' '*';
 7         add_header 'Access-Control-Allow-Headers' 'appkey,X_forwarded-for,Content-Type';
 8         return 200;
 9     }
10 }

 

常見跨域問題

下面是一些跨域下常見的一些問題

  • 添加了跨域服務器不容許的自定義頭會拋出 XMLHttpRequest cannot load http://www.movesun.com/cors.php. Request header field custom_heaer is not allowed by Access-Control-Allow-Headers in preflight response.
  • 當未設置容許某種複雜請求時,使用複雜請求就會拋出以下錯誤,表示真實請求使用了服務器不容許的方法。在只容許POST的狀況下,GET請求是能夠被髮送的,HEAD也能夠成功,僅僅容許GET的狀況下,POST也是能夠發送成功的,HEAD也能夠成功 。簡單請求均可以成功,等等,其實經測試發現,不論Access-Control-Allow-Methods設置爲簡單請求仍是複雜請求類型,全部的簡單的請求(GET,HEAD,POST)也是能夠正常請求的。XMLHttpRequest cannot load http://www.movesun.com/cors.php. Method PUT is not allowed by Access-Control-Allow-Methods in preflight response.
  • 預處理請求沒有沒正常處理,這種是詢問請求響應了非200狀態碼,會拋出 XMLHttpRequest cannot load http://movesun.qq.com/cors.php?allow_method=PUT. Response for preflight has invalid HTTP status code 405 。如  
  • 1 if('OPTIONS' ===  $_SERVER['REQUEST_METHOD']){
    2     header("HTTP/1.1 405 Method Not Allowed");
    3     exit(-1);
    4 }

     

  • 錯誤是來源地址不是服務器所容許的來源地址。以下,此時服務器響應 Access-Control-Allow-Origin:http://www.movesun.com,表示跨域服務器容許在Origin:http://www.movesun.com 的機器上訪問,而用戶試圖在http://movesun.com跨域請求目的服務器http://movesun.qq.com/cors.php?allow_method=PUT:XMLHttpRequest cannot load http://movesun.qq.com/cors.php?allow_method=PUT. Response to preflight request doesn't pass access control check: The 'Access-Control-Allow-Origin' header has a value 'http://www.movesun.com' that is not equal to the supplied origin. Origin 'http://movesun.com' is therefore not allowed access.
  • 前端設置了攜帶簽名標誌,可是跨域服務器不容許攜帶,沒有設置 Access-Control-Allow-Credentials:true 。如:XMLHttpRequest cannot load http://movesun.qq.com/cors.php?allow_method=PUT. Credentials flag is 'true', but the 'Access-Control-Allow-Credentials' header is ''. It must be 'true' to allow credentials. Origin 'http://movesun.com' is therefore not allowed access.
  • 前端嘗試在真實請求中攜帶簽名Cookie,跨域服務器容許攜帶Cookie,可是服務器容許全部來源地址,會報這個錯誤,在跨域攜帶cookie時,必須明確指定來源地址,好比 Access-Control-Allow-Origin:http://movesun.com。例如:XMLHttpRequest cannot load http://movesun.qq.com/cors.php?allow_method=PUT. A wildcard '*' cannot be used in the 'Access-Control-Allow-Origin' header when the credentials flag is true. Origin 'http://movesun.com' is therefore not allowed access. The credentials mode of an XMLHttpRequest is controlled by the withCredentials attribute.
而且 跨域攜帶Cookie時,跨域服務器處理詢問請求(OPTIONS)和真實請求,都必須響應明確的來源地址和容許攜帶cookie的標誌。不然會報上面兩種錯誤。固然很顯然的兩次響應的Allow-Origin都是一致的。以下
 1 if('OPTIONS' ===  $_SERVER['REQUEST_METHOD']){
 2     header('Access-Control-Allow-Origin:http://movesun.com');
 3     header('Access-Control-Allow-Headers:appkey,X_forwarded-for,Content-Type');
 4     header('Access-Control-Allow-Methods:' . (isset($_GET['allow_method']) ? $_GET['allow_method'] : 'OPTIONS'));
 5     header('Access-Control-Allow-Credentials:true');
 6     exit(0);
 7 }
 8 header('Access-Control-Allow-Origin:http://movesun.com');
 9 header('Access-Control-Allow-Credentials:true');
10 echo json_encode(array(
11     'code'  =>  0,
12     'msg'   =>  '',
13     'data'  =>  array()
14 ));exit(0);

 

注意:文中的測試接口 在 http://movesun.com/cors.php  或者 http://www.movesun.com/cors.php,感興趣的讀者能夠用這個接口測試。

相關文章
相關標籤/搜索