1. 程式人生 > >web 專案解決跨域問題終極解決方案

web 專案解決跨域問題終極解決方案


一.跨域問題的由來
二.怎麼就算跨域(同源的定義)
三.常見跨域解決方法
四.總結

一.跨域問題的由來
為什麼會產生這樣一個問題,擺在我們面前呢??
理解跨域,首先必須要了解同源策略。同源策略是瀏覽器上為安全性考慮實施的非常重要的安全策略。
為了防止某些文件或指令碼載入別的域下的未知內容造成洩露隱私,破壞系統等安全行為,

哦~說的很有道理,但腦子中沒有什麼體會認知吧?那咱們換個角度來說這個問題
假設沒有同源, 網際網路世界是什麼樣?
1.連結跳轉導致的問題.
http://a.com , 放一個連結到 icbc.com, 然後 window.open來開啟,window.open有個返回值控制代碼,
沒有同源,a.com可以擁有對這個頁面完全的控制權. 攔截表單,捕獲資料,將賬號密碼上傳到a.com等等.

2.ajax請求, 要啥就有啥.
你登入jd.com產生了登陸cookie;
然後開啟a.com, a.com通過ajax 請求http://jd.com 的使用者資訊介面,
這時候因為你登陸jd.com,所以a.com發起訪問jd.com自動帶上了jd的合法cookie,繞過jd的登陸驗證,
然後獲取到你京東的訂單list ,暱稱, 所有私密資訊返還給a.com.【是不是有點像CSRF?】

所以,為什麼需要同源策略,顯而易見,必須得限制跨域
(安全性和方便性是成反比的,同源策略提升了Web前端的安全性,但犧牲了Web拓展上的靈活性。所以,現代瀏覽器在安全性和可用性之間選擇了一個平衡點。在遵循同源策略的基礎上,選擇性地為同源策略“開放了後門”。例如img script style等標籤,都允許垮域引用資源,嚴格說這都是不符合同源要求的。)

二.怎麼就算跨域(同源的定義)
1995年, Netscape 公司在瀏覽器中引入同源策略/SOP(Same origin policy)
同domain(或ip),同埠,同協議視為同一個域,一個域內的指令碼僅僅具有本域內的許可權,
可以理解為本域指令碼只能讀寫本域內的資源,而無法訪問其它域的資源。這種安全限制稱為同源策略。

三.常見跨域解決方法
1.JSONP
在js中,我們直接用XMLHttpRequest請求不同域上的資料時,是不可以的。但是,在頁面上引入不同域上的js指令碼檔案卻是可以的,jsonp正是利用這個特性來實現的。
啥意思?正如前邊提到的,ajax訪問介面時受同源限制的,但是<script src="XXXX">是不受限制的,所以通過此方法避開同源限制。哈哈,本質是因為這樣啊,鑽了同源的漏網之魚。【通過script的src來載入,這也解釋了為什麼jsonp只支援get傳輸】
為啥還要有callback引數?因為需要一個代理函式做中間人來處理資料,這個引數成了約定的函式名了。
這樣jsonp的原理就很清楚了,通過script標籤引入一個js檔案,這個js檔案載入成功後會執行我們在url引數中指定的函式,並且會把我們需要的json資料作為引數傳入。所以jsonp是需要伺服器端的頁面進行相應的配合的。
如果你的頁面使用jquery,那麼通過它封裝的方法就能很方便的來進行jsonp操作了.原理是一樣的,只不過我們不需要手動的插入script標籤以及定義回掉函式。jquery會自動生成一個全域性函式來替換callback=?中的問號,之後獲取到資料後又會自動銷燬,實際上就是起一個臨時代理函式的作用。$.getJSON方法會自動判斷是否跨域,不跨域的話,就呼叫普通的ajax方法;跨域的話,則會以非同步載入js檔案的形式來呼叫jsonp的回撥函式。

2.CORS
CORS(跨域資源共享,Cross-Origin Resource Sharing)是通過客戶端+服務端協作宣告的方式來確保請求安全的。
服務端會在HTTP請求頭中增加一系列HTTP請求引數(例如Access-Control-Allow-Origin等),來限制哪些域的請求和哪些請求型別可以接受。
可以在程式碼裡寫,也可以寫在伺服器配置檔案裡。

先解釋下有什麼配置,不用全寫,按需選取配置即可
Access-Control-Allow-Origin:指定授權訪問的域
Access-Control-Allow-Methods:授權請求的方法(GET,POST,PUT,DELETE,OPTIONS等)
Access-Control-Allow-Credentials 首部欄位用於預檢請求的響應,表明伺服器是否允許,credentials標誌設定為true的請求。
Access-Control-Max-Age:<delta-seconds> 首部欄位指明瞭預檢請求的響應的有效時間。delta-seconds 表示該響應在多少秒內有效。
Access-Control-Allow-Headers 首部欄位用於預檢請求的響應。其指明瞭實際請求中允許攜帶的首部欄位。

實現ajax跨域訪問
配置可以在程式碼裡,
也可以寫在伺服器配置檔案(apache,nginx)裡。
1)php程式碼
header('Access-Control-Allow-Origin:*');  // 指定允許其他域名訪問  
header('Access-Control-Allow-Methods:POST');  // 響應型別  
header('Access-Control-Allow-Credentials:true');  //允許客戶端傳輸cookie
 
2) Nginx
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
add_header 'Access-Control-Allow-Credentials' "true";
 
3) Apache
Header set Access-Control-Allow-Origin www.a.com
(開啟Credentials與Methods方式暫時報錯,待嘗試)

/**
     * yii2 行為方法,自動執行開啟跨域@inheritdoc
     */
    public function behaviors()
    {
 
        return [
            
            'corsFilter' => [
                'class' => \yii\filters\Cors::className(),
                'cors' => [
                    'Origin' => ['http://a.com','http://b.com'],//多域名設定
                    'Access-Control-Allow-Credentials' => true,
                ]
            ],
        ];
    }
<!-- 前臺跨域程式碼格式 -->
 $.ajax({
     type: 'POST',
     url: 'http://www.b.com' ,
     data: {id:1} ,
     dataType: 'json',
     xhrFields: {withCredentials: 'true'},
     success:function(){alert(121);}
});

效果演示 :以修改apache,nginx配置檔案的方法為例子

圖1.前臺跨域程式碼


圖2.觸發跨域後的提示


圖3.apache配置開啟跨域


圖4.nginx開啟跨域限制

圖5.再次呼叫跨域成功

c.iframe
本質www.a調用不了www.b下的東西,但是a可以iframe開啟一個b域名下的第三個頁面c,c與b是同域名讓c來呼叫b
所以ifame的本質就是,把a要呼叫b的程式碼邏輯寫到c裡邊,然後把c召喚出來即可,剩下的c來做也不涉及跨域.
這裡文章介紹的很好:http://blog.csdn.net/fdipzone/article/details/17619673/


4、其它一些方式
WebSocket、伺服器代理、flash socket。這裡接不詳細敘述說了。

四.總結:
這麼一看,跨域限制還是很有必要的,安全。
但是為了方便,同源限制也開放了幾個可以跨域訪問的標籤<img><script><style>等
但是就是因為這個小缺口的開放不也是產生CSRF的可乘之機嘛
所以說,安全與便捷總是相對力點,就像原來聽說支付寶想推出一款掃物品就能付款的產品,出門不用帶錢包了,帶上這個設定的物體就行了,方便是方便了,但是多危險嗎丟了別人就隨便拿來支付了嗎。(不過後來也沒見阿里推出該產品哈哈)

五。注意點
1.關於Access-Control-Allow-Origin設定多域名問題
總不能給伺服器設定*吧太危險了,但是又想設定多域名,不能簡單的加逗號
比如:add_header 'Access-Control-Allow-Origin' 'www.a.com,www.b.com'; 是錯誤
因該怎麼做呢?加入邏輯程式碼,把它變數化。
$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); }
if ($http_origin ~ <允許的域(正則匹配)>) { add_header 'Access-Control-Allow-Origin'"$http_origin";
if ($request_method = "OPTIONS") {...}
}
2.關於Credentials設定問題
我們從上文知道,Credentials是開啟傳輸cookie的一個開關
但是注意:
1).首先:伺服器Access-Control-Allow-Origin【不能設定成*】 得設定成對應域名,否則無效!!!
2).其次,只在服務端設定開啟Access-Control-Allow-Credentials伺服器開啟不行,
還得客戶端開啟withCredentials=true,否則,即使伺服器同意傳送Cookie,瀏覽器也不會發送。或者,伺服器要求設定Cookie,瀏覽器也不會處理。

自己實踐:由於top.$存在跨域問題,我們需容器中建立一個過濾器,大概意思就是在我們的對所跨域進行放行即可。主要程式碼如下:

HttpServletResponse res = (HttpServletResponse)response;
        res.setContentType("text/html;charset=UTF-8");
//        res.setHeader("Access-Control-Allow-Origin", "http://168.168.102.14:8081");
//        res.setHeader("Access-Control-Allow-Origin", "http://192.168.101.81:7001");
        String corsPath = new String(PlanFramework.getSystemConfig().getProperty("corsPath").getBytes("ISO-8859-1"), "GBK");
        res.setHeader("Access-Control-Allow-Origin", corsPath);
        // res.setHeader("Access-Control-Allow-Origin", "*");
        res.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
        res.setHeader("Access-Control-Max-Age", "0");
        res.setHeader("Access-Control-Allow-Headers", "Origin, No-Cache, X-Requested-With, If-Modified-Since, Pragma, Last-Modified, Cache-Control, Expires, Content-Type, X-E4M-With,userId,token");
        res.setHeader("Access-Control-Allow-Credentials", "true");
        res.setHeader("XDomainRequesteAllowed", "1");

//        Cookie cookie = getCookieByName((HttpServletRequest) request, "platpublish");
//        String tokenKey = null;
//        if(cookie != null)
//            tokenKey = cookie.getValue();
//
//        String userAccount = UserLoginLog.getUserAccountByTokenKey(tokenKey);
//        UserDelegate uDelegate = new UserDelegate();
//        UserInfo userInfo = null;
//        try {
//            userInfo = uDelegate.getUserInfoByAccount(userAccount);
//        } catch (GeneralFailureException e) {
//            e.printStackTrace();
//        }
//
//        if(userInfo != null)
//            ((HttpServletRequest) request).getSession().setAttribute("userInfo", userInfo);

//            UserInfo tm = schema.newMessage();
//            ProtostuffIOUtil.mergeFrom(tokenKey.getBytes(), tm, schema);

//        }


//        ((HttpServletRequest) request).getSession().setAttribute("a", tokenKey);
//        ((HttpServletRequest) request).getSession().setAttribute("userInfo", new UserInfo());
//            String tokenKey = "cd1e67a7-544b-4b9c-bfa9-498eb8708fd9";
//            String tokenKey = cookie.getValue();
//            URLDecoder.decode(tokenKey, "utf-8");
            // redis伺服器連線
//            Jedis jedis = new Jedis("168.168.100.12", 6379);
//        Jedis jedis = new Jedis("114.67.140.171", 6379);
//        Jedis jedis = new Jedis("168.168.46.55", 6379);

            // redis密碼驗證
//            jedis.auth("admin");

//            byte[] bytes = jedis.get("userInfo".getBytes());
//            byte[] bytes = jedis.get(tokenKey.getBytes());

//        System.out.println(bytes.length);

//            if(bytes != null){
//                UserInfo tm = schema.newMessage();
//                ProtostuffIOUtil.mergeFrom(bytes, tm, schema);
//                ((HttpServletRequest) request).getSession().setAttribute("userInfo", tm);
//            System.out.println(tm.getAddress());
//            }

//            jedis.close();
        chain.doFilter(request, res);
    }

3.cors預檢請求問題
若http請求不是get,post,head,或者攜帶header頭過多,瀏覽器會先預檢請求(option(經測試確實存在兩次請求)
瀏覽器先詢問伺服器,當前網頁所在的域名是否在伺服器的許可名單之中,以及可以使用哪些HTTP動詞和頭資訊欄位。只有得到肯定答覆,瀏覽器才會發出正式的XMLHttpRequest請求,否則就報錯。


參考文章:
https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Access_control_CORS
iframe與主框架跨域相互訪問方法:http://blog.csdn.net/fdipzone/article/details/17619673/
js中幾種實用的跨域方法原理詳解:http://www.cnblogs.com/2050/p/3191744.html
前端跨域請求如何實現:https://ask.zkbhj.com/?/question/96