同源政策 、跨域及解决方法——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('服务器启动成功');
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
### 回答1: 使用CORS解决跨域问题的具体方法包括:首先,在服务器端设置 Access-Control-Allow-Origin 允许跨域请求;其次,在客户端使用 XMLHttpRequest 对象发起跨域请求;最后,在服务器端返回 Access-Control-Allow-Origin 响应头,以允许跨域访问。 ### 回答2: 跨域问题是由于浏览器的同源策略引起的,为了解决这个问题,可以使用CORS(Cross-Origin Resource Sharing)来进行跨域资源共享。CORS是一种机制,它允许网页服务器进行跨域访问控制,从而使跨域数据传输成为可能。 下面是使用CORS解决跨域问题的具体方法: 1. 在服务端设置Access-Control-Allow-Origin响应头来指定允许的源,允许的值可以是具体的域名、通配符(如*)或null。如果需要指定多个源,可以使用逗号分隔。 2. 如果请求需要携带身份凭证(如cookies、HTTP认证等),则还需要设置Access-Control-Allow-Credentials响应头为true。 3. 如果跨域请求包含自定义的请求头字段,浏览器会首先发送一个预检请求(OPTIONS)来获取服务器的CORS配置。服务器收到预检请求后,需要设置Access-Control-Allow-Methods响应头来指定允许的请求方法,以及Access-Control-Allow-Headers响应头来指定允许的请求头字段。 4. 可以通过设置Access-Control-Expose-Headers响应头来指定允许客户端访问的响应头字段。 5. 在客户端的XMLHttpRequest请求中需要设置withCredentials属性为true,以便允许携带身份凭证。 使用CORS可以灵活地控制跨域资源的访问权限,提高了安全性,并且不需要使用代理或JSONP等其他方法来实现跨域请求。但是需要注意的是,CORS只能解决部分跨域问题,不支持跨域文件上传和跨域流媒体传输等。 ### 回答3: 跨域请求是指在浏览器端,当网页的请求向不同域名、不同端口号或不同协议的服务器发起时,会被浏览器拦截,这是为了保护用户的安全。为了解决跨域问题,我们可以使用CORS(Cross-Origin Resource Sharing)。 CORS是一种通过在服务器端设置响应头来允许跨域请求的机制。具体方法如下: 1. 在服务端设置响应头:在服务器端的API接口中设置`Access-Control-Allow-Origin`响应头,允许指定的域名来访问该接口。例如,设置为`Access-Control-Allow-Origin: http://example.com`,表示只允许`http://example.com`域名来访问该接口。 2. 处理预请求:对于带有特殊请求方法(例如DELETE、PUT等)或自定义头部的请求,浏览器会首先发起一个OPTIONS预请求,来确认服务器是否接受该请求。在服务器端的API接口中,需要对OPTIONS请求进行特殊处理,设置相关的响应头,例如`Access-Control-Allow-Methods`(允许的方法)和`Access-Control-Allow-Headers`(允许的头部)。 3. 处理跨域携带凭证请求:对于需要在跨域请求中携带用户凭证(如cookie、authorization等)的情况,需要在服务器端的API接口中设置`Access-Control-Allow-Credentials: true`,表示允许跨域携带凭证。 4. 处理非简单请求:当请求方法为GET、POST、HEAD,并且没有自定义头部时,浏览器会发起简单请求,对于这种请求,服务器不需要额外设置响应头。 综上所述,使用CORS解决跨域问题的具体方法包括在服务器端设置`Access-Control-Allow-Origin`响应头,处理OPTIONS预请求,设置`Access-Control-Allow-Methods`和`Access-Control-Allow-Headers`来处理特殊请求,以及设置`Access-Control-Allow-Credentials`来处理跨域携带凭证请求。通过这些设置,可以实现跨域请求的安全和可控访问。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值