前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >浏览器同源策略与如何解决跨域问题总结

浏览器同源策略与如何解决跨域问题总结

作者头像
henu_Newxc03
发布2022-05-05 18:20:33
1.7K0
发布2022-05-05 18:20:33
举报

🧐 什么是同源策略

跨域问题实际就是浏览器的同源策略造成的。

同源策略限制了从同一个源加载的文档或脚本如何与另一个源的资源进行交互。这是浏览器的一个用于隔离潜在恶意文件的重要安全机制。同源指的是: 协议端口号域名必须一致。

下表给出了与 URL http://store.company.com/dir/page.html 的源进⾏对⽐的示例:

在这里插入图片描述
在这里插入图片描述

同源策略:protocol(协议)、domain(域名)、port(端口)三者必须一致

同源策略主要限制了三个方面:

  • 当前域下的 js 脚本不能够访问其他域下的 cookie、localStorage 和 indexDB
  • 当前域下的 js 脚本不能够操作访问操作其他域下的 DOM。
  • 当前域下ajax无法发送跨域请求

同源政策的⽬的主要是为了保证⽤户的信息安全,它只是对 js 脚本的⼀种限制,并不是对浏览器的限制,对于⼀般的img、或者script脚本请求都不会有跨域的限制,这是因为这些操作都不会通过响应结果来进⾏可能出现安全问题的操作。

😎 如何解决跨域问题

(1) CORS

下⾯是MDN对于CORS的定义:

跨域资源共享(CORS) 是⼀种机制,它使⽤额外的 HTTP 头来告诉浏览器 让运⾏在⼀个 origin(domain)上的Web应⽤被准许访问来⾃不同源服务器上的指定的资源。当⼀个资源从与该资源本身所在的服务器不同的域、协议或端⼝请求⼀个资源时,资源会发起⼀个跨域HTTP 请求。

CORS需要浏览器服务器同时⽀持,整个CORS过程都是浏览器完成的,⽆需⽤户参与。因此实现CORS的关键就是服务器,只要服务器实现了CORS请求,就可以跨源通信了。

浏览器将CORS分为简单请求⾮简单请求:

简单请求不会触发CORS预检请求。若该请求满⾜以下两个条件,就可以看作是简单请求:

1)请求方法是以下三种方法之一:

  • HEAD
  • GET
  • POST

2)HTTP的头信息不超出以下⼏种字段:

  • Accept
  • Accept-Language
  • Content-Language
  • Last-Event-ID
  • Content-Type:只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain 若不满足以上条件,就属于非简单请求了
简单请求过程:

对于简单请求,浏览器会直接发出CORS请求,它会在请求的头信息中增加⼀个Orign字段,该字段⽤来说明本次请求来⾃哪个源(协议+端⼝+域名),服务器会根据这个值来决定是否同意这次请求。如果Orign指定的域名在许可范围之内,服务器返回的响应就会多出以下信息头:

代码语言:javascript
复制
Access-Control-Allow-Origin: http://api.bob.com // 和Orign⼀直
Access-Control-Allow-Credentials: true // 表示是否允许发送Cookie
Access-Control-Expose-Headers: FooBar // 指定返回其他字段的值
Content-Type: text/html; charset=utf-8 // 表示⽂档类型

如果Orign指定的域名不在许可范围之内,服务器会返回⼀个正常的HTTP回应,浏览器发现没有上⾯的Access-Control-Allow-Origin头部信息,就知道出错了。这个错误⽆法通过状态码识别,因为返回的状态码可能是200。

在简单请求中,在服务器内,⾄少需要设置字段: Access-Control-Allow-Origin

非简单请求过程:

⾮简单请求是对服务器有特殊要求的请求,⽐如请求⽅法为DELETE或者PUT等。⾮简单请求的CORS请求会在正式通信之前进⾏⼀次HTTP查询请求,称为预检请求

浏览器会询问服务器,当前所在的⽹⻚是否在服务器允许访问的范围内,以及可以使⽤哪些HTTP请求⽅式和头信息字段,只有得到肯定的回复,才会进⾏正式的HTTP请求,否则就会报错。

预检请求使⽤的请求⽅法是OPTIONS,表示这个请求是来询问的。他的头信息中的关键字段是Orign,表示请求来⾃哪个源。除此之外,头信息中还包括两个字段:

  • Access-Control-Request-Method:该字段是必须的,⽤来列出浏览器的CORS请求会⽤到哪些HTTP⽅法。
  • Access-Control-Request-Headers: 该字段是⼀个逗号分隔的字符串,指定浏览器CORS请求会额外发送的头信息字段。

服务器在收到浏览器的预检请求之后,会根据头信息的三个字段来进⾏判断,如果返回的头信息在中有Access-Control-Allow-Origin这个字段就是允许跨域请求,如果没有,就是不同意这个预检请求,就会报错。

服务器回应的CORS的字段如下:

代码语言:javascript
复制
Access-Control-Allow-Origin: http://api.bob.com // 允许跨域的源地址
Access-Control-Allow-Methods: GET, POST, PUT // 服务器⽀持的所有跨域请求的⽅法
Access-Control-Allow-Headers: X-Custom-Header // 服务器⽀持的所有头信息字段
Access-Control-Allow-Credentials: true // 表示是否允许发送Cookie
Access-Control-Max-Age: 1728000 // ⽤来指定本次预检请求的有效期,单位为秒

只要服务器通过了预检请求,在以后每次的CORS请求都会⾃带⼀个Origin头信息字段。服务器的回应,也都会有⼀个Access-Control-Allow-Origin头信息字段。

在⾮简单请求中,⾄少需要设置以下字段:

代码语言:javascript
复制
'Access-Control-Allow-Origin'
'Access-Control-Allow-Methods'
'Access-Control-Allow-Headers'

减少OPTIONS请求次数: OPTIONS请求次数过多就会损耗⻚⾯加载的性能,降低⽤户体验度。所以尽量要减少OPTIONS请求次数,可以后端在请求的返回头部添加:Access-Control-Max-Age:number。它表示预检请求的返回结果可以被缓存多久,单位是秒。该字段只对完全⼀样的URL的缓存设置⽣效,所以设置了缓存时间,在这个时间范围内,再次发送请求就不需要进⾏预检请求了

CORS中Cookie相关问题: 在CORS请求中,如果想要传递Cookie,就要满⾜以下三个条件:

  • 在请求中设置 withCredentials

默认情况下在跨域请求,浏览器是不带 cookie 的。但是我们可以通过设置 withCredentials 来进⾏传递 cookie

代码语言:javascript
复制
// 原⽣ xml 的设置⽅式
var xhr = new XMLHttpRequest();
xhr.withCredentials = true;
// axios 设置⽅式
axios.defaults.withCredentials = true;
  • Access-Control-Allow-Credentials 设置为 true
  • Access-Control-Allow-Origin 设置为false

(2) JSONP

jsonp的原理就是利⽤<script>标签没有跨域限制,通过<script>标签src属性,发送带有callback参数的GET请求,服务端将接⼝返回数据拼凑到callback函数中,返回给浏览器,浏览器解析执⾏,从⽽前端拿到callback函数返回的数据。 1)原生JS实现

代码语言:javascript
复制
  <script>
    let script = document.createElement("script");
    script.type = "text/javascript";
    //传参一个回调函数名给后端,方便后端返回时执行这个在前端定义的回调函数
    script.src =
      "https://www.domain2.com:8080/login?user=admin&callback=handleCallback";
    document.head.appendChild(script);
    //回调执行函数
    function handleCallback(res) {
      alert(JSON.stringify(res));
    }
  </script>

服务端返回如下(返回时即执行全局函数):

代码语言:javascript
复制
handleCallback({"success":true,"user":"admin"})

2)Vue axios实现

代码语言:javascript
复制
    this.$http=axios;
    this.$http.jsonp('http://www.domain2.com:8080/login'{
      params:{},
      jsonp:'handleCallback'
    }).then((res)=>{
      console.log(res);
    })

后端nodejs代码

代码语言:javascript
复制
var querystring = require('querystring');
var http = require('http');
var server = http.createServer();
server.on('request', function(req, res) {
var params = querystring.parse(req.url.split('?')[1]);
var fn = params.callback;
// jsonp返回设置
res.writeHead(200, { 'Content-Type': 'text/javascript' });
res.write(fn + '(' + JSON.stringify(params) + ')');
res.end();
});
server.listen('8080');
console.log('Server is running at port 8080...');

(3) postMessage跨域

postMessage是HTML5 XMLHttpRequest Level 2中的API,且是为数不多可以跨域操作的window属性之⼀,它可⽤于解决以下⽅⾯的问题:

  • ⻚⾯和其打开的新窗⼝的数据传递
  • 多窗⼝之间消息传递
  • ⻚⾯与嵌套的iframe消息传递
  • 上⾯三个场景的跨域数据传递

⽤法:postMessage(data,origin)⽅法接受两个参数:

  • data: html5规范⽀持任意基本类型或可复制的对象,但部分浏览器只⽀持字符串,所以传参时最好⽤JSON.stringify()序列化。
  • origin: 协议+主机+端⼝号,也可以设置为"*“,表示可以传递给任意窗⼝,如果要指定和当前窗⼝同源的话设置为”/"。

1) a.html:(domain1.com/a.html)

代码语言:javascript
复制
<script>
<iframe id="iframe" src="http://www.domain2.com/b.html"
style="display:none;"></iframe>
<script>
var iframe = document.getElementById('iframe');
iframe.onload = function() {
var data = {
name: 'aym'
};
// 向domain2传送跨域数据
iframe.contentWindow.postMessage(JSON.stringify(data),
'http://www.domain2.com');
};
// 接受domain2返回数据
window.addEventListener('message', function(e) {
alert('data from domain2 ---> ' + e.data);
}, false);
</script>

2) b.html:(domain2.com/b.html)

代码语言:javascript
复制
<script>
// 接收domain1的数据
window.addEventListener('message', function(e) {
alert('data from domain1 ---> ' + e.data);
var data = JSON.parse(e.data);
if (data) {
data.number = 16;
// 处理后再发回domain1
window.parent.postMessage(JSON.stringify(data),
'http://www.domain1.com');
}
}, false);
</script>

(4) nginx代理跨域

nginx代理跨域,实质和CORS跨域原理⼀样,通过配置⽂件设置请求响应头Access-Control-AllowOrigin…等字段

1)nginx配置解决iconfont跨域

浏览器跨域访问js、css、img等常规静态资源被同源策略许可,但iconfont字体⽂件(eot|otf|ttf|woff|svg)例外,此时可在nginx的静态资源服务器中加⼊以下配置。

代码语言:javascript
复制
location / {
add_header Access-Control-Allow-Origin *;
}

2)nginx反向代理接⼝跨域

跨域问题:同源策略仅是针对浏览器的安全策略。服务器端调⽤HTTP接⼝只是使⽤HTTP协议,不需要同源策略,也就不存在跨域问题。 实现思路:通过Nginx配置⼀个代理服务器域名与domain1相同,端⼝不同)做跳板机,反向代理访问domain2接⼝,并且可以顺便修改cookie中domain信息,⽅便当前域cookie写⼊,实现跨域访问。

代码语言:javascript
复制
#proxy服务器
server {
	listen 81;
	server_name www.domain1.com;
	location / {
	proxy_pass http://www.domain2.com:8080; #反向代理
	proxy_cookie_domain www.domain2.com www.domain1.com; #修改cookie⾥域名
	index index.html index.htm;
	# 当⽤webpack-dev-server等中间件代理接⼝访问nignx时,此时⽆浏览器参与,故没有同源限制,下⾯的跨域配置可不启⽤
	add_header Access-Control-Allow-Origin http://www.domain1.com; 
	#当前端只跨域不带cookie时,可为*
	add_header Access-Control-Allow-Credentials true;
	}
}

(5) nodejs 中间件代理跨域

node中间件实现跨域代理,原理⼤致与nginx相同,都是通过启⼀个代理服务器,实现数据的转发,也可以通过设置cookieDomainRewrite参数修改响应头中cookie中域名,实现当前域的cookie写⼊,⽅便接⼝登录认证。

1)⾮vue框架的跨域 使⽤node + express + http-proxy-middleware搭建⼀个proxy服务器。

  • 前端代码
代码语言:javascript
复制
var xhr = new XMLHttpRequest();
// 前端开关:浏览器是否读写cookie
xhr.withCredentials = true;
// 访问http-proxy-middleware代理服务器
xhr.open('get', 'http://www.domain1.com:3000/login?user=admin', true);
xhr.send();
  • 中间件服务器代码
代码语言:javascript
复制
var express = require('express');
var proxy = require('http-proxy-middleware');
var app = express();
app.use('/', proxy({
// 代理跨域⽬标接⼝
target: 'http://www.domain2.com:8080',
changeOrigin: true,
// 修改响应头信息,实现跨域并允许带cookie
onProxyRes: function(proxyRes, req, res) {
res.header('Access-Control-Allow-Origin', 'http://www.domain1.com');
res.header('Access-Control-Allow-Credentials', 'true');
},
// 修改响应信息中的cookie域名
cookieDomainRewrite: 'www.domain1.com' // 可以为false,表示不修改
}));
app.listen(3000);
console.log('Proxy server is listen at port 3000...');

2)vue框架的跨域 node + vue + webpack + webpack-dev-server搭建的项⽬,跨域请求接⼝,直接修改webpack.config.js配置。开发环境下,vue渲染服务和接⼝代理服务都是webpack-dev-server同⼀个,所以⻚⾯与代理接⼝之间不再跨域。

webpack.config.js部分配置

代码语言:javascript
复制
module.exports = {
	entry: {},
	module: {},
	...
	devServer: {
		historyApiFallback: true,
		proxy: [{
		context: '/login',
		target: 'http://www.domain2.com:8080', // 代理跨域⽬标接⼝
		changeOrigin: true,
		secure: false, // 当代理某些https服务报错时⽤
		cookieDomainRewrite: 'www.domain1.com' // 可以为false,表示不修改
		}],
		noInfo: true
	}
}
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2022-05-01,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 🧐 什么是同源策略
  • 😎 如何解决跨域问题
    • (1) CORS
      • (2) JSONP
        • (3) postMessage跨域
          • (4) nginx代理跨域
            • (5) nodejs 中间件代理跨域
            相关产品与服务
            消息队列 TDMQ
            消息队列 TDMQ (Tencent Distributed Message Queue)是腾讯基于 Apache Pulsar 自研的一个云原生消息中间件系列,其中包含兼容Pulsar、RabbitMQ、RocketMQ 等协议的消息队列子产品,得益于其底层计算与存储分离的架构,TDMQ 具备良好的弹性伸缩以及故障恢复能力。
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档