文章目录
浏览器的同源策略
同源是指"协议+域名+端口"三者都相同,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,浏览器很容易受到XSS、CSRF等攻击。
同源策略限制以下几种行为:
- cookie(同ip不同端口可共享)、localStorage、sessionStorage和indexDB无法读取
- DOM和JS对象无法获得
- AJAX请求不能发送
跨域解决方案
跨域:从一个网页去请求另一个网页的资源,只要协议、域名、端口其中一个不同,就被当作是跨域
跨域资源共享(CORS)
浏览器将CORS跨域请求分为简单请求和非简单请求,对这两种请求的处理是不一样的
只要满足两个条件就属于简单请求,否则属于非简单请求:
使用以下方法之一:
head、get、post
请求的Header是:
Accept
Accept-Language
Content-Language
Content-Type(只限于三个值:application/x-www-form-urlencoded、multipart/form-data、text/plain)
简单请求
对于简单请求,浏览器直接发出CORS请求。
在头信息中,增加一个Origin
字段,用来说明本次请求来自哪个源(协议+域名+端口),服务器根据这个值,决定是否同意这次请求。
-
如果Origin指定的源,不在许可范围内,服务器返回的responseHeader中就不会包含
Access-Control-Allow-Origin
字段,浏览器抛出错误,被XMLHttpRequest的onerror回调函数捕获 -
如果Origin指定的源在许可范围内,服务器返回响应(会多出几个字段)
Access-Control-Allow-Origin: http://api.bob.com 必须。值为请求时的Origin或*,*表示接受任意域名的请求 Access-Control-Allow-Credentials: true 可选。是否允许发送cookie Access-Control-Expose-Headers: FooBar //getREsponseHeader('FooBar')可以返回FooBar的值 可选。CORS请求时,XMLHttpRequest对象的getResponseHeader()方法只能拿到6个基本字段: Cache-Control、Expires、Last-Modified、Content-Type、Content-Language、Pragma 如果想拿到其他字段就必须在Access-Control-Expose-Headers里面指定 Content-Type: text/html; charset=utf-8
withCredentials属性:
CORS请求默认不发送cookie和http认证信息,若要带上cookie,需要服务端和客户端同时设置,否则即使服务器同意带上cookie,浏览器也不会发送,或者浏览器发送,服务器也不会处理
但是如果省略withCredentials设置,有的浏览器还是会发送cookie,这种情况下需要显式关闭withCredentials
服务端设置:Access-Control-Allow-Credentials: true
客户端打开withCredentials属性:
var xhr = new XMLHttpRequest();
xhr.withCredentials = true;
注意:如果要发送cookie,Access-Control-Allow-Origin
就不能设为*,必须指明与请求网页一致的域名,并且cookie也遵循同源策略,只有服务器域名设置的cookie才会上传,并且跨源原网页代码中的document.cookie也无法读取服务器域名下的cookie
非简单请求
对服务器有特殊要求的请求,比如请求方法是PUT、DELETE
,或Content-Type
字段的类型是application/json
预检请求:非简单请求的CORS请求,会在正式通信之前,增加一次HTTP查询请求,称为预检请求
浏览器先询问服务器,当前网页所在的域名是否在服务器许可名单之中,以及可以使用哪些HTTP动词和Headers,只有得到肯定答复,浏览器才会发出正式的XMLHttpRequest请求,否则报错
例如:
var url = 'http://api.alice.com/cors';
var xhr = new XMLHttpRequest();
xhr.open('PUT', url, true);
xhr.setRequestHeader('X-Custom-Header', 'value');
xhr.send();
浏览器发现这是一个非简单请求,于是先发送一个预检请求
预检请求的HTTP头信息:
OPTIONS /cors HTTP/1.1 - 请求方法是OPTIONS,表示这个请求是用来询问的
Origin: http://api.bob.com - 关键字段,表示请求来自哪个源
Access-Control-Request-Method: PUT - 必须。列出浏览器CORS请求会用到哪些方法
Access-Control-Request-Headers: X-Custom-Header - 指定浏览器CORS请求会额外发送的Headers
Host: api.alice.com
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...
服务器收到预检请求之后,检查了Origin
、Access-Control-Request-Method
、Access-Control-Request-Headers
之后,确认允许跨源请求就可以做出回应,responseHeader中关键是Access-Control-Allow-Origin
,若服务器否定了预检请求,返回一个正常http回应,没有任何CORS相关的头信息字段,浏览器随之报错,同普通请求
其他CORS相关字段:
Access-Control-Allow-Methods: GET, POST, PUT
必须。值为逗号分隔的字符串,表示服务器支持的所有(为避免多次预检请求)跨域请求的方法
Access-Control-Allow-Headers: X-Custom-Header
若requestHeader有Access-Control-Request-Headers,则responseHeader就有Access-Control-Allow-Headers
值为逗号分隔的字符串,表示服务器支持的所有Header字段
Access-Control-Allow-Credentials: true
同简单请求
Access-Control-Max-Age: 1728000 //s
可选。本次预检请求的有效期,在缓存期间内不能发出另一条预检请求
一旦服务器通过了"预检"请求,以后每次浏览器正常的CORS请求,就都跟简单请求一样,会有一个Origin
头信息字段。服务器的回应,也都会有一个Access-Control-Allow-Origin
头信息字段
通过JSONP跨域
-
jsonp的原理是利用
<script>
标签没有跨域限制,通过<script>
标签的src属性,发送带有callback参数的get请求,服务端将接口返回数据拼凑到callback函数中,返回给浏览器,浏览器解析执行,从而前端拿到callback函数返回的数据 -
注意:服务端返回的是函数调用,如:
handleCallback({"name": "amethyst", "color": "blueviolet"})
-
server.js
-
const express=require("express"); const app=express(); app.all('/login',(request,response)=>{ var data={ name:'amethyst', color:'blueviolet' } var str=JSON.stringify(data); var cb=request.query.callback; response.send(`${cb}(${str})`); }); app.listen(8080,()=>{ console.log('8080端口监听中...'); });
-
前台使用:
-
const script=document.createElement('script'); script.type='text/javascript'; //传参并指定回调函数名为onBack script.src='http://localhost:8080/login?callback=onBack'; document.head.appendChild(script); //执行回调函数 function onBack(res){ console.log(res); }
-
执行结果:
-
过程:
-
网页端插入一个
script
标签,src指向目标url(url后面加上query,?callback=函数名
) -
后端处理函数接收到请求,得到函数名,比如handle(handle函数定义在前端)
-
后端用handle包装数据,返回给浏览器(返回的
content-type
必须是text/javascript;charset=utf-8
) -
网页端script内容加载完成得到
handle(data)
-
浏览器发现内容是js(查看content-type),则调用js解释器执行
handle(data)
优点:
- 它不像XMLHttpRequest对象实现的Ajax请求那样受到同源策略的限制
- 它的兼容性更好,在更加古老的浏览器中都可以运行
- 不需要XMLHttpRequest或ActiveX的支持,并且在请求完毕后可以通过调用callback的方式回传结果
缺点:
- 它只支持GET请求而不支持POST等其它类型的HTTP请求
- 它只支持跨域HTTP请求这种情况,不能解决不同域的两个页面之间如何进行数据通信的问题(JavaScript调用)
- 安全性低,容易遭受XSS攻击,因为我们拿到的是对方接口的数据作为js执行,如果得到的是一个很危险的js,获取了用户信息和cookies,这时执行了js就会出现安全问题,因此使用jsonp跨域就必须保证jsonp服务是安全可信的
- 在失败的时候不会返回各种HTTP状态码
Node中间件跨域(http-proxy-middleware)
实现原理:同源策略是浏览器需要遵循的标准,而服务器向服务器发送请求则无需遵循同源策略,使用一个代理服务器,设置Access-Control-Allow-Origin
等字段便可以解决浏览器和代理服务器之间的跨域问题,而服务器之间没有限制,便实现了浏览器和服务器之间跨域通信,并且可以实现代理多个请求到不同的服务器
代理服务器所需要做的:
- 接受客户端的请求
- 将请求转发给服务器
- 拿到服务器响应数据
- 将响应转发给客户端
在开发环境下配置代理:
方法一
直接在package.json文件中配置proxy
方法二
新建一个setupProxy.js文件,在里面配置proxy,这种方法可以同时设置转发多个请求
const proxy=require('http-proxy-middleware');
module.exports=function(app){
app.use(
proxy('/api1',{
target:'http://localhost:5000',
changeOrigin:true,
pathRewrite:{'^/api1':''}
}),
proxy('/api2',{
target:'http://localhost:5001',
changeOrigin:true,
pathRewrite:{'^/api2':''}
})
);
}
nginx反向代理
实现原理和node中间件代理类似,只需要修改nginx的配置即可解决跨域问题,支持所有浏览器,支持session,可修改cookie信息
// 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;
}
}
websocket协议跨域
websocket protocol是html5的一种新的协议。,实现了浏览器与服务器全双工通信,同时允许跨域通讯,是server push技术的一种很好的实现。原生websocket api使用起来不太方便,我们使用socket.io,它很好地封装了websocket接口,提供了更简单、灵活的接口,也对不支持websocket的浏览器提供了向下兼容