项目中和面试经常遇到跨域问题,老生常谈的问题,今天就来讲讲跨域:
浏览器不支持跨域?
- 用户登录A网站,在cookie里面存入了登录信息,用户再登录B网站,如果没有同源策略,B网站能够轻易拿到用户A网站信息,去伪造用户向A网站发送信息。
- DOM元素,iframe嵌入页面,如果没有同源策略,嵌入一个淘宝页面,伪造成淘宝钓鱼网站,用户输入账户密码可以通过dom操作拿到账户信息。
- ajax,LocalStorage也不支持跨域,不能随意拿取信息。
总的来说浏览器不支持跨域是为了安全,但在项目中有些情况下需要去跨域。
先讲讲什么是同源策略
- 协议
- 域名
- 端口
3个都一样为同域,其中某一个不一样那么就跨域了。
列如你的网站域名是http://www.xxx.com
- https://www.xxx.com (跨域,协议不一致)
- http://aaa.xxx.com(跨域,域名不一致)
- http://www.xxx.com:8080 (跨域,端口不一致)
- http://www.xxx.com/aaa (同域)
但是在浏览器中link标签(加载css) img(加载图片) script(加载js) 标签不受同源策略,可以随便跨域,去加载其他域下面的资源。
jsonp
由于script标签不受同源策略,可以用script去跨域,原理就是创建一个script标签,src地址去引入其他域下的js文件并且带入参数和回调函数,js文件返回一个执行函数,去执行window下的回调函数。
例如现在要向www.xxx.com去请求数据,首先我们在window下声明一个函数a,然后创建一个script标签<script src="www.xxx.com?params=xxx&cb=a"></script>
,params代表请求参数,cb指定成功后需要调用的回调函数,这个请求返回一个js文件内容a({data: 'xxx'})
,执行了最开始在window下声明的函数a并且传入我们需要的数据。
下面来看看,如果封装一个简单的jsonp函数
1 | function jsonp({ url, params }) { |
后端实现
1 | const express = require('express'); |
jsonp缺点: 只支持get请求并且不安全,如果加载第三方返回script标签,会出现恶意攻击(xss)。
cors
解决jsonp缺点 支持get post put delet请求,由服务端控制,安全性高,前端正常发送ajax请求,项目中最常用的方式。
上面说了同源策略是浏览器的行为,其实我们的请求能够到达服务器,只是浏览器给屏蔽掉了数据,cors就是利用http header头告诉浏览器一些信息,浏览器放开同源策略。
简单请求
-
- 必须是以下三种方法
- get
- post
- head(什么是head请求? -> 只返回响应头,不会返回响应内容,http1.0定义的方法,前端用的很少)
- 请求头只能包含以下字段
- Accept
- Accept-Language
- Content-Language
- Last-Event-ID
- Content-Type:application/x-www-form-urlencoded multipart/form-data text/plain
-
如果不满足上面的简单请求,那么就是非简单请求,非简单请求的时候浏览器会首先发出一个预检请求OPTIONS到服务器,把将要发送的请求方法,请求头给服务器,如果服务器返回成功,那么浏览器才会发出正式的请求,否则报错。
服务器可以设置的header头
Access-Control-Allow-Origin: www.baidu.com
: 允许百度这个域下的请求,可以配置多个域,以逗号隔开,还能设置为*(*代表所有域都能请求但是origin写*不能允许携带cookie凭证)Access-Control-Allow-Methods: POST,GET,PUT,DELETE
: 允许哪些方法,以逗号隔开Access-Control-Allow-Headers: name,token
: 表示服务器支持name和token字段Access-Control-Allow-Credentials: true
: 允许cookie跨域Access-Control-Max-Age: 6
: 预检请求的有效期,单位为秒,相当于把预检请求缓存下来,下次直接发送正式请求,不用再去预检是否服务器支持该请求方法Access-Control-Expose-Headers: token
: 允许用js获取响应头里的token值Access-Control-Request-Headers: token
: 指定浏览发送请求时,需要带上的额外请求头
用express简单实现一个cors跨域服务端
1 | const express = require('express'); |
postMessage
除了与服务器通信需要跨域,有时候会存在iframe加载跨域,window.open()一个标签页跨域通信,html5为了解决这个问题新增了postMessage方法。
postMessage(需要传递的参数, 目标源)
iframe:
1 | localhost:3000 下 a.html |
注意: 一定要在onload结束之后再发送postMessage,否则子页面接收不到消息
window.open()
1 | localhost:3000 下 a.html |
window.open()如果是跨域,则无法监听onload事件
document.domain
该方法存在一定的限制条件,必须是2个网页的一级域名相同,用法也很简单,可以使2个页面共享cookie。
例如: a.xxx.com
和b.xxx.com
:
同时设置docment.domain = 'xxx.com'
服务器设置cookie时也设置到xxx.com
这样的话,在这个一级域名下的所有二三级域名,都可以互通cookie
window.name
利用window.name改变网页地址,该值不变的特点,可以做到跨域。
a网站iframe加载b网站,b网站把需要传输的数据放入window.name中,然后重定向到a网站下同域的网址,这时a网站可以顺利的拿到window.name属性。
1 | localhost: 3000 下的a.html |
location.hash
hash值得变化不会导致页面刷新,通过a页面改变b页面hash值,b页面不能直接通过parent去修改a页面的hash值,需要通过加载一个a域下的代理iframe修改,从而实现跨域通信。
1 | localhost: 3000 下的a.html |
other
以上就是项目中最常用到的跨域方式,还有一种与服务器通信方式websocket不受浏览器同源策略。平常在开发中经常用到的webpack-dev-server,nginx,http-server…利用的是代理去请求。
先是本地起了一个代理服务器,前端发送http到代理服务器,由代理服务器请求后端的接口,跨域是浏览器的行为,2个服务器之间是没有同源策略的,所有代理服务器拿到数据后,再返回给前端。代理服务器和前端同源。