跨域问题及其解决方案
跨域问题及其解决方案
本文介绍了跨域问题的定义和同源策略的限制,以及三种跨域解决方案:CORS、JSONP和代理。
什么是跨域问题
我们先看一段简单的html代码,对所谓的跨域问题有一个直观的了解
<!DOCTYPE html>
<html>
<head>
<title>跨域问题示例</title>
<meta charset="utf-8">
<script>
function requestData() {
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (xhr.readyState == 4 && xhr.status == 200) {
document.getElementById("result").innerHTML = xhr.responseText;
}
}
xhr.open("GET", "<https://www.baidu.com/>", true);
xhr.send();
}
</script>
</head>
<body>
<button onclick="requestData()">请求百度</button>
<div id="result"></div>
</body>
</html>
页面上的按钮会触发一个JavaScript函数,该函数使用XMLHttpRequest对象向www.baidu.com/发出GET请求。如果请…
但是,该请求将会被浏览器拦截。在控制台中,我们会看到以下错误消息:
Access to XMLHttpRequest at '<https://www.baidu.com/>' from origin 'null' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: Redirect is not allowed for a preflight request.
这就是我们常说的跨域问题的具体表现之一。
同源政策
跨域错误源自浏览器的”同源政策”(same-origin policy),该策略在浏览器中的实现最初由 Netscape Communications Corporation 在1995年引入。
同源策略是浏览器的基本安全策略,旨在防止恶意网站窃取用户数据,缺少了同源策略,浏览器很容易受到XSS、CSFR等攻击
这里的同源指的是两个页面的协议、端口和主机名都相同,例如
请求1 | 请求2 | 是否同源 |
---|---|---|
www.example.com/dir/page.ht… | www.example.com/dir/page.ht… | 同源 |
www.example.com/dir/page.ht… | www.example.com/dir/page.ht… | 不同源 |
同源策略主要限制以下行为:
- Cookie、LocalStorage 和 IndexDB 等存储机制的访问:同源的页面可以共享这些存储机制,但不同源的页面无法访问对方的存储数据。
- DOM 节点的访问:同源的页面可以通过 JavaScript 访问对方的 DOM 节点,但不同源的页面无法访问对方的 DOM 节点。
- AJAX 请求的发送:同源的页面可以通过 AJAX 发送请求并获取响应,但不同源的页面无法发送请求或获取响应。
- 通过 iframe 加载的资源的访问:如果一个页面通过 iframe 加载了另一个页面,那么这两个页面必须同源才能相互访问。
- Web Workers 之间的消息通信:同源的 Web Workers 可以相互通信,但不同源的 Web Workers 无法通信。
在远古时代,大部分应用都是单体应用,同源策略并不会造成什么困扰,直到前后端分离开发模式的出现。在前后端分离的开发模式下,前端服务往往部署在A域名下,后端服务往往部署在B域名下,这时前端请求后端接口,就会出现我们一开始例子中的跨域问题。
解决方案
CORS(跨域资源共享)
CORS 是一个W3C标准,全称是"跨域资源共享"(Cross-origin resource sharing)。
该机制解决跨域问题的方式如下:在浏览器发出跨域请求前,要求后端服务器先列出允许跨域访问的源,浏览器根据这个源名单放行跨域请求。具体实现上,浏览器在发出正式请求前,增加一次OPTIONS请求,称为"预检"请求(preflight)。
这里以Springboot项目为例,例举两种对预检请求的处理方式
- 在过滤器中处理预检请求
@Configuration
public class MyWebConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
// allowedOrigins 决定哪些域名可以跨域发起请求 这里*表示放行所有跨域请求
.allowedOrigins("***")
.allowedMethods("POST", "GET", "PUT", "OPTIONS", "DELETE")
.maxAge(3600)
.allowCredentials(true);
}
}
- 使用
@CrossOrigin 注解
@GetMapping(value = "login")
@CrossOrigin
public Map<String, Object> queryActivityBonusInfo() {
.......
}
JSONP
JSONP是一种跨域请求的解决方案,它的原理是利用script标签的src属性不受同源策略限制的特点,通过动态创建script标签的方式向服务器发送请求,服务器返回的数据被包裹在一个函数调用中返回给客户端,客户端通过回调函数获取数据。
<script>
function handleData(data) {
console.log(data);
}
const url = '<http://example.com/data?callback=handleData>';
const script = document.createElement('script');
script.src = url;
document.body.appendChild(script);
</script>
在这个例子中,我们定义了一个名为handleData
的回调函数来处理从服务器返回的数据。
我们使用document.createElement()
方法创建了一个<script>
元素,并将其src
属性设置为带有回调函数名称的URL。
然后,我们将该元素添加到文档中,浏览器会自动执行该URL返回的JavaScript代码,该代码将调用我们定义的回调函数,并将数据作为参数传递给该函数。
服务器必须返回一个包含回调函数名称和数据的JavaScript函数,例如:
handleData({"name": "John", "age": 30});
代理
代理是一种比较常见的跨域解决方案,它的原理是通过在同域下的服务器端发起请求,将请求转发给目标服务器,再将目标服务器返回的数据返回给客户端,客户端只需要与同域下的服务器端进行通信即可。
使用服务器代理
可以通过设置服务器代理来解决跨域问题。例如,使用 Node.js 的 Express 框架:
const express = require('express');
const request = require('request');
const app = express();
app.get('/api', (req, res) => {
const url = '{要请求的接口地址}';
req.pipe(request(url)).pipe(res);
});
app.listen(3000, () => {
console.log('服务器已启动');
});
在上述代码中,我们定义了一个 /api
路由,当我们访问该路由时,会使用 request
模块向指定接口地址发起请求,并将请求结果返回给前端。
使用反向代理
另一种常见的代理方式是使用反向代理。例如,使用 Nginx:
server {
listen 80;
server_name {服务器地址};
location /api {
proxy_pass {要请求的接口地址};
}
}
在上述代码中,我们定义了一个 /api
路由,当我们访问该路由时,Nginx 会将请求转发给指定的接口地址,并将请求结果返回给前端。
转载自:https://juejin.cn/post/7236305021594566693