分享跨域訪問的解決方案與基礎分析

逍遙俠發表於2019-03-04

什麼是跨域訪問?

由於瀏覽器同源策略,凡是傳送請求url的協議、域名、埠三者之間任意一個與當前頁面地址不同即為跨域。存在跨域的情況:

  • 網路協議不同,如http協議訪問https協議。

  • 埠不同,如80埠訪問8080埠。

  • 域名不同,如qianduanblog.com訪問baidu.com。

  • 子域名不同,如abc.qianduanblog.com訪問def.qianduanblog.com。

  • 域名和域名對應ip,如www.a.com訪問20.205.28.90.

  • 請求的資料是XHR(XMLHttpRequest)瀏覽器會有跨域的限制。如果不是XHR型別的則不會限制

我們的瀏覽器需要禁止跨域呢?

這裡舉兩個常見的例子:

1.我們知道一幫瀏覽器都使用cookie去儲存我們的使用者登入資訊。如果允許跨域訪問,那麼別的網站只需要一段指令碼就可以獲取你的cookie,從而冒充你的身份去登入網站,造成非常大的安全問題。

2.假設現在有兩個不同域,如果沒有這一安全策略,那麼當使用者在訪問a.com時,a.com的一段指令碼就可以在不載入b.com的頁面而隨意修改或者獲取b.com上面的內容。這樣將會導致b.com頁面的頁面發生混亂。

跨域的解決辦法

其實跨域的解決辦法從本質上去區分,可以劃分為兩類。

第一類:被呼叫方

第二類:呼叫方

接下來我們就開始基於這兩種型別去解決跨域問題。

被呼叫方的解決辦法

這裡在說明程式碼之前,我們要先知道在我們的請求中可以分為兩種請求:(1)簡單請求(2)非簡單請求

簡單請求:
        在heard裡面無定義頭
        Content-Type為一下幾種:
            text/plain
            multipart/from-data
            application/x-www-form-urlencoded
常見非簡單請求:
        put,delete方法的ajax請求
        傳送json格式的ajax請求
        帶自定義頭的ajax請求複製程式碼
簡單請求與非簡單請求的區別
簡單請求:瀏覽器會先自行後判斷
非簡單請求:瀏覽器先判斷後執行(OPTIONS)
複製程式碼

因此我們在訪問非簡單請求的時候,我們可以看到傳送的方法會是opttions。意思就是伺服器先詢問服務是否存在相關資源。當存在相關資源的時候才能正常返回。

接下來我們就可以開展被呼叫方的解決方案:

解決方案1:JSONP

什麼是JSONP?

Jsonp(JSON with Padding) 是 json 的一種”使用模式”,可以讓網頁從別的域名(網站)那獲取資料,即跨域讀取資料。

下面我們展示JSONP的使用案例

#這裡我們模仿ajax進行一個跨越訪問,其datatype需要設定為jsonp
#裡面的jsonp,設定回撥函式的名稱為callback
<script type="text/javascript">  
    $.ajax({  
        url:"http://crossdomain.com/services.php",  
        dataType:`jsonp`,  
        data:``,  
        jsonp:`callback`,  
        success:function(result) {  
            for(var i in result) {  
                alert(i+":"+result[i]);//迴圈輸出a:1,b:2,etc.  
            }  
        },  
        timeout:3000  
    });  
</script> 
複製程式碼
#這是後臺返回資料的方法
#可以看出其實就是把返回的資料再包含在與前端的回撥函式的名字一樣的函式體即可
function api_jsonp_encode($json)
    {
        if (!empty($_GET[`callbak`])) {
            return $_GET[`callbak`] . `(` . $json . `)`; // jsonp
        }
        return $json; // json
    }複製程式碼

jsonp其實作為一種跨域的解決手段其實存在比較明顯的缺陷:

1.伺服器需要改動程式碼 
2.只支援get方法 
3.傳送的不是XHR複製程式碼

解決方法2:增加header頭

設定標頭檔案:
    //設定請求域名
    header(`Access-Control-Allow-Origin:*`);
    //設定請求頭
    header("Access-Control-Allow-Headers:Origin, X-Requested-With, Content-Type, Accept, Authorization");
    //設定請求方法
    header(`Access-Control-Allow-Methods:GET, POST, PUT,DELETE,OPTIONS,PATCH`);
    header(`Access-Control-Allow-Methods:*`);
    //設定預檢命令的快取頭
    header(`Access-Control-Max-Age:3600`);
    #這裡說明一下,*號代表沒有限制全匹配。因此你可以看到們設定的請求域名,還有請求方法都是有*號配置。
    #設定御檢命令快取頭就是讓瀏覽器當傳送一次御檢命令後,後期就可以在快取時間內直接使用快取,而不需要再次請求。其第二個引數為快取時間
複製程式碼

但是我們直接上述的配置,只能滿足一般情況。因為如果當跨域訪問的時候攜帶cookie,或者自定義頭的時候我們還是不能成功跨域的因此。針對這兩種情況,我們可以做出以下的調整修改

  帶Cookie的跨域
            //設定允許攜帶Cookie
            header("Access-Control-Allow-Credentials: true");
            //需要設定與伺服器匹配的域名,不能使用*
            header(`Access-Control-Allow-Origin:http://www.xxx.com`);

            #如果可能需要匹配多個允許域名,可以參考下面的動圖做法
            //通過系統變數獲取origin
            $origin = isset($_SERVER[`HTTP_ORIGIN`])? $_SERVER[`HTTP_ORIGIN`] : ``;
            //設定允許陣列列表
            $allow_origin = array(
                `http://client1.runoob.com`,
                `http://client2.runoob.com`
            );
            //判斷是否存在於陣列中
            if(in_array($origin, $allow_origin)){
                header(`Access-Control-Allow-Origin:`.$origin);
            }
複製程式碼
 自定義請求頭的跨域處理
            一般情況下可以使用以下的程式碼設定請求頭
            header("Access-Control-Allow-Headers:Origin, X-Requested-With, Content-Type, Accept, Authorization");
            但是如果不生效。可以把對應的請求頭也寫進去例如:
            header("Access-Control-Allow-Headers:Origin, X-Requested-With, Content-Type, Accept, Authorization,test-header");
複製程式碼

以上就是通過被呼叫方設定header頭去解決對應的跨域問題。

但是上述的方法還是存在一定的侷限性。因為加header頭本質上還是會改動了我們的程式碼。那麼我們還有沒有更好的方式呢?其實我們可以基於伺服器的改造

解決方法3:配置伺服器的

 ngix的配置
            server {
                    listen       80; #監聽80埠
                    server_name  demo.com; #本地域名
                    location / {
                        proxy_pass http://XXXX.com; #把所有請求都轉發到此需要提供服務的埠下
            			add_header Access-Control-Allow-Headers $http_access_control_request_headers`;
            			add_header Access-Control-Allow-Origin $http_origin;
            			add_header Access-Control-Allow-Methods *;
            			add_header Access-Control-Max-Age 3600;
            			add_header Access-Control-Allow-Credentials true;

            			#判斷如果進入的是與預檢查
            			if ($request_method = OPTIONS){
                                   return 200;
            			    }
                        }
                    }
複製程式碼
.apache配置
            //修改vhost檔案
            <virtualhost *:80="">
                DocumentRoot "C:/htdocs/demo" #目錄路徑
                ServerName demo.com           #域名
                ##ErrorLog "logs/dummy-host.localhost-error.log"
                ##CustomLog "logs/dummy-host.localhost-access.log" common

                #設定轉發(需要開啟 proxy_module,proxy_http)
                ProxyPass/ http://XXXX.com

                #把請求頭的Access-Controller-Request-Headerss值返回到Access-Control-Allow-Headers
                Header always set Access-Control-Allow-Headers "expr=%{req:Access-Controller-Request-Headers}"
                #把請求頭的orgin值返回到Access-Control-Allow-Orgin
                Header always set Access-Control-Allow-Orgin "expr=%{req:orgin}"

                Header always set Access-Control-Allow-Methods "*"
                Header always set Access-Control-Allow-Credentials "true"
                Header always set Access-Control-Max-Age "3600"

                #處理預檢命令OPTIONS,直接返回204(開啟 headers_module,rewrite_module)
                RewriteEngine On
                RewriteCond%{REQUEST_METHOD}OPTIONS
                RewriteRule^(.*)$"/"[R=204,L]
            </virtualhost>
複製程式碼

以上的伺服器配置就是用於跨域請求的被呼叫方的配置

#我們剛剛討論完了被呼叫方的設定。那麼我們再來說一下呼叫方的設定。因為在我們實際的開發中,有時候被呼叫方有可能是不配合我們進行跨域訪問的修改的,那麼我們只能自己解決跨域問題了。

在一般開發中我們解決的思路就是使用反向代理。而配置反向代理只能通過我們的伺服器去訪問對方的伺服器,從而繞過瀏覽器的這一個檻。

nginx:
        server {
                  listen       80; #監聽80埠
                  server_name  demo.com; #本地域名
                  #後期所有的呼叫介面都需要使用該地址
                  #例如:我們訪問http://aa.com/index/login
                  #改為:/ajaxserver/index/login
                  location/ajaxserver {
                          proxy_pass http://XXXX.com; #需要跨域訪問介面的的地址
                      }

                 }複製程式碼
<virtualhost *:80="">
                DocumentRoot "C:/htdocs/demo" #目錄路徑
                ServerName demo.com           #域名
                ##ErrorLog "logs/dummy-host.localhost-error.log"
                ##CustomLog "logs/dummy-host.localhost-access.log" common
                #後期所有的呼叫介面都需要使用該地址
                #例如:我們訪問http://aa.com/index/login
                #反向代理設定
                ProxyPass /ajaxserver http://XXXX.com
</virtualhost>複製程式碼

以上就是我們在跨域工作中遇上的常見情況以及解決思路和事例程式碼。

相關文章