深入理解跨域及跨域请求数据方式
一、跨域概念解析

跨域是一个在前端开发中经常遇到的问题。它指的是浏览器不能执行其他网站的脚本,这是由于浏览器的同源策略造成的。所谓同源,即协议、域名、端口都要相同。只要这三个要素中有任何一个不同,都被当作是不同的域,就会产生跨域问题。
同源策略是浏览器最核心也最基本的安全功能。如果缺少了同源策略,浏览器很容易受到 XSS、CSFR 等攻击。同源策略具体限制内容包括:
- Cookie、LocalStorage 和 IndexDB 无法读取:不同源的文档之间不能读取对方的存储内容。
- DOM 无法获得:不能获取非同源网页的 DOM。
- AJAX 请求不能发送:不能向非同源之地发送 ajax 请求。
例如,假设有两个网站,A 网站部署在 http://localhost:81,B 网站部署在 http://localhost:82。当 A 网站的页面想去访问 B 网站的信息时,就会出现跨域问题。因为它们的端口不同,不满足同源策略的要求。
总之,同源策略的存在是为了保护用户的安全和隐私,但在某些情况下,也给开发带来了一些挑战。开发人员需要了解跨域问题的本质,并掌握一些解决跨域问题的方法,以确保应用程序的正常运行。
二、跨域请求方式大揭秘

(一)JSONP 跨域
JSONP 是利用浏览器对 <script> 的资源引用没有同源限制这一特点来实现跨域请求。其原理是浏览器端动态生成 <script> 标签来请求后台提供数据的接口,并定义好用于接收响应数据的函数,同时将函数名通过请求参数提交给后台。服务器端接收到请求处理产生结果数据后,返回一个函数调用的 js 代码,并将结果数据作为实参传入到回调函数中。浏览器端收到响应后自动执行函数调用的 js 代码,即执行了提前定义好的回调函数,便得到了所需要的结果数据。
优点是对浏览器的支持较好,尤其支持老式浏览器。缺点是只能解决 GET 类型的 ajax 跨域请求,其他类型的跨域请求并不能处理;错误处理机制并不完善,无法进行错误处理。
前后端配合的代码示例:
前端 AJAX 请求:
$.ajax({
url: "http://otherdomain.com/manage/role/get",
async: false,
type: "get",
dataType: "jsonp",
data: {
"id": 1
},
jsonp: "callback",
jsonpCallback:"fn",
success: function(data){
alert(data.code);
},
error: function(){
alert('fail');
}
})
后端响应数据:
@RequestMapping("/manage/role/get")
@ResponseBody
public String get(HttpServletRequest request, HttpServletResponse response) {
BaseOutput outPut = new BaseOutput();
try {
QueryFilter filter = new QueryFilter(request);
logger.info(filter.toString());
String id = filter.getParam().get(MainConst.KEY_ID);
if(!StringUtil.isEmpty(id)) {
ImRole role = roleService.getByPk(filter);
outPut.setData(role);
} else {
outPut.setCode(OutputCodeConst.INPUT_PARAM_IS_NOT_FULL);
outPut.setMsg("The get id is needed.");
}
} catch (Exception e) {
logger.error("获取角色数据异常!", e);
outPut.setCode(OutputCodeConst.UNKNOWN_ERROR);
outPut.setMsg("获取角色数据异常! " + e.getMessage());
}
return "fn("+JsonUtil.objectToJson(outPut)+")";
}
(二)nginx 反向代理
nginx 反向代理解决跨域的原理是将 nginx 安装在本地,将项目部署于 nginx 下,通过反向代理的方式访问后台服务器。当浏览器发起请求时,由于请求的域名、协议、端口与 nginx 服务器相同,满足同源策略,从而避免了跨域问题。
需要进行的额外配置情况如下:
- 下载并安装 nginx。
- 在 nginx.conf 文件中进行配置,例如配置本地 web 项目访问路径和反向代理。
server {
listen 80;
server_name localhost;
location/ {
root myApp/mobile;
index index.html index.htm;
}
location/remote-interface/{
proxy_pass http://remote-address/remote-interface/ ;
}
}
(三)PHP 端修改 header
通过 PHP 端修改 header 可以解决跨域问题。可以在文件 header 里设置 ACCESS-CONTROL-ALLOW-ORIGIN,允许特定域名或所有域名访问。
设置方式如下:
- 允许全部的域名访问
header("Access-Control-Allow-Origin:*");
- 允许指定域名访问
header('Access-Control-Allow-Origin:http://a.test.com');
(四)document.domain 跨子域
利用 document.domain 可以解决不同域的两个页面之间 JavaScript 交互操作限制。此方案仅限于跨子域的应用场景,两个页面通过设置 document.domain 为基础主域,从而实现同域。
例如:
// A 页面:http://www.example.com/a.html
// B 页面:http://example.com/b.html
// 当 A、B 想要获取对方的 cookie 或者 DOM 节点时,可以设置:
document.domain='example.com';
(五)CORS 跨域
CORS(Cross-Origin Resource Sharing),即跨域资源共享。其原理是利用浏览器自动添加一些附加的头信息来实现跨域请求。服务器端通过返回特定的响应头来控制是否同意跨域请求。
服务器端响应头设置:
- 指定允许其他域名访问
Access-Control-Allow-Origin:// 值要么是请求时 Origin 字段的值,要么是一个 *,表示接受任意
域名的请求。
- 是否允许后续请求携带认证信息(cookies)
Access-Control-Allow-Credentials:true // 值是一个布尔值,表示是否允许发送 Cookie。
- 预检结果缓存时间
Access-Control-Max-Age: 1800
- 允许的请求类型
Access-Control-Allow-Methods:GET,POST,PUT,POST
- 允许的请求头字段
Access-Control-Allow-Headers:x-requested-with,content-type
如果要在跨域请求中带上 cookie,需要满足 3 个条件:
- web(浏览器)请求设置 withCredentials 为 true。
- 服务器设置首部字段 Access-Control-Allow-Credentials 为 true。
- 服务器的 Access-Control-Allow-Origin 不能为 *。
(六)图片 ping 或 script 标签跨域
图片 ping 和 script 标签跨域的方式是利用浏览器允许加载不同源的图片和脚本的特点来实现跨域数据传递。特点是简单易用,但缺点是只能进行单向的数据传递,且无法获取响应内容。
(七)window.name + iframe
通过 window.name 和 iframe 结合实现跨域的原理是在页面中动态创建一个 iframe 页面指向另一个域,将数据赋值给 iframe 的 window.name 属性,然后将 iframe 的 src 指向相同域的空白页面,之后再将 iframe 删除,就可以在当前页面获取到跨域的数据。
代码实现过程:
var proxy = function(url, callback) {
var state = 0;
var iframe = document.createElement('iframe');
iframe.src = url;
iframe.onload = function() {
if (state ===1) {
callback(iframe.contentWindow.name);
destoryFrame();
} else if (state ===0) {
iframe.contentWindow.location = 'http://www.domain.com/aa.html';
state = 1;
}
};
document.body.appendChild(iframe);
};
(八)postMessage
postMessage 是 HTML5 XMLHttpRequest Level 2 中的 API,它可以用于不同窗口之间的消息传递,包括跨域数据传递。
原理是通过 postMessage(data, origin) 方法,接受两个参数:data(要发送的数据,可以是任意基本类型或可复制的对象,但部分浏览器只支持字符串,所以传参时最好用 JSON.stringify() 序列化)和 origin(协议 + 域名 + 端口号,也可以设置为 "*",表示可以传递给任意窗口,如果要指定和当前窗口同源的话,设置为 "/")。
例如:
在父页面中嵌入子页面,通过 postMessage 发送数据。
// parent.com/index.html
window.onload = function() {
var ifr = document.getElementById('ifr');
var targetOrigin = 'http://child.com';
ifr.contentWindow.postMessage('I was there!', targetOrigin);
};
在子页面中通过 message 事件监听父页面发送来的消息并显示。
// child.com/index.html
window.addEventListener('message', function(event){
if (event.origin == 'http://parent.com') {
alert(event.data);
alert(event.source);
}, false);
};
(九)websocket
websocket 作为跨域解决方案的原理是通过建立一个持久化的连接,使得客户端和服务器可以双向通信,不受同源策略的限制。
代码实现如下:
var socket = new WebSocket('ws://otherdomain.com/socket');
socket.onopen = function() {
socket.send('Hello from client!');
};
socket.onmessage = function(event) {
console.log('Received message from server: ' + event.data);
};
(十)Node 中间件代理
Node 中间件代理解决跨域的原理是在 Node.js 服务器端设置一个代理,将客户端的请求转发到目标服务器,然后将目标服务器的响应返回给客户端。这样,对于客户端来说,请求看起来就像是来自同一个域。
代码示例:
const express = require('express');
const request = require('request');
const app = express();
app.use('/proxy', function(req, res) {
request('http://otherdomain.com' + req.url).pipe(res);
});
app.listen(3000, function() {
console.log('Proxy server running on port 3000');
});
(十一)nginx 反向代理
再次强调 nginx 反向代理解决跨域的原理是通过将本地的请求转发到不同域的服务器上,同时满足浏览器的同源策略,从而避免跨域问题。其优势在于配置相对简单,不需要对前端代码进行大量修改,并且可以同时代理多个不同域的服务器。