01 前言
跨域是几乎我们在请求数据是都会遇到问题,那究竟什么是跨域,又如何解决呢?话不多说我们直接开始。
02 同源策略
同源策略 是一个安全策略。所谓的同源,指的是协议
,域名
,端口
相同。
同源策略限制了一下行为:
- Cookie、LocalStorage、SessionStorage 和 IndexDB 无法读取
- DOM 和 JS 对象无法获取
- Ajax 请求发送不出去
03 什么是跨域
跨域,是指浏览器不能执行其他网站的脚本。它是由浏览器的同源策略造成的,是浏览器对JavaScript实施的安全限制。
04 解决方法
JSONP跨域
JSONP跨域是利用script标签src没有跨域限制的漏洞,将前端方法作为参数传递到服务器端,由服务器端注入参数后返回,实现服务器端向客户端通信。
「实现」
- 创建script标签
- 设置script标签的src属性,以问号传递参数,设置好回调函数callback名称
- 插入html文本中
- 调用回调函数,res参数就是获取的数据
let script = document.createElement('script');
script.src = 'http://www.xxx.com/login?username=xxx&callback=callback';
document.body.appendChild(script);
function callback(res) {
console.log(res);
}
jquery方法
$.ajax({
url: 'http://www.baidu.cn/login',
type: 'GET',
dataType: 'jsonp', //请求方式为jsonp
jsonpCallback: 'callback',
data: {
"username": "xxx"
}
})
「JSONP的优点」
- 它不像XMLHttpRequest对象实现的Ajax请求那样受到同源策略的限制
- 它的兼容性更好,在更加古老的浏览器中都可以运行,不需要XMLHttpRequest或ActiveX的支持
- 它在请求完毕后可以通过调用callback的方式回传结果。
「JSONP的缺点」
- 它只支持GET请求,具有局限性,不安全,可能会受到XSS攻击
- 它只能解决跨域HTTP请求这种情况,不能解决不同域的两个页面之间如何进行JavaScript调用的问题
跨域资源共享CORS
1.什么是CORS
CORS(Cross-Origin Resource Sharing)跨域资源共享,是一个W3C的标准。CORS背后的基本思想是使用自定义的HTTP头部让浏览器与服务器进行沟通,从而决定请求或响应是应该成功还是失败。
CORS需要浏览器和服务器同时支持。目前,虽然所有浏览器都支持该功能,但IE8/9需要使用XDomainRequest
对象来支持CORS。
整个CORS通信过程,都是浏览器自动完成,不需要用户参与。对于前端开发者来说,CORS通信与同源的AJAX通信没有差别,代码完全一样。浏览器一旦发现AJAX请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉。
因此,实现CORS通信的关键是服务器,服务端设置 Access-Control-Allow-Origin
就可以开启 CORS。 该属性表示哪些域名可以访问资源,如果设置通配符(*)
则表示所有网站都可以访问资源。
2.简单请求与非简单请求
CORS通行的这种方式分为两种请求:一种是 简单请求,另一种是 非简单请求,浏览器对这两种请求方式的处理方式是不同的。
所谓的简单请求,就是满足一下条件的请求,反之则为非简单请求。
条件1:请求方式为以下三者之一
- GET
- HEAD
- POST
条件2:Content-Type 的值仅限于下列三者之一
- text/plain
- multipart/form-data
- application/x-www-form-urlencoded
「简单请求」
对于简单请求,浏览器直接发出CORS请求。具体来说,就是浏览器发现这次跨源AJAX请求是简单请求,就自动在头信息之中,添加一个Origin字段。
Origin: http://localhost:5678
Origin字段用来说明,本次请求来自哪个源(协议 + 域名 + 端口)。服务器根据这个值,决定是否同意这次请求。
如果Origin指定的源不在许可范围内,服务器会返回一个正常的HTTP响应,但响应头信息没有包含Access-Control-Allow-Origin字段,从而抛出一个错误被XMLHttpRequest的onerror回调函数捕获。
注意,这种错误无法通过状态码识别,因为HTTP回应的状态码有可能是200。
如果Origin指定的源在许可范围内,服务器返回的响应,会多出几个头信息字段:
Access-Control-Allow-Origin: http://api.bob.com
Access-Control-Allow-Credentials: true
Access-Control-Expose-Headers: FooBar
Content-Type: text/html; charset=utf-8
Access-Control-Allow-Origin: 必须。它的值要么是请求时Origin字段的值,要么是一个表示接受任意域名的请求。
Access-Control-Allow-Credentials: 可选。它是一个布尔值且只能为true
,表示允许浏览器发送Cookie。默认情况下,Cookie不包括在CORS请求之中。如果服务器不允许浏览器发送Cookie,删除该字段即可。
Access-Control-Expose-Headers: 可选。CORS请求时,XMLHttpRequest对象的getResponseHeader()方法只能拿到6个基本字段:Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma。如果想拿到其他字段,就必须在Access-Control-Expose-Headers里面指定。
需要注意的是,如果需要发送Cookie,Access-Control-Allow-Origin就不能设为星号,必须指定明确的、与请求网页一致的域名。同时,Cookie依然遵循同源政策,只有用服务器域名设置的Cookie才会上传,其他域名的Cookie并不会上传,且跨源原网页代码中的document.cookie也无法读取服务器域名下的Cookie。
「非简单请求」
非简单请求一般对服务器有特殊要求的请求,比如请求方法是PUT或DELETE,Content-Type字段的类型是application/json等。
非简单请求的CORS请求,会在正式通信之前,增加一次HTTP查询请求,称为 "预检"请求(preflight)。 该请求的方法是OPTIONS
方法,浏览器先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些HTTP动词和头信息字段。只有得到肯定答复,浏览器才会发出正式的XMLHttpRequest请求,否则就报错。
WebSocket协议跨域
Websocket 是 HTML5 的一个持久化的协议,它实现了浏览器与服务器的全双工通信,同时也是跨域的一种解决方案。
WebSocket 和 HTTP 都是应用层协议,都基于 TCP 协议。但是 WebSocket 是一种 双向通信协议,在建立连接之后,WebSocket 的 server 与 client 都能主动向对方发送或接收数据。虽然 WebSocket 在建立连接时需要借助 HTTP 协议,但连接建立好了之后 client 与 server 之间的双向通信就与 HTTP 无关了。
来看下面这个例子
client向localhost:5678发送数据和接受数据👇
let socket = new WebSocket('ws://localhost:5678');
socket.onopen = ()=> {
socket.send('test');//向服务器发送数据
}
socket.onmessage = (e)=> {
console.log(e.data);//接收服务器返回的数据
}
socket.onclose = (e)=> {
//当客户端收到服务端发送的关闭连接请求时,触发onclose事件
console.log("close");
}
socket.onerror = (e)=> {
//如果出现连接、处理、接收、发送数据失败的时候触发onerror事件
console.log(error);
}
后端部分👇
// server.js
const WebSocket = require('ws');
let socketServer = new WebSocket.Server({port:5678});
socketServer.on('connection',(ws)=> {
ws.on('message', (data)=> {
console.log(data);
ws.send('server data')
});
});
想要深入了解WebSocket,可以尝试使用Socket.io
nginx代理跨域
1.nginx配置解决iconfont跨域
浏览器跨域访问js、css、img等常规静态资源被同源策略许可,但iconfont字体文件(eot/otf/ttf/woff/svg)例外,此时可在nginx的静态资源服务器中加入以下配置。
location / {
add_header Access-Control-Allow-Origin *;
}
2.nginx反向代理接口跨域
「原理」
同源策略是浏览器的安全策略,不是HTTP协议的一部分。服务器端调用HTTP接口只是使用HTTP协议,不会执行JS脚本,因此不需要同源策略,也就不存在跨越问题。
「实现」
通过nginx配置一个代理服务器(域名与domain1相同,端口不同)做跳板机,反向代理访问domain2接口,并且可以顺便修改cookie中domain信息,方便当前域cookie写入,实现跨域登录。
nginx配置👇
#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;
}
}
前端部分👇
let xhr = new XMLHttpRequest();
// set browser read/write cookie
xhr.withCredentials = true;
// open nginx proxy
xhr.open('get', 'http://www.domain1.com:81/?user=admin', true);
xhr.send();
后端部分👇
const http = require('http');
const qs = require('querystring');
let server = http.createServer();
server.on('request', (req, res)=> {
let params = qs.parse(req.url.substring(2));
// write cookie
res.writeHead(200, {
'Set-Cookie': 'l=a123456;Path=/;Domain=www.domain2.com;HttpOnly' // HttpOnly:脚本无法读取
});
res.write(JSON.stringify(params));
res.end();
});
server.listen('8080');