跨域
- 跨域是针对浏览器环境,脱离浏览器环境则不存在该问题
同源跨域
同源
- 协议,域名(地址),端口,三者都相同就称之为同源
- 同源策略是一种安全协议,指一段脚本只能读取来自同一来源的窗口和文档的属性
- 不同源:协议,域名(地址),端口,三者有一个不一样就称之为不同源
跨域
- 跨域:不同源的网站之间互相发送请求,称之为跨域访问
- 浏览器默认是跨域访问的,虽然被限制了,但是在开发中不可避免会出现跨域访问,为了解决跨域访问
- 在服务器后台设置允许跨域访问:header(‘Access-Control-Allow-Origin: *’),表示允许任何客户端环境都可以访问该服务器
- 在服务器后台设置允许跨域访问:header(‘Access-Control-Allow-Origin: http://xx.xx.xx:xxxx’),设置白名单,只有白名单中的客户端环境可以访问改服务器
- 此方法只有html5才支持,为了兼容低版本浏览器,就使用另外一种解决方案jsonp
跨域解决方案
Jsonp
-
json是一种数据格式,而jsonp是用来解决跨域获取数据的一种解决方案
-
jsonp原理:
-
jsonp就是利用了script标签的src属性支持跨域获取资源
-
script标签后面中src属性写上需要请求页面的网址,并且发送一个方法的方法名到服务器,如:
<!-- 请求地址为:http://localhost:5880/dev/api/system/router --> <!-- 方法名为:callback --> <scipt src="http://localhost:5880/dev/api/system/router?method=callback"></script>"></script>
// 也可通过js动态创建script标签进行请求 const jsonp = (url, name) => { const script = document.createElement('script'); script.src = `${url}?method=${name}`; document.body.appendChild(script); // script加载完毕后移除标签 script.onload = () => { script.remove(); }; }; // 调用 jsonp('http://localhost:5880/dev/api/system/router', 'callback');
-
服务器接收到方法名之后,拼接一个方法的调用,在方法的参数中传入需要给浏览器的数据,响应格式为:
callback(json数据)
// 严重影响服务器的正常响应格式 const script = `callback(${json})` res.header('content-type', 'application/javascirpt').send(script)
-
返回给浏览器,浏览器把数据当做js进行解析,解决跨域问题
// 调用js中的callback方法 const callback = (data) => { console.log(data) // 获得响应结果 }
-
-
缺点:
- 只支持get请求方式
- 严重影响服务器的正常响应格式
CORS
CORS
:基于http1.1
的一种跨域解决方案,跨域资源共享- 实现原理:如果浏览器要跨域访问服务器的资源,需要获得服务器的允许
- 一个请求可以附带很多信息,从而会对服务器造成不同程度的影响,针对不同的请求,CORS规定了三种不同的交互模式
- 简单请求
- 需要预检的请求
- 附带身份凭证的请求
简单请求
简单请求的判定
-
当请求同时满足以下条件时,浏览器会认为它是一个简单请求:
- 请求方法属于下面的一种:
get
post
head
- 请求头仅包含安全的字段,常见的安全字段如下:
Accept
Accept-Language
Content-Language
Content-Type
DPR
Downlink
Save-Data
Viewport-Width
Width
- 请求头包含
Content-Type
,仅限下面的值之一:text/plain
multipart/form-data
application/x-www-form-urlencoded
- 请求方法属于下面的一种:
-
如以下示例:
// 简单请求 fetch("http://crossdomain.com/api/news"); // 请求方法不满足要求,不是简单请求 fetch("http://crossdomain.com/api/news", { method:"PUT" }) // 加入了额外的请求头,不是简单请求 fetch("http://crossdomain.com/api/news", { headers:{ a: 1 } }) // 简单请求 fetch("http://crossdomain.com/api/news", { method: "post" }) // content-type不满足要求,不是简单请求 fetch("http://crossdomain.com/api/news", { method: "post", headers: { "content-type": "application/json" } })
简单请求的交互规范
- 当浏览器判定某个ajax跨域请求是简单请求时,会发生以下的事情
- 请求头中会自动添加
Origin
字段,origin
:正进行跨域请求的源地址 - 服务器响应头中应包含
Access-Control-Allow-Origin
,Access-Control-Allow-Origin
的值为*
:表示我很开放,什么人我都允许访问- 具体的源:比如
http://my.com
,表示只允许该源地址访问
- 请求头中会自动添加
需要预检的请求
- 简单的请求对服务器的威胁不大,若浏览器不认为这是一种简单请求,就会按照下面的流程进行:
- 浏览器发送预检请求,询问服务器是否允许
- 服务器允许
- 浏览器发送真实请求
- 服务器完成真实的响应
预检请求的交互规范
- 浏览器发送预检请求,询问服务器是否允许
- 预检请求没有请求体,为以下特征:
- 请求方法为
OPTIONS
- 没有请求体
- 请求头中包含
Origin
:请求的源,和简单请求的含义一致Access-Control-Request-Method
:后续的真实请求将使用的请求方法Access-Control-Request-Headers
:后续的真实请求设置的请求头
- 请求方法为
- 预检请求没有请求体,为以下特征:
- 服务器允许
- 服务器收到预检请求后,可以检查预检请求中包含的信息,若允许该的请求,需要响应以下消息格式
- 不需要响应任何消息体
- 响应头中包含
Access-Control-Allow-Origin
:和简单请求一样,表示允许的源Access-Control-Allow-Methods
:表示允许的后续真实的请求方法Access-Control-Allow-Headers
:表示允许改动的请求头Access-Control-Max-Age
:告诉浏览器,多少秒内,对于同样的请求源、方法、头,都不需要再发送预检请求
- 服务器收到预检请求后,可以检查预检请求中包含的信息,若允许该的请求,需要响应以下消息格式
- 浏览器发送真实请求
- 预检被服务器允许后,浏览器就会发送真实请求
- 服务器响应真实请求
附带身份凭证的请求
- 默认情况下,ajax的跨域请求并不会附带cookie,某些需要权限的操作就无法进行
- 解决:通过简单的配置就可以实现附带cookie
- 响应头添加:
Access-Control-Allow-Credentials: true
- 响应头添加:
- 注意:对于附带身份凭证的请求,服务器不得设置
Access-Control-Allow-Origin
的值为*
补充
-
在跨域访问时,JS只能拿到一些最基本的响应头,如:Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma,如果要访问其他头,则需要服务器设置响应头:
Access-Control-Expose-Headers
,如下:Access-Control-Expose-Headers: authorization, a, b
CORS中间件使用
- 安装:
npm install cors
- 文档地址:[GitHub - expressjs/cors: Node.js CORS middleware](https://github.com/rs/cors)