同源政策 、跨域及解決方法——jsonp,CORS

同源政策

Ajax請求限制

Ajax 只能向自己的服務器發送請求。比如現在有一個A網站、有一個B網站,A網站中的 HTML 文件只能向A網站服務器中發送 Ajax請求,B網站中的 HTML 文件只能向 B 網站中發送 Ajax 請求,但是 A 網站是不能向 B 網站發送 Ajax請求的,同理,B網站也不能向 A 網站發送 Ajax請求。

什麼是同源

如果兩個頁面擁有相同的協議、域名和端口,那麼這兩個頁面就屬於同一個源,其中只要有一個不相同,就是不同源。

http://www.example.com/dir/page.html
http://www.example.com/dir2/other.html:同源
http://example.com/dir/other.html:不同源(域名不同)
http://v2.www.example.com/dir/other.html:不同源(域名不同)
http://www.example.com:81/dir/other.html:不同源(端口不同)
https://www.example.com/dir/page.html:不同源(協議不同)

同源政策的目的

  • 同源政策是爲了保證用戶信息的安全,防止惡意的網站竊取數據。最初的同源政策是指 A 網站在客戶端設置的Cookie,B網站是不能訪問的。

  • 隨着互聯網的發展,同源政策也越來越嚴格,在不同源的情況下,其中一項規定就是無法向非同源地址發送Ajax 請求,如果請求,瀏覽器就會錯。

使用 JSONP 解決同源限制問題

jsonp 是 json with padding 的縮寫,它不屬於 Ajax 請求,但它可以模擬 Ajax 請求,是JSON的一種“使用模式”,可用於解決主流瀏覽器的跨域數據訪問的問題。

  1. 將不同源的服務器端請求地址寫在 script 標籤的 src 屬性中。
	<script src="http://localhost:3001/test"></script>
  1. 服務器端響應數據必須是一個函數的調用,真正要發送給客戶端的數據需要作爲函數調用的參數。
	app.get('/test', (req, res) => {
		const result = 'fn({name: "張三",age:20})';
		//fn是調用客戶端的函數名
		res.send(result);
	});
  1. 在客戶端全局作用域下定義函數 fn。
	function fn (data) { }
  1. 在 fn 函數內部對服務器端返回的數據進行處理。
	function fn (data) { console.log(data); }
	//輸出一個對象 服務端返回的{name: "張三", age: "20"}

JSONP 代碼優化

  1. 客戶端需要將函數名稱傳遞到服務器端。

  2. 將 script 請求的發送變成動態請求。

    客戶端代碼

		// 爲按鈕添加點擊事件
		 btn.onclick = function () { 
	 	// 創建script標籤 
	 	var script = document.createElement('script'); 
	 	// 設置src屬性  把調用的函數名發過去
	 	script.src = 'http://localhost:3001/better?callback=fn2'; 
	 	// 將script標籤追加到頁面中
	 	document.body.appendChild(script); 
	 	// 爲script標籤添加onload事件 
	 	//當請求傳送過去後就觸發
	 	script.onload = function () { 
	 	// 將body中的script標籤刪除掉 
	 	document.body.removeChild(script); 
	 	} 
	 }
  1. 封裝 jsonp 函數,方便請求發送。

    客戶端代碼

	function jsonp (options){ 
		// 動態創建 script 標籤 
		var script = document.createElement('script') 
		// 爲script標籤 添加src屬性 
		script.src = options.url; 
		// 將script 標籤追加到頁面上 
		document.body.appendChild(script); 
		// 爲script 標籤添加onload事件 
		script.onload = function () {
		 document.body.removeChild(script) 
		 } 
	 }

服務器端代碼優化之 res.jsonp 方法

	app.get('/better', (req, res) => {
		 // 接收客戶端傳遞過來的函數的名稱 
		 //const fnName = req.query.callback; 
		 // 將函數名稱對應的函數調用代碼返回給客戶端 
		 //const data = JSON.stringify({name: "張三"}); 
		 //const result = fnName + '('+ data +')'; 
		 // setTimeout(() => { // res.send(result); 
		// }, 1000) 
		res.jsonp({name: 'lisi', age: 20}); 
		//express框架裏面的方法  客戶端success函數接受數據
	});

完整代碼

客戶端

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>jsonp應用</title>
</head>
<body>
	<button id="btn">點我發送請求</button>
	<script type="text/javascript">
		// 獲取按鈕
		var btn = document.getElementById('btn');
		// 爲按鈕添加點擊事件
		btn.onclick = function () {
			jp({
				// 請求地址
				url: 'http://localhost:3001/better',
				data: {
					name: 'lisi',
					age: 30
				},
				success: function (data) {
					console.log(123)
					console.log(data)
					//輸出服務端返回的{name: 'lisi', age: 20}
				}
			})
		}
		
		//jsonp的封裝函數
		function jp (options) {
			// 動態創建script標籤
			var script = document.createElement('script');
			// 拼接字符串的變量
			var params = '';

			for (var attr in options.data) {
				params += '&' + attr + '=' + options.data[attr];
			}
			
			// myJsonp0124741 生成一個一個隨機的函數名
			var fnName = 'myJsonp' + Math.random().toString().replace('.', '');
			// 它已經不是一個全局函數了
			// 將它變成全局函數
			window[fnName] = options.success;
			// 爲script標籤添加src屬性 添加函數名 傳遞參數
			script.src = options.url + '?callback=' + fnName + params;
			// 將script標籤追加到頁面中
			document.body.appendChild(script);
			// 爲script標籤添加onload事件 事件完成後刪除script標籤,避免請求發送多次產生多個標籤
			script.onload = function () {
				document.body.removeChild(script);
			}
		}
	</script>
</body>
</html>

服務端代碼

// 引入express框架
const express = require('express');
// 創建web服務器
const app = express();

app.get('/better', (req, res) => {
	res.jsonp({name: 'lisi', age: 20});
	//這裏的jsonp是服務器方法
});

案例——使用jsonp獲取騰訊天氣信息

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>使用jsonp獲取騰訊天氣信息</title>
	<link rel="stylesheet" href="/assets/bootstrap/dist/css/bootstrap.min.css">
	<style type="text/css">
		.container {
			padding-top: 60px;
		}
	</style>
</head>
<body>
	<div class="container">
		<table class="table table-striped table-hover" align="center" id="box"></table>
	</div>
	<!-- 引入封裝的jsonp文件 -->
	<script src="/js/jsonp.js"></script>
	<!-- 引入前端渲染模板 -->
	<script src="/js/template-web.js"></script>
	<!-- 創建模板 -->
	<script type="text/html" id="tpl">
		<tr>
			<th>時間</th>
			<th>溫度</th>
			<th>天氣</th>
			<th>風向</th>
			<th>風力</th>
		</tr>
		{{each info}}
		<tr>
			<!-- dataFormat 引入的用於格式化數據 -->
			<td>{{dateFormat($value.update_time)}}</td>
			<td>{{$value.degree}}</td>
			<td>{{$value.weather}}</td>
			<td>{{$value.wind_direction}}</td>
			<td>{{$value.wind_power}}</td>
		</tr>
		{{/each}}
	</script>
	<script>
		// 獲取table標籤
		var box = document.getElementById('box');

		function dateFormat(date) {
			//   substr 截取字符串 開始位置 個數  
			var year = date.substr(0, 4);
			var month = date.substr(4, 2);
			var day = date.substr(6, 2);
			var hour = date.substr(8, 2);
			var minute = date.substr(10, 2);
			var seconds = date.substr(12, 2);

			return year + '年' + month + '月' + day + '日' + hour + '時' + minute + '分' + seconds + '秒';
		}

		// 向模板中開放外部方法    方法名(任意)  定義的函數名
		template.defaults.imports.dateFormat = dateFormat;

		// 向服務器端獲取天氣信息  根據接口文檔字段進行匹配傳輸
		jsonp({
			url: 'https://wis.qq.com/weather/common',
			data: {
				source: 'pc',
				weather_type: 'forecast_1h',  //未來24小時
				// weather_type: 'forecast_1h|forecast_24h',  //未來24小時|未來7天(未來7天接口字段與未來24小時不同,可自行查看)
				province: '浙江省',
				city: '杭州市'
			},
			success: function (data) {
				console.log(data)
				// 把 數據導入 模板
				var html = template('tpl', {info: data.data.forecast_1h});
				// 把 模板放到 頁面
				box.innerHTML = html;
			}
		})
	</script>
</body>
</html>

返回數據
在這裏插入圖片描述
頁面展示
在這裏插入圖片描述

CORS 跨域資源共享

CORS:全稱爲 Cross-originresource sharing,即跨域資源共享,它允許瀏覽器向跨域服務器發送 Ajax 請求,克服了 Ajax 只能同源使用的限制。
在這裏插入圖片描述

Node 服務器端設置響應頭示例代碼:

// 攔截所有請求
app.use((req, res, next) => {
	 //設置允許跨域請求的ip地址
	 // * 代表允許所有的客戶端訪問我
	 res.header('Access‐Control‐Allow‐Origin', '*'); 
	 //設置可以跨域的請求方式
	 res.header('Access‐Control‐Allow‐Methods', 'GET, POST');
	 //執行權移交下一個中間件
	  next(); 
})

demo

html頁面 所在服務器監聽端口爲3000 訪問端口爲3001

	<!DOCTYPE html>
	<html lang="en">
	<head>
		<meta charset="UTF-8">
		<title>Document</title>
	</head>
	<body>
		<button id="btn">點我發送請求</button>
		<!-- ajax封裝文件-->
		<script src="/js/ajax.js"></script>
		<script>
			// 獲取按鈕
			var btn = document.getElementById('btn');
			// 爲按鈕添加點擊事件
			btn.onclick = function () {
				ajax({
					type: 'get',
					url: 'http://localhost:3001/cross',
					success: function (data) {
						console.log(data)
					}
				})
			};
		</script>
	</body>
	</html>

監聽的3001端口的服務器

	// 引入express框架
	const express = require('express');
	// 路徑處理模塊
	const path = require('path');
	// 創建web服務器
	const app = express();
	
	
	// 攔截所有請求
	app.use((req, res, next) => {
		// 1.允許哪些客戶端訪問我
		// * 代表允許所有的客戶端訪問我
		res.header('Access-Control-Allow-Origin', '*')
		// 2.允許客戶端使用哪些請求方法訪問我
		res.header('Access-Control-Allow-Methods', 'get,post')
		next();
	});
	
	app.get('/cross', (req, res) => {
		res.send('你得到了3001端口下返回的數據')
	});

訪問非同源數據 服務器端解決方案

在這裏插入圖片描述
demo

html頁面 在監聽3000端口的服務器下

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>Document</title>
</head>
<body>
	<button id="btn">點我發送請求</button>
	<!-- ajax封裝文件 -->
	<script src="/js/ajax.js"></script>
	<script>
		// 獲取按鈕
		var btn = document.getElementById('btn');
		// 爲按鈕添加點擊事件
		btn.onclick = function () {
			ajax({
				type: 'get',
				url: 'http://localhost:3000/server',
				success: function (data) {
					console.log(data);
				}
			})
		};
	</script>
</body>
</html>

監聽3000端口的服務器

// 引入express框架
const express = require('express');
// 路徑處理模塊
const path = require('path');
// 向其他服務器端請求數據的模塊
const request = require('request');
// 創建web服務器
const app = express();
// 靜態資源訪問服務功能
app.use(express.static(path.join(__dirname, 'public')));

app.get('/server', (req, res) => {
	request('http://localhost:3001/cross', (err, response, body) => {
		res.send(body);
	})
});
// 監聽端口
app.listen(3000);
// 控制檯提示輸出
console.log('服務器啓動成功');

監聽3001端口的服務器

	// 引入express框架
	const express = require('express');
	// 路徑處理模塊
	const path = require('path');
	// 創建web服務器
	const app = express();

	
	// 攔截所有請求
	app.use((req, res, next) => {
		// 1.允許哪些客戶端訪問我
		// * 代表允許所有的客戶端訪問我
		res.header('Access-Control-Allow-Origin', '*')
		// 2.允許客戶端使用哪些請求方法訪問我
		res.header('Access-Control-Allow-Methods', 'get,post')
		next();
	});
	
	app.get('/cross', (req, res) => {
		res.send('你得到了3001端口下返回的數據')
	});

案例——實現跨域登錄功能

cookie 複習

在這裏插入圖片描述
在這裏插入圖片描述

withCredentials屬性

  • 在使用Ajax技術發送跨域請求時,默認情況下不會在請求中攜帶cookie信息。
  • withCredentials:指定在涉及到跨域請求時,是否攜帶cookie信息,默認值爲false
  • Access-Control-Allow-Credentials:true 允許客戶端發送請求時攜帶cookie
<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>實現跨域功能</title>
	<!-- 引入bootstrap框架 -->
	<link rel="stylesheet" href="/assets/bootstrap/dist/css/bootstrap.min.css">
	<style type="text/css">
		.container {
			padding-top: 60px;
		}
	</style>
</head>
<body>
	<div class="container">
		<form id="loginForm">
			<div class="form-group">
				<label>用戶名</label>
				<input type="text" name="username" class="form-control" placeholder="請輸入用戶名">
			</div>
			<div class="form-group">
				<label>密碼</label>
				<input type="password" name="password" class="form-control" placeholder="請輸入用密碼">
			</div>
			<input type="button" class="btn btn-default" value="登錄" id="loginBtn">
			<input type="button" class="btn btn-default" value="檢測用戶登錄狀態" id="checkLogin">
		</form>
	</div>
	<script type="text/javascript">
		// 獲取登錄按鈕
		var loginBtn = document.getElementById('loginBtn');
		// 獲取檢測登錄狀態按鈕
		var checkLogin = document.getElementById('checkLogin');
		// 獲取登錄表單
		var loginForm = document.getElementById('loginForm');
		// 爲登錄按鈕添加點擊事件
		loginBtn.onclick = function () {
			// 將html表單轉換爲formData表單對象
			var formData = new FormData(loginForm);
			// 創建ajax對象
			var xhr = new XMLHttpRequest();
			// 對ajax對象進行配置
			xhr.open('post', 'http://localhost:3001/login');
			// 當發送跨域請求時,攜帶cookie信息
			xhr.withCredentials = true;
			// 發送請求並傳遞請求參數
			xhr.send(formData);
			// 監聽服務器端給予的響應內容
			xhr.onload = function () {
				console.log(xhr.responseText);
			}
		}

		// 當檢測用戶狀態按鈕被點擊時
		checkLogin.onclick = function () {
			// 創建ajax對象
			var xhr = new XMLHttpRequest();
			// 對ajax對象進行配置
			xhr.open('get', 'http://localhost:3001/checkLogin');
			// 當發送跨域請求時,攜帶cookie信息
			xhr.withCredentials = true;
			// 發送請求並傳遞請求參數
			xhr.send();
			// 監聽服務器端給予的響應內容
			xhr.onload = function () {
				console.log(xhr.responseText);
			}
		}
	</script>
</body>
</html>

跨域請求的服務器

// 引入express框架
const express = require('express');
// 路徑處理模塊
const path = require('path');
// 接收post請求參數
const formidable = require('formidable');
// 實現session功能
var session = require('express-session');
// 創建web服務器
const app = express();

// 實現session功能
app.use(session({
  secret: 'keyboard cat',
  resave: false,
  saveUninitialized: false
}));

// 靜態資源訪問服務功能
app.use(express.static(path.join(__dirname, 'public')));

// 攔截所有請求
app.use((req, res, next) => {
	// 1.允許哪些客戶端訪問我
	// * 代表允許所有的客戶端訪問我
	// 注意:如果跨域請求中涉及到cookie信息傳遞,值不可以爲*號 比如是具體的域名信息
	res.header('Access-Control-Allow-Origin', 'http://localhost:3000')
	// 2.允許客戶端使用哪些請求方法訪問我
	res.header('Access-Control-Allow-Methods', 'get,post')
	// 允許客戶端發送跨域請求時攜帶cookie信息
	res.header('Access-Control-Allow-Credentials', true);
	next();
});

app.post('/login', (req, res) => {
	// 創建表單解析對象
	var form = formidable.IncomingForm();
	// 解析表單
	req.session.isLogin = false;
	// 					普通數據  文件數據
	form.parse(req, (err, fields, file) => {
		// 接收客戶端傳遞過來的用戶名和密碼 表單的name屬性
		const { username, password } = fields;
		// 用戶名密碼比對
		if (username == 'oldsport' && password == '123456') {
			// 設置session
			req.session.isLogin = true;
			res.send({message: '登錄成功'});
		} else {
			res.send({message: '登錄失敗, 用戶名或密碼錯誤'});
		}
	})
});

app.get('/checkLogin', (req, res) => {
	// 判斷用戶是否處於登錄狀態
	if (req.session.isLogin) {
		res.send({message: '處於登錄狀態'})
	} else {
		res.send({message: '處於未登錄狀態'})
	}
});

// 監聽端口
app.listen(3001);
// 控制檯提示輸出
console.log('服務器啓動成功');
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章