面試官:跨域的解決辦法有哪些

深刻解析各類解決跨域的方法

同源策略

同源策略,它是由Netscape提出的一個著名的安全策略。如今全部支持JavaScript 的瀏覽器都會使用這個策略來對腳本和請求進行校驗,若不一樣源,則禁止使用。html

同源的定義

那若是判斷是否同源?主要根據三個維度,域名,協議,端口三個都相同纔算同源。
舉個🌰:前端

網站A 網站B 結果
http://www.zhenai.com http://i.zhenai.com 不一樣源,域名不一樣
http://www.zhenai.com http://www.zhenai.cn 不一樣源,域名不一樣
http://www.zhenai.com https://www.zhenai.com 不一樣源,協議不一樣
http://www.zhenai.com http://www.zhenai.com:3000 不一樣源,端口不一樣(默認端口80)

同源策略的做用

①沒法用js讀取非同源的Cookie、LocalStorage 和 IndexDB

這個主要是爲了防止惡意網站經過js獲取用戶其餘網站的cookie等用戶信息。vue

②沒法用js獲取非同源的DOM

防止惡意網站經過iframe獲取頁面dom,從而竊取頁面的信息。node

③沒法用js發送非同源的AJAX請求

防止惡意的請求攻擊服務器竊取數據信息。nginx

那是否是說非同源的請求就沒法實現呢?也不是,這就引出了咱們本文主要闡述的解決跨域請求問題的方法。ajax

jsonp

jsonp能實現跨域是利用了img、script和link標籤自身的跨域能力。
咱們知道當img或者script中的src是一個連接的時候,瀏覽器會請求這個連接獲取資源,那麼這個連接若是是跨域的,瀏覽器也會請求,從而達到了跨域請求的一個功能。npm

用法

var script = document.createElement('script');
script.src = 'http://localhost:3000/api/test.do?a=1&b=2&callback=cb';
$('body').append(script);

function cb(res){
    // do something
    console.log(res)
}

能夠看到,咱們建立一個script標籤,將src改爲咱們要請求的接口,並將script添加在body中,那麼當瀏覽器解析到這個script時,會想src對應的服務器發送一個get請求,並將參數帶過去。
而後當瀏覽器接收到服務端返回的數據,就會觸發參數中callbak對應的回調函數cb,從而完成整個get請求。json

優勢

簡單粗暴segmentfault

缺點

①只支持get請求
②須要後臺配合,將返回結果包裝成callback(res)的形式後端

防範

那若是黑客植入script腳本經過jsonp的方式對服務器進行攻擊,怎麼辦?
能夠經過頁面設置的內容安全協議csp進行防範。

cors跨域

cors 是一個W3C標準,全稱是"跨域資源共享"(Cross-origin resource sharing),它容許瀏覽器向跨源服務器發送XMLHttpRequest請求,從而克服了 AJAX 只能同源使用的限制
cors 須要瀏覽器和服務器同時支持,整個 CORS通訊過程,都是瀏覽器自動完成不須要用戶參與,對於開發者來講,cors的代碼和正常的 ajax 沒有什麼差異,瀏覽器一旦發現跨域請求,就會添加一些附加的頭信息
可是,cors不支持ie10及如下版本。

簡單請求和複雜請求

瀏覽器將cors請求分爲簡單請求和複雜請求。
簡單請求則直接發送請求到服務器,只請求一次。
而複雜請求在正式請求前都會有預檢請求,在瀏覽器中都能看到有OPTIONS請求,用於向服務器請求權限信息的,須要請求兩次。

那如何區分是簡單請求仍是複雜請求呢?

簡單請求

簡單請求必需要同時知足下面三個條件:

  1. 請求方式只能是:GET、POST、HEAD
  2. HTTP請求頭限制這幾種字段:Accept、Accept-Language、Content-Language、Content-Type、Last-Event-ID
  3. Content-type只能取:application/x-www-form-urlencoded、multipart/form-data、text/plain

content-type的類型

類型 描述
application/json 消息主體是序列化後的 JSON 字符串
application/x-www-form-urlencoded 數據被編碼爲鍵值對。這是標準的編碼格式
multipart/form-data 須要在表單中進行文件上傳時,就須要使用該格式。常見的媒體格式是上傳文件之時使用的
text/plain 數據以純文本形式(text/json/xml/html)進行編碼,其中不含任何控件或格式字符

application/json:

  • 做用: 告訴服務器請求的主題內容是json格式的字符串,服務器端會對json字符串進行解析,
  • 好處: 前端人員不須要關心數據結構的複雜度,只要是標準的json格式就能提交成功。

application/x-www-form-urlencoded:是Jquery的Ajax請求默認方式

  • 做用:在請求發送過程當中會對數據進行序列化處理,以鍵值對形式?key1=value1&key2=value2的方式發送到服務器。
  • 好處: 全部瀏覽器都支持。

複雜請求

不知足簡單請求的條件,那麼就是複雜請求。
複雜請求會在正式請求發送以前,先發一個預檢請求進行校驗,校驗經過後才能進行正式請求。
舉個🌰
瀏覽器如今要發送一個put的複雜請求,那麼在put請求發送以前,瀏覽器先發送一個options請求。
options請求頭信息:

OPTIONS /cors HTTP/1.1
Origin: localhost:3000
Access-Control-Request-Method: PUT // 表示使用的什麼HTTP請求方法
Access-Control-Request-Headers: X-Custom-Header // 表示瀏覽器發送的自定義字段
Host: localhost:3000
Accept-Language: zh-CN,zh;q=0.9
Connection: keep-alive
User-Agent: Mozilla/5.0...

服務器收到options請求之後,檢查了Origin、Access-Control-Request-Method和Access-Control-Request-Headers字段之後,確認容許跨源請求,就能夠作出迴應
options響應頭信息

HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 01:15:39 GMT
Server: Apache/2.0.61 (Unix)
Access-Control-Allow-Origin: http://localhost:3000 // 表示http://localhost:3000能夠訪問數據
Access-Control-Allow-Methods: GET, POST, PUT      
Access-Control-Allow-Headers: X-Custom-Header    
Content-Type: text/html; charset=utf-8
Content-Encoding: gzip
Content-Length: 0
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Content-Type: text/plain

當options請求經過以後發出正式的HTTP請求,假若options請求不經過,則服務器不容許這次訪問,從而拋出錯誤

options請求經過以後的,瀏覽器發出發請求

PUT /cors HTTP/1.1
Origin: http://api.zhenai.com
Host: api.alice.com
X-Custom-Header: value
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...

options請求緩存

那這樣的話,若是頁面存在大量的複雜請求,豈不是每一個請求前面都要進行一次options的請求,那不會形成大量資源的浪費麼?
若是基於cors請求的方法來解決跨域問題,那麼複雜請求以前是須要進行一個options的請求的,但咱們能夠經過對options請求進行緩存來減輕請求的壓力。

在options請求中,咱們能夠經過設置響應頭的參數Access-Control-Max-Age來對結果進行緩存
好比: Access-Control-Max-Age: 600 表示對options檢驗結果進行十分鐘的緩存

  1. url變化會致使緩存失效,須要從新驗證options請求的返回值
  2. 預檢不關心post data
  3. header變化,若是是去掉了自定義的header使得請求變成簡單請求,不會發送options請求。若是是增長其餘的header,是會從新驗證Access-Control-Allow-Headers的值。
  4. cookie變化,只要後端容許發送cookie,cookie值變化不會致使緩存失效。

該字段的兼容性以下:
image.png

nginx

nginx解決跨域的問題跟以前的方法有所不一樣,它是經過服務器的方向代理,將前端訪問域名跟後端服務域名映射到同源的地址下,從而實現前端服務和後端服務的同源,那天然不存在跨域的問題了。
舉個🌰:
前端服務:http://localhost:3000
前端頁面路由:http://localhost:3000/page.html
後端服務:http://localhost:3001
後端接口路由:http://localhost:3001/api/test.do
能夠看出,兩個服務處於跨域的狀態
經過nginx的配置進行反向代理,便可實現先後端服務同源,以下:

server
{
    listen 80;
    server_name localhost;

    location = / {
        proxy_pass http://localhost:3000;
    }

   location /api {
        proxy_pass http://localhost:3001;

        #指定容許跨域的方法,*表明全部
        add_header Access-Control-Allow-Methods *;

        #預檢命令的緩存,若是不緩存每次會發送兩次請求
        add_header Access-Control-Max-Age 3600;
        #帶cookie請求須要加上這個字段,並設置爲true
        add_header Access-Control-Allow-Credentials true;

        #表示容許這個域跨域調用(客戶端發送請求的域名和端口) 
        #$http_origin動態獲取請求客戶端請求的域   不用*的緣由是帶cookie的請求不支持*號
        add_header Access-Control-Allow-Origin $http_origin;

        #表示請求頭的字段 動態獲取
        add_header Access-Control-Allow-Headers 
        $http_access_control_request_headers;

        #OPTIONS預檢命令,預檢命令經過時才發送請求
        #檢查請求的類型是否是預檢命令
        if ($request_method = OPTIONS){
            return 200;
        }
   }
}

其實nginx不只僅只是用於解決跨域問題,而是涉及到不少服務器資源分配的處理,在此就不詳細探討了。

vue proxyTable

其實,在咱們主流使用的MVVM框架中,配置項裏面也提供解決跨域問題的能力,繼續舉個🌰,以vue2.x爲例,咱們能夠經過在config/index.js中添加配置項實現跨域請求:

proxyTable: {
    '/apis': {
        // 測試環境
        target: 'http://www.zhenai.com/',  // 接口域名
        changeOrigin: true,  //是否跨域
        pathRewrite: {
            '^/apis': ''   //須要rewrite重寫的,
        } 
    }             
}

原理

其實原理很簡單,就是在咱們使用npm run dev命中,啓動了一個node服務,而後將前端發出的請求發送到node服務,再將該服務轉發到本來的後臺服務,在這過程當中實現了一層代理,由一個node服務發送一個請求到另一個後臺服務,天然也沒有了瀏覽器所限制的跨域問題。

參考文獻

https://blog.csdn.net/yingwang9/article/details/90716623
https://www.jianshu.com/p/d89c62572acd
https://segmentfault.com/a/1190000019227927?utm_source=tag-newest

相關文章
相關標籤/搜索