跨域 與 跨域的解決方案

跨域的產生源於瀏覽器的同源策略。
同源策略:
	瀏覽器最核心也最基本的安全功能,Web是構建在同源策略基礎之上的,瀏覽器只是針對同源策略的一種實現
	同源策略會阻止一個域的javascript腳本和另外一個域的內容進行交互
	瀏覽器採用同源策略,其中一個重要原因就是對cookie的保護
	所謂同源(即同域)就是兩個頁面具有相同的協議(protocol),主機(host)和端口號(port)
	注意:
		協議方式,域名,端口號必須相同才能進行數據交互
		一級域名相同,二級域名不同或者子域名不同也不能進行數據交互
非同源限制:
	1. 無法讀取非同源網頁的 Cookie、LocalStorage 和 IndexedDB
	2. 無法接觸非同源網頁的 DOM
	3. 無法向非同源地址發送 AJAX 請求

跨域的解決方案:

1. 讀取非同源網頁的 Cookie問題
	瀏覽器是通過document.domain屬性來檢查兩個頁面是否同源
	通過設置相同的document.domain,兩個頁面就可以共享Cookie(這兩個域名必須屬於同一個基礎域名!而且所用的協議,端口都要一致)
	實例:
		// 兩個頁面都設置相同的domain值
		document.domain = 'test.com'
2. 跨窗口通信
	使用window.postMessage()實現多個窗口之間的通信
	適用場景:
		1. 頁面與其打開窗口之前的通信
		2. 多窗口之間的消息傳遞
		3. 頁面與其中嵌套iframe之間的通信
	父窗口:
		// 父窗口打開一個子窗口
		var openWindow = window.open('http://test2.com', 'title');
		// 父窗口向子窗口發消息(第一個參數代表發送的內容,第二個參數代表接收消息窗口的url)
		openWindow.postMessage('Nice to meet you!', 'http://test2.com');
	子窗口:
		// 子窗口監聽 message 消息
		window.addEventListener('message', function (e) {
			console.log(e.source); // e.source 發送消息的窗口
			console.log(e.origin); // e.origin 消息發向的網址
			console.log(e.data);   // e.data   發送的消息
		},false);
3. JSONP
	JSONP常用於服務器與客戶端跨源通信
	簡單適用,兼容性好,只支持get請求,不支持post請求
		
	核心思想:
		通過添加一個<script>元素,向服務器請求 JSON 數據
		服務器收到請求後,將數據放在一個指定名字的回調函數的參數位置傳回來
		
	
	原生實現:
		// 向服務器test.com發出請求,該請求的查詢字符串有一個callback參數,用來指定回調函數的名字
		<script src="http://test.com/data.php?callback=dosomething"></script>
		// 處理服務器返回回調函數的數據
		<script type="text/javascript">
			function dosomething(res){
				// 處理獲得的數據
				console.log(res.data)
			}
		</script>
		

	jquery實現:
		$.ajax({
			url: 'http://www.test.com:8080/login',
			type: 'get',
			dataType: 'jsonp',  // 請求方式爲jsonp
			jsonpCallback: "handleCallback",    // 自定義回調函數名
			data: {}
		});


	vue實現:
		this.$http.jsonp('http://www.domain2.com:8080/login', {
			params: {},
			jsonp: 'handleCallback'
		}).then((res) => {
			console.log(res); 
		})
4. 跨域資源分享
	跨域資源分享 CORS,是 W3C 標準,屬於跨源 AJAX 請求的根本解決方法。
	普通跨域請求:
		只需服務器端設置Access-Control-Allow-Origin

	帶cookie跨域請求:
		前後端都需要進行設置
		

	前端設置:
		根據xhr.withCredentials字段判斷是否帶有cookie

	    原生實現:	
		var xhr = new XMLHttpRequest(); // IE8/9需用window.XDomainRequest兼容
		// 前端設置是否帶cookie
		xhr.withCredentials = true;
		xhr.open('post', 'http://www.domain2.com:8080/login', true);
		xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
		xhr.send('user=admin');
		xhr.onreadystatechange = function() {
			if (xhr.readyState == 4 && xhr.status == 200) {
				alert(xhr.responseText);
			}
		};
			
	    jquery實現:
		$.ajax({
			url: 'http://www.test.com:8080/login',
			type: 'get',
			data: {},
			xhrFields: {
				withCredentials: true    // 前端設置是否帶cookie
			},
			crossDomain: true,   // 會讓請求頭中包含跨域的額外信息,但不會含cookie
		});
	 
	    vue-resource:
		Vue.http.options.credentials = true
				
	    vue-axios:
		axios.defaults.withCredentials = true


	服務端設置:

            java: 
		/*
		 * 導入包:import javax.servlet.http.HttpServletResponse;
		 * 接口參數中定義:HttpServletResponse response
		 */
				 
		// 允許跨域訪問的域名:若有端口需寫全(協議+域名+端口),若沒有端口末尾不用加'/'
		response.setHeader("Access-Control-Allow-Origin", "http://www.domain1.com"); 
				 
		// 允許前端帶認證cookie:啓用此項後,上面的域名不能爲'*',必須指定具體的域名,否則瀏覽器會提示
		response.setHeader("Access-Control-Allow-Credentials", "true"); 
				 
		// 提示OPTIONS預檢時,後端需要設置的兩個常用自定義頭
		response.setHeader("Access-Control-Allow-Headers", "Content-Type,X-Requested-With");

	    nodeJS:
		var http = require('http');
		var server = http.createServer();
		var qs = require('querystring');
				 
		server.on('request', function(req, res) {
			var postData = '';
				 
			// 數據塊接收中
			req.addListener('data', function(chunk) {
				postData += chunk;
			});
				 
			// 數據接收完畢
			req.addListener('end', function() {
				postData = qs.parse(postData);
				 
				// 跨域後臺設置
				res.writeHead(200, {
					'Access-Control-Allow-Credentials': 'true',     // 後端允許發送Cookie
					'Access-Control-Allow-Origin': 'http://www.domain1.com',    // 允許訪問的域(協議+域名+端口)
				/* 
				 * 此處設置的cookie還是domain2的而非domain1,因爲後端也不能跨域寫cookie(nginx反向代理可以實現),
				 * 但只要domain2中寫入一次cookie認證,後面的跨域接口都能從domain2中獲取cookie,從而實現所有的接口都能跨域訪問
				 */
				'Set-Cookie': 'l=a123456;Path=/;Domain=www.domain2.com;HttpOnly'  // HttpOnly的作用是讓js無法讀取cookie
			});
				 
			res.write(JSON.stringify(postData));
			res.end();
		    });
		});
 
		server.listen('8080');
		console.log('Server is running at port 8080...');

	    php:
		<?phpheader("Access-Control-Allow-Origin:*");

		Apache:
			使用mod_headers模塊來激活HTTP頭的設置,默認是激活的。
			需要在Apache配置文件的<Directory>, <Location>, <Files>或<VirtualHost>的配置里加入以下內容
				Header set Access-Control-Allow-Origin *
5. 設立服務器代理
	前後端分離的項目雖然可以採用相同的服務協議以及ip地址,但端口是不同的
	可以通過nginx作爲與用戶之間的服務器代理
	再由nginx根據具體需求去與web服務或者接口服務去交互
6. WebSocket協議跨域
	WebSocket protocol是HTML5一種新的協議。
	它實現了瀏覽器與服務器全雙工通信,同時允許跨域通訊,是server push技術的一種很好的實現。
	前端:
		<div>user input:<input type="text"></div>
		<script src="./socket.io.js"></script>
		<script>
		        var socket = io('http://www.demo2.com:8080');

			// 連接成功處理
			socket.on('connect', function() {
				// 監聽服務端消息
				socket.on('message', function(msg) {
					console.log('data from server: ---> ' + msg); 
				});

				// 監聽服務端關閉
				socket.on('disconnect', function() { 
					console.log('Server socket has closed.'); 
				});
			});

			document.getElementsByTagName('input')[0].onblur = function() {
				socket.send(this.value);
			};
		</script>

	後臺服務:
		// 啓http服務
		var server = http.createServer(function(req, res) {
			res.writeHead(200, {
				'Content-type': 'text/html'
			});
			res.end();
		});

		server.listen('8080');
		console.log('Server is running at port 8080...');

		// 監聽socket連接
		socket.listen(server).on('connection', function(client) {
			// 接收信息
			client.on('message', function(msg) {
				client.send('hello:' + msg);
				console.log('data from client: ---> ' + msg);
			});

			// 斷開處理
			client.on('disconnect', function() {
				console.log('Client socket has closed.'); 
			});
		});

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章