摘要
在现代Web开发中,前端与后端分离的架构已成为主流。前端应用通常部署在独立的域名或端口,通过API请求与后端服务进行数据交互。然而,这种分离也带来了前端开发者绕不开的"跨域问题"。跨域,即Cross-Origin,是浏览器基于安全考虑实施的"同源策略"所导致的限制。本文将作为系列文章的上篇,深入剖析同源策略的定义、目的与限制,揭示跨域问题的本质,并为读者建立对跨域问题的底层认知,为后续理解各种解决方案打下坚实基础。
1. 什么是"源"(Origin)?
在Web安全领域,理解"源"的概念是理解同源策略的前提。一个"源"由以下三个部分组成:
- 协议(Protocol) :例如
http://
、https://
。 - 域名(Domain) :例如
www.example.com
、api.example.com
。 - 端口(Port) :例如
80
(HTTP默认端口)、443
(HTTPS默认端口)、8080
。
只有当这三个部分都完全相同时,两个URL才被认为是"同源"的。任何一个部分不同,都被认为是"异源"(Cross-Origin)。
示例:
假设当前页面URL为 http://www.example.com:80/index.html
URL | 是否同源? | 原因 |
---|---|---|
http://www.example.com:80/data |
是 | 协议、域名、端口都相同 |
https://www.example.com:80/data |
否 | 协议不同(http vs https) |
http://api.example.com:80/data |
否 | 域名不同(www.example.com vs api.example.com) |
http://www.example.com:8080/data |
否 | 端口不同(80 vs 8080) |
2. 同源策略(Same-Origin Policy):Web安全的基石
2.1 定义与目的
同源策略 (Same-Origin Policy)是浏览器最核心、最基本的安全功能。它限制了一个源的文档或脚本如何与另一个源的资源进行交互。简而言之,就是一个源的Web页面只能访问同源的资源,不能直接访问异源的资源。
同源策略的主要目的是为了保护用户的信息安全,防止恶意网站窃取或篡改用户数据。试想一下,如果没有同源策略,当你登录了银行网站后,同时又打开了一个恶意网站。这个恶意网站就可以通过JavaScript向银行网站发送请求,获取你的账户信息,甚至进行转账操作,这将带来巨大的安全风险。
2.2 同源策略的限制
同源策略主要限制了以下三种跨域行为:
- XMLHttpRequest 和 Fetch API 请求:这是最常见的跨域问题。浏览器会阻止从一个源发起的XMLHttpRequest或Fetch请求,去获取另一个源的资源。即使请求成功返回了数据,浏览器也会阻止JavaScript访问这些数据。
- DOM操作 :不同源的
iframe
之间不能互相访问DOM内容。例如,父页面无法获取iframe
内部的DOM元素,iframe
也无法访问父页面的DOM。 - Cookie、LocalStorage 和 IndexedDB:不同源的页面之间无法互相读取或修改对方的Cookie、LocalStorage和IndexedDB等客户端存储数据。
需要注意的是 :同源策略只是一种浏览器行为 。服务器之间进行数据交互时,并没有同源策略的限制。例如,后端服务器可以向任何其他服务器发送请求并获取数据。跨域问题只存在于浏览器端,是为了保护用户在浏览网页时的安全。
3. 跨域问题的本质
跨域问题的本质是浏览器对JavaScript发起的HTTP请求的一种安全限制。当浏览器检测到JavaScript代码尝试访问一个与当前页面不同源的资源时,它会阻止该请求的响应数据被JavaScript访问,从而避免潜在的安全风险。
一个典型的跨域场景:
假设你的前端应用部署在 http://localhost:3000
,而后端API服务运行在 http://localhost:8080
。当前端页面中的JavaScript代码尝试通过XMLHttpRequest
或fetch
向 http://localhost:8080/api/data
发送请求时,由于协议、域名和端口中至少有一个不同(端口不同:3000 vs 8080),浏览器就会触发同源策略,阻止前端JavaScript获取到后端返回的数据,即使后端服务器已经成功响应了请求。
从提供的文件看跨域场景:
在server.js
文件中,我们看到了一个简单的Node.js HTTP服务器,它监听在8080
端口,并提供了一个/api/hello
接口:
javascript
// server.js
const http = require('http');
const server = http.createServer((req, res) => {
if(req.url ==='/api/hello'&&req.method ==='GET'){
console.log('/////');
res.writeHead(200,{
'Content-Type':'text/javascript'
});
const data = {
code:0,
msg:'字节,我来了'
}
// json with padding
res.end("callback("+ JSON.stringify(data) +")")
}else{
res.writeHead(404,{
'Content-Type':'text/plain'
});
res.end('Not Found');
}
})
server.listen(8080,()=>{
console.log(`Server is running on port 8080`);
})
这个server.js
返回的Content-Type
是text/javascript
,并且响应内容是callback(...)
的形式,这暗示了它可能被设计用于JSONP 这种跨域解决方案。如果前端页面(例如部署在http://localhost:3000
的index.html
)尝试通过script
标签加载这个http://localhost:8080/api/hello
接口,就会涉及到跨域。
而index.html
本身是一个本地文件,或者部署在某个前端服务器上。如果index.html
中的JavaScript尝试直接通过XMLHttpRequest
或fetch
请求http://localhost:8080/api/hello
,那么就会遇到典型的跨域问题。
4. 总结(上篇)
通过上篇的探讨,我们明确了"源"的定义,理解了同源策略作为浏览器安全基石的重要性,以及它对跨域请求的限制。我们还通过实际案例揭示了跨域问题的本质,即浏览器出于安全考虑对异源请求的响应数据进行拦截。在下一篇中,我们将深入探讨各种解决跨域问题的方案,包括它们的原理、优缺点以及适用场景,帮助读者在实际开发中选择最合适的跨域解决方案。