报错如下:
同源策略(Same-Origin Policy, SOP)
同源策略(Same-Origin Policy,SOP)是Web浏览器中的一种关键安全机制,旨在限制不同源之间的交互,以保护用户数据的安全和隐私。
域名、协议、端口有一个不同就不是同源,三者均相同,这两个网站就是同源。
1. 跨域网络请求
-
AJAX/Fetch 请求:
-
向不同源的服务器发送
XMLHttpRequest
或Fetch
请求,若服务器未配置 CORS(跨域资源共享),浏览器会拒绝返回数据。
-
// 请求不同源的 API
fetch('https://api.other-site.com/data')
.then(response => response.json())
.catch(error => console.error('被浏览器拦截'));
2. 跨域资源访问
读取跨域资源内容:
-
通过脚本加载跨域资源(如 JSON 文件、图片、音频),但直接读取内容会被拦截
// 跨域的图片(允许加载,但无法读取像素数据)
const img = new Image();
img.src = 'https://other-site.com/image.jpg';
img.onload = () => {
canvasContext.drawImage(img, 0, 0);
canvasContext.getImageData(0, 0, 100, 100); // 报错:画布被污染
};
但也不是所有资源都是要遵守同源策略:
解决跨域
JSONP
JSONP 的核心原理是 通过 <script>
标签加载并执行一段远程代码,是一种“通过代码执行而非数据传输”的跨域方案:
-
跨域能力:
<script>
标签的src
属性不受同源策略限制。 -
代码注入:服务端返回的代码被浏览器当作普通脚本执行,等同于在页面中直接编写的代码。
-
数据传递:通过函数调用将数据注入客户端代码。
实现:
<!DOCTYPE html>
<html>
<body>
<!-- 定义全局回调函数 -->
<script>
window.abc = function (data) {
console.log(data); // 当JSONP响应加载后触发
}
</script>
<!-- 动态插入JSONP请求 -->
<script src="http://localhost:8002/jsonp.js?username=xxx&callback=abc"></script>
</body>
</html>
等同于
<script>
window.abc = function (data) {
console.log(data);
};
</script>
<script>
// 直接调用全局函数
abc({ name: 'xxx' }); // 输出 { name: 'xxx' }
</script>
- 效果:与 JSONP 完全一致,只是第二个
<script>
的内容由服务端动态生成。
jQuery实现jsonp
跨域资源共享(CORS)
尽管SOP提供了强有力的安全保障,但它也限制了合法的跨域资源共享需求。为了解决这些问题,Web标准引入了跨域资源共享(CORS)等机制,允许在特定条件下进行跨域交互。CORS是一个W3C标准,允许服务器声明哪些源可以访问其资源,突破了AJAX只能同源使用的限制。
CORS的工作原理
- 浏览器检测:当浏览器检测到AJAX请求跨域时,它会自动添加CORS相关的HTTP头信息。这些头信息用于告知服务器请求的源以及请求的性质。(简单请求与非简单请求有不同的HTTP头信息, 详细可见阮一峰的文章)
- 服务器响应:服务器需要通过设置适当的CORS响应头来允许跨域请求。常见的CORS响应头包括:
- 浏览器处理:如果服务器响应中包含适当的CORS头信息,浏览器将允许跨域请求的响应被JavaScript访问,否则将被拦截。
不同服务器实现例子:
node.js直接配置
const http = require('http');
const port = process.env.PORT || 8000;
const server = http.createServer((request, response) => {
console.log(`METHOD: ${request.method}; URL: ${request.url}`);
// 设置CORS头信息
response.setHeader('Access-Control-Allow-Origin', '*'); // 允许所有源
response.setHeader('Access-Control-Allow-Methods', 'OPTIONS, GET, POST, PUT, DELETE'); // 允许的HTTP方法
response.setHeader('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept'); // 允许的请求头
if (request.method === 'OPTIONS') {
// 对于预检请求,直接返回成功响应
response.writeHead(204);
response.end();
return;
}
// 处理其他请求
response.writeHead(200, { 'Content-Type': 'application/json' });
response.end(JSON.stringify({ message: 'CORS is enabled for this server' }));
});
server.listen(port, () => {
console.log(`Server is running on port ${port}`);
});
Node.js(Express)
const express = require('express');
const cors = require('cors');
const app = express();
// 使用默认配置允许所有跨域请求
app.use(cors());
// 自定义配置
// app.use(cors({
// origin: 'http://example.com',
// methods: 'GET,POST',
// allowedHeaders: ['Content-Type', 'Authorization']
// }));
app.listen(3000, () => {
console.log('Server is running on port 3000');
});
Apache
在httpd.conf
或.htaccess
中添加:
<Directory "/var/www/html">
Header set Access-Control-Allow-Origin "*"
Header set Access-Control-Allow-Methods "GET,POST,OPTIONS"
Header set Access-Control-Allow-Headers "Content-Type"
</Directory>
Nginx
在Nginx配置文件中添加:
location / {
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
add_header 'Access-Control-Max-Age' 1728000;
add_header 'Content-Type' 'text/plain charset=UTF-8';
add_header 'Content-Length' 0;
return 204;
}
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
}