同源是指"协议+域名+端口"三者都相同,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,浏览器很容易受到XSS、CSFR等攻击。
http://www.a.com:3000/index.html
这个网址,协议是http
,域名是www.a.com
,端口是3000
(我们经常看的网址没有,是因为默认端口80可以省略)
同源策略限制一下几种行为:
跨域:从一个网页去请求另一个网页的资源,只要协议、域名、端口其中一个不同,就被当作是跨域
浏览器将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的原理是利用标签没有跨域限制,通过
标签的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)
优点:
缺点:
实现原理:同源策略是浏览器需要遵循的标准,而服务器向服务器发送请求则无需遵循同源策略,使用一个代理服务器,设置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':''}
})
);
}
实现原理和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 protocol是html5的一种新的协议。,实现了浏览器与服务器全双工通信,同时允许跨域通讯,是server push技术的一种很好的实现。原生websocket api使用起来不太方便,我们使用socket.io,它很好地封装了websocket接口,提供了更简单、灵活的接口,也对不支持websocket的浏览器提供了向下兼容