【跨域】jsonp看完這篇文章就夠了

Colin_Mindset發表於2019-04-21

jsonp是一種jQuery提供的跨域解決方案,我們今天來好好講講jsonp。

同源策略及限制

所有瀏覽器都會使用同源策略這個安全策略
所謂同源,是指協議、域名、埠號都相同。
那麼,同源策略就是,一個源只能載入同源的資源。

那麼同源策略有什麼限制呢?

  • Cookie、LocalStorage和indexDB不同源無法讀取
  • dom不同源無法獲取
  • ajax不同源請求不能傳送

沒有同源的危險場景(CSRF攻擊)

1.CSRF是什麼呢?

跨站請求偽造。CSRF全名是Cross-site request forgery,是一種對網站的惡意利用,CSRF比XSS更具危險性。

2.CSRF攻擊的主要目的

是讓使用者在不知情的情況下攻擊自己已登入的一個系統(類似於釣魚)。
例如,

  • 使用者當前已經登入了A網站,同時使用者又在使用另外一個釣魚網站。
  • 這個網站上面可能因為某個圖片吸引你,你去點選一下,此時可能就會觸發一個js的點選事件,構造一個A網站的請求。
  • 利用當前cookie中的登陸狀態,讓使用者在不知情的情況下,幫你幹一些事情。
3.防禦CSRF
  • 同源檢測
    在HTTP協議中,每一個非同步請求都會攜帶兩個Header,用於標記來源域名(Origin Header和Referer Header)。這兩個Header在瀏覽器發起請求時,大多數情況會自動帶上,並且不能由前端自定義內容。 伺服器可以通過解析這兩個Header中的域名,確定請求的來源域。
  • token令牌
    CSRF 攻擊之所以能夠成功,是因為黑客可以完全偽造使用者的請求,該請求中所有的使用者驗證資訊都是存在於 cookie 中,因此黑客可以在不知道這些驗證資訊的情況下直接利用使用者自己的 cookie 來通過安全驗證。
    要抵禦 CSRF,關鍵在於在請求中放入黑客所不能偽造的資訊,並且該資訊不存在於 cookie 之中。
    那麼,我們可以要求所有的使用者請求都攜帶一個CSRF攻擊者無法獲取到的Token。伺服器通過校驗請求是否攜帶正確的Token,來把正常的請求和攻擊的請求區分開,也可以防範CSRF的攻擊。

跨域的簡單原理

我們新建一個web程式,裡面包含demo.htmldemo.js檔案如下:

//demo.html

<!DOCTYPE>
<html>
<head>
    <title>test</title>
    <script type="text/javascript" src="demo.js"></script>
</head>
<body>
</body>
</html>
//demo.js

alert("success");

開啟demo.html會彈出success對話方塊。
當然,現在這種情況是同源的,我們來模擬一下非同源情況。
我們再新建一個web程式,把demo.js放到這個程式裡。現在demo.html中訪問demo.js就變成了下面這樣:

<script type="text/javascript" src="http://localhost:xxxx/demo.js"></script>

這個時候,其實就是訪問到了非同源的demo.js

<script>標籤的src屬性並不被同源策略所約束,所以可以獲取任何伺服器上指令碼並執行。

jsonp如何處理跨域

$.ajax({
                   url:"http://api.map.baidu.com/telematics/v3/weather?ak=6tYzTvGZSYB5Oc2YGGOKt8&location=天津&output=json",
                   type:"get",
                   dataType:"jsonp",
                   success:function(data){
                       console.log(data);
                   }
               })

只需要指定dataType是jsonp格式就行了。
這只是簡單的使用,那麼jsonp內部實現跨域的原理是什麼呢?

  1. jsonp使用script標籤傳送網路請求,這個標籤是不受同源策略影響的。
  2. 在url里加入callback引數,指向的是一個全域性(如果不是全域性的,在頁面的script標籤中找不到這個函式,也就沒法執行)的function。
  3. 最後服務端返回的是一個函式的呼叫

知道了jsonp的實現原理,我們來手寫一個jsonp。

function jsonP({url, data, callbackName}) {
    return new Promise((resolve, reject) => {
        //1. 對請求進行url編碼
        function formatParams(data) {
            let arr = []
            for(let key in data) {
                if(data.hasOwnProperty(key)) {
                    arr.push(encodeURIComponent(key) + '=' + encodeURIComponent(data[key]))
                }
            }
            return arr.join('&')
        }

        //2. 建立script標籤
        const script = document.createElement('script')
        
        //3. 設定好回撥方法
        window.jsonCb = function(res) {
            document.body.removeChild(script)
            delete window.jsonCb
            resolve(res)
        }

        //4. 請求
        script.src = `${url}?${formatParams(data)}&jsonCb=${callbackName}`

        //5. 新增到dom結構中
        document.body.appendChild(script)
        
    })
}

jsonp需要服務端配合

服務端會讀取請求引數中的jsonpCallback欄位,並返回。

import java.io.IOException;  
import java.io.PrintWriter;  
import java.util.HashMap;  
import java.util.Map;  
import javax.servlet.http.HttpServletRequest;  
import javax.servlet.http.HttpServletResponse;  
import net.sf.json.JSONObject;  
import org.springframework.stereotype.Controller;  
import org.springframework.web.bind.annotation.RequestMapping;  
  
@Controller  
public class ExchangeJsonController {  
    @RequestMapping("/base/json.do")  
    public void exchangeJson(HttpServletRequest request,HttpServletResponse response) {  
       try {  
        response.setContentType("text/plain");  
        response.setHeader("Pragma", "No-cache");  
        response.setHeader("Cache-Control", "no-cache");  
        response.setDateHeader("Expires", 0);  
        Map<String,String> map = new HashMap<String,String>();   
        map.put("result", "content");  
        PrintWriter out = response.getWriter();       
        JSONObject resultJSON = JSONObject.fromObject(map); //根據需要拼裝json  
        String jsonpCallback = request.getParameter("jsonpCallback");//客戶端請求引數  
        out.println(jsonpCallback+"("+resultJSON.toString(1,1)+")");//返回jsonp格式資料  
        out.flush();  
        out.close();  
      } catch (IOException e) {  
       e.printStackTrace();  
      }  
    }  
} 

跨域通訊的幾種方式

這裡趁熱打鐵,再介紹幾種jsonp之外的跨域通訊方式。

  • hash
  • postMessage
  • webSocket
  • CORS

參考

文中jsonp實現程式碼收錄在https://github.com/colinNaive/algorithm

https://www.cnblogs.com/soyxiaobi/p/9616011.html
https://www.cnblogs.com/dream0530/p/6179819.html
https://blog.csdn.net/qq_33562825/article/details/60765688
https://segmentfault.com/a/1190000007665361
https://juejin.im/post/5c9c38e2e51d452db7007f66
https://juejin.im/post/5be4f163f265da61483b1b08
https://juejin.im/entry/589921640ce46300560ef894
https://segmentfault.com/a/1190000015597029
https://blog.csdn.net/as645788/article/details/51285688
https://www.cnblogs.com/shytong/p/5308667.html
https://www.cnblogs.com/cxying93/p/6035031.html
https://juejin.im/post/5bc009996fb9a05d0a055192#heading-8

相關文章