# HTTP跨域
前后端数据交互经常会碰到请求跨域,什么是跨域,以及有哪几种跨域方式,这是本文要探讨的内容。
跨域并不是请求发不出去,请求能发出去,服务端能收到请求并正常返回结果,只是结果被浏览器拦截了。你可能会疑问明明通过表单的方式可以发起跨域请求,为什么 Ajax 就不会?因为归根结底,跨域是为了阻止用户读取到另一个域名下的内容,Ajax 可以获取响应,浏览器认为这不安全,所以拦截了响应。但是表单并不会获取新的内容,所以可以发起跨域请求。同时也说明了跨域并不能完全阻止 CSRF,因为请求毕竟是发出去了。
# 什么是跨域?
由于浏览器同源策略,当前端通过ajax向后端发起请求时,当协议,域名,端口中有一个不同就会发生跨域。
协议 | 子域名 | 主域名 | 端口 | 资源地址 |
---|---|---|---|---|
http:// | www | baidu.com | :80 | index.html |
# 同源策略
- Cookie、LocalStorage、IndexedDB 等存储性内容
- DOM 节点
- AJAX 请求发送后,结果被浏览器拦截了
# 解决方案
# 方案一 Jsonp
利用 script 标签没有跨域限制的漏洞,网页可以得到从其他来源动态产生的 JSON 数据。JSONP 请求一定需要对方的服务器做支持才可以。
前端动态创建script标签作为前端请求,后端返回的数据用 callback的name包裹进行返回
前端代码示例
DETAILS
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<button id="send">send</button>
<script>
function show(data) {
console.log('data :>> ', data);
}
$('#send').click(function() {
$.ajax({
url: "http://192.168.34.168:3000/jsonp",
dataType: "jsonp",
type: "get",//可以省略
jsonpCallback: "show",//->自定义传递给服务器的函数名,而不是使用jQuery自动生成的,可省略
jsonp: "callback",//->把传递函数名的那个形参callback,可省略
success: function (data) {
console.log('data :>> ', data);
}
});
})
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
后端代码示例
DETAILS
// 后端服务
const Koa = require('koa');
const app = new Koa();
const Router =require('@koa/router')
const router = new Router();
router.get('/jsonp', async function(ctx, next) {
ctx.body = `show({name:1,age:2,test:34243})`
})
app.use(router.routes())
app.listen(3000,'0.0.0.0');
2
3
4
5
6
7
8
9
10
- 优点:JSONP 优点是简单兼容性好,可用于解决主流浏览器的跨域数据访问的问题。
- 缺点:仅支持 get 方法具有局限性,不安全可能会遭受 XSS 攻击。
# 方案二 CORS 跨域资源共享
CORS 需要浏览器和后端同时支持。IE 8 和 9 需要通过 XDomainRequest 来实现。
浏览器会自动进行 CORS 通信,实现 CORS 通信的关键是后端。只要后端实现了 CORS,就实现了跨域。
简单请求在跨域的时候只需要服务端设置 Access-Control-Allow-Origin: *就可以开启 CORS。 该属性表示哪些域名可以访问资源,如果设置通配符则表示所有网站都可以访问资源。
虽然设置 CORS 和前端没什么关系,但是通过这种方式解决跨域问题的话,会在发送请求时出现两种情况,分别为简单请求和复杂请求。
# 简单请求
只要同时满足以下两大条件,就属于简单请求
- 使用下列方法之一:
- GET
- HEAD
- POST
- Content-Type 的值仅限于下列三者之一:
- text/plain
- multipart/form-data
- application/x-www-form-urlencoded
# 复杂请求
复杂请求的 CORS 请求,会在正式通信之前,增加一次 HTTP 查询请求,称为"预检"请求,该请求是 option 方法的,通过该请求来知道服务端是否允许跨域请求。
put delete 等复杂请求在跨域的时候需要同时设置下面这2个
- Access-Control-Allow-Origin
- Access-Control-Allow-Methods
# 方案三 postMessage
postMessage 是 HTML5 XMLHttpRequest Level 2 中的 API,postMessage()方法允许来自不同源的脚本采用异步方式进行有限的通信,可以实现跨文本档、多窗口、跨域消息传递。
它可用于解决以下方面的问题:
- 页面和其打开的新窗口的数据传递
- 多窗口之间消息传递
- 页面与嵌套的 iframe 消息传递
# 方案四 websocket
Websocket 是 HTML5 的一个持久化的协议,它实现了浏览器与服务器的全双工通信,同时也是跨域的一种解决方案。WebSocket 和 HTTP 都是应用层协议,都基于 TCP 协议。但是 WebSocket 是一种双向通信协议,在建立连接之后,WebSocket 的 server 与 client 都能主动向对方发送或接收数据。同时,WebSocket 在建立连接时需要借助 HTTP 协议,连接建立好了之后 client 与 server 之间的双向通信就与 HTTP 无关了。
# 方案五 Node 中间件代理(两次跨域)
同源策略是浏览器需要遵循的标准,而如果是服务器向服务器请求就无需遵循同源策略
前端向同源服务器发送请求,代理服务向目标服务进行请求转发,再次将相应结果返回目标前端。
- 代理服务接受客户端请求 。
- 代理服务将请求 转发给目标服务器。
- 代理服务拿到目标服务器 响应 数据。
- 代理服务将 响应 转发给客户端。
# 方案六 nginx 反向代理
实现原理类似于 Node 中间件代理,需要你搭建一个中转 nginx 服务器,用于转发请求。
使用 nginx 反向代理实现跨域,是最简单的跨域方式。只需要修改 nginx 的配置即可解决跨域问题,支持所有浏览器,支持 session,不需要修改任何代码,并且不会影响服务器性能。
实现思路:通过 nginx 配置一个代理服务器(域名与 domain1 相同,端口不同)做跳板机,反向代理访问 domain2 接口,并且可以顺便修改 cookie 中 domain 信息,方便当前域 cookie 写入,实现跨域登录。