写在前面:
跨域是面试经常会问到的问题。
在工作中,开发环境可以用webpack-dev-server
,线上环境服务端会配置好。
为什么会出现跨域问题
浏览器同源策略导致了跨域问题
浏览器同源策略
[协议
,域名
,端口
] 这三个完全相同,就表示同域。
- 协议:http, https等
- 域名:一级域名,二级域名等
- 端口:80,8080等

跨域的几种情况,不同域之间进行资源请求就相当于跨域了。
URL | 描述 | 是否允许 |
---|---|---|
http://www.baidu.com/a.js http://www.baidu.com/a.js |
同一域名,协议,端口(80) | 允许 |
http://www.baidu.com/src/a.js http://www.baidu.com/script/a.js |
同一域名,不同文件夹 | 允许 |
http://www.baidu.com:8888/a.js http://www.baidu.com:9999/a.js |
不同端口 | 不允许 |
http://www.baidu.com/a.js http://10.42.66.88/a.js |
域名和域名对应的IP | 不允许 |
http://www.baidu.com/a.js http://script.baidu.com/a.js |
主域名相同,子域名不同 | 不允许 |
http://www.baidu.com/a.js http://baidu.com/a.js |
同一域名,不同二级域名 | 不允许(cookie也不允许) |
http://www.baidu.com/a.js http://www.google.com/a.js |
不同域名 | 不允许 |
http://www.baidu.com/a.js https://www.baidu.com/a.js |
不同协议 | 不允许 |
为什么浏览器默认不支持跨域(危险性,XSS CSRF攻击)
浏览器是从两个方面做同源策略的,一是针对接口请求
,二是针对DOM查询
。
没有同源策略限制的接口请求(引用)
cookie一般用来处理登录等场景,目的是让服务器知道是谁发出的请求。
如果请求了接口进行登录,服务端验证通过后会在响应头上加上Set-Cookie
字段,等下次再发请求的时候,浏览器会自动将cookie附加在http请求的头字段cookie中,服务器就知道这个用户已经登录过了。
引用别人发的一个场景:
1.你准备去清空你的购物车,于是打开了买买买网站www.maimaimai.com,然后登录成功,一看,购物车东西这么少,不行,还得买多点。
2.你在看有什么东西买的过程中,你的好基友发给你一个链接www.nidongde.com,一脸yin笑地跟你说:“你懂的”,你毫不犹豫打开了。
3.你饶有兴致地浏览着www.nidongde.com,谁知这个网站暗地里做了些不可描述的事情!由于没有同源策略的限制,它向www.maimaimai.com发起了请求!聪明的你一定想到上面的话“服务端验证通过后会在响应头加入Set-Cookie字段,然后下次再发请求的时候,浏览器会自动将cookie附加在HTTP请求的头字段Cookie中”,这样一来,这个不法网站就相当于登录了你的账号,可以为所欲为了!如果这不是一个买买买账号,而是你的银行账号,那……
这就是传说中的CSRF攻击[浅谈CSRF攻击方式](http://www.cnblogs.com/hyddd/archive/2009/04/09/1432744.html)。
看了这波CSRF攻击我在想,即使有了同源策略限制,但cookie是明文的,还不是一样能拿下来。于是我看了一些cookie相关的文章聊一聊 cookie、Cookie/Session的机制与安全,知道了服务端可以设置httpOnly,使得前端无法操作cookie,如果没有这样的设置,像XSS攻击就可以去获取到cookieWeb安全测试之XSS;设置secure,则保证在https的加密通信中传输以防截获。
没有同源策略限制的DOM查询(引用)
1.有一天你刚睡醒,收到一封邮件,说是你的银行账号有风险,赶紧点进www.yinghang.com改密码。你吓尿了,赶紧点进去,还是熟悉的银行登录界面,你果断输入你的账号密码,登录进去看看钱有没有少了。
2.睡眼朦胧的你没看清楚,平时访问的银行网站是www.yinhang.com,而现在访问的是www.yinghang.com,这个钓鱼网站做了什么呢?
// HTML
<iframe name="yinhang" src="www.yinhang.com"></iframe>
// JS
// 由于没有同源策略的限制,钓鱼网站可以直接拿到别的网站的Dom
const iframe = window.frames['yinhang']
const node = iframe.document.getElementById('你输入账号密码的Input')
console.log(`拿到了这个${node},我还拿不到你刚刚输入的账号密码吗`)
同源策略能规避一些危害,但并不是说有了同源策略就完全安全了,只能说同源策略是浏览器最基本的安全机制。
哪些不支持跨域
- cookie,localStorage是同一域下的,不支持跨域
- DOM元素也有同源策略的限制
- 在页面中引入别人的页面
- Ajax也不支持跨域
- 支持的话,只要知道接口链接,就会被别人引用你的接口了。不安全。
三个允许跨域的标签
- <img src="">
- <link href="">
- <script src="">
为什么要实现跨域呢
前端与后端放在不同的服务器上,就得跨域
如何实现跨域
- jsonp(json with padding)
- cors(纯后端实现跨域)
- postMessage(两个页面通信)
- document.domain(有一个一级域名和二级域名是同一域下的,子域和父域跨域)
- window.name
- location.hash
- http-proxy(反向代理,Webpack)
- nginx(配置Nginx)
- WebSocket(消息通信,实现页面通信,没有跨域限制。)
说明:
- 如果是协议和端口造成的跨域,前端是无法解决的。
- 在跨域问题上,是根据域名端口协议是否匹配来识别,而不会根据域名对应的IP地址来判断。
请求跨域到底发出去请求了没有?
事实上,即便是跨域了,浏览器请求能发送出去,服务器能接收到请求并返回正确的结果,只是这个结果被浏览器拦截了。
跨域是为了阻止读取到另一个域名下的内容,Ajax可以获取响应,但浏览器认为这不安全,所以就把响应拦截住了。
jsonp方式实现跨域
-
jsonp的原理
利用<script>
标签没有跨域限制的原理,网页可以得到从其他来源动态产生的json数据。
创建一个script标签,把文件引进来 -
jsonp和Ajax的区别
jsonp和Ajax都是客户端向服务器发送请求,从服务器获取数据。但是Ajax属于同源策略,jsonp属于非同源策略(可以跨域)。 -
jsonp的缺点
- 只支持get请求,不支持post,delete,put请求
- 不安全,容易受到xss攻击
- 现在很多网站都不采用jsonp的方式了
-
jsonp示例
index.html请求百度的数据。
<script>
function show(data){
console.log(data);
}
</script>
<script src="https://sp0.baidu.com/5a1Fazu8AA54nxGko9WTAnF6hhy/su?wd=有钱&cb=show"></script>
- 封装jsonp
function jsonp({url, params, cb}){
return new Promise((resolve, reject) => {
let script = document.createElement('script')
window[cb] = function(data){
resolve(data)
document.body.removeChild(script)
}
params = {...params, cb}
let arrs = [];
for(let key in params){
arrs.push(`${key}=${params[key]}`)
}
script.src = `${url}?${arrs.join("&")}`
document.body.appendChild(script)
})
}
jsonp({
url: "https://sp0.baidu.com/5a1Fazu8AA54nxGko9WTAnF6hhy/su",
params: {
wd: '花'
},
cb: "show"
}).then(data => {
console.log(data);
})
cors方式实现跨域
cors是目前开发中最常用的跨域解决方案
cors的安全性比较高,只要在服务器端做出修改,就可以实现跨域。
服务端设置Access-Control-Allow-Origin
可以开启CORS,这个属性表示哪些域名可以访问资源,如果写成"*",允许任何源都可以访问。
res.setHeader("Access-Control-Allow-Origin", origin); // 设置哪个源可以访问
res.setHeader("Access-Control-Allow-Headers", "name");// 设置携带哪个头访问
res.setHeader("Access-Control-Allow-Methods", "PUT");// 设置哪个方法可以访问
res.setHeader("Access-Control-Allow-Credentials", true); // 允许携带cookie
res.setHeader("Access-Control-Max-Age", 6000); // 预检的存活时间
res.setHeader("Access-Control-Expose-Headers", "name"); // 允许前端获取哪个头
网友评论