要理解跨域,必须先明白同源策略。这是浏览器施加的一个极其重要的安全基石。
跨域(Cross-Origin)的定义:
- 当一个网页(运行在源A)试图向与自己不同源(源B)的服务器发起请求时,就发生了跨域请求。
- 浏览器检测到请求的目标源与当前页面的源不同,就会触发同源策略的限制。
为什么前端会碰到跨域问题?
现代前端应用通常是**SPA(单页应用)**架构:
1、前端代码(HTML,JS,CSS)部署在一个域名下(如https://www.myapp.com)。
2、后端API服务可能部署在另一个域名下(如https://api.myapp.com)或者完全不同的服务商(如https://third-party.api.com)。
3、前端代码需要调用这些不同源的后端API来获取或提交数据。
4、浏览器会阻止这种默认的跨域请求,导致前端无法获取数据,控制台抛出类似Access to fetch at ‘https://api.example.com/data’ from origin ‘https://www.myapp.com’ has been blocked by CORS policy的错误。
前端的解决方案(如何让跨域请求被允许?)
浏览器虽然默认阻止跨域请求,但也提供了机制让服务器和前端协作,在安全的前提下允许特定的跨域请求。主要方法有:
1、CORS(跨域资源共享 - Cross-Origin Resource Sharing)- 主流方案
-
原理: 这是W3C标准,是最推荐、最安全、最强大的解决方案。它需要后端服务器的配合。
-
机制:
- 浏览器在发送真正的跨域请求(尤其是非简单请求)之前,会先自动发送一个OPTIONS 预检请求(Preflight Request) 到目标服务器。
- 预检请求包含Origin(来源),Access-Control-Request-Method(想用的方法,如PUT,DELETE),Access-Control-Request-Headers(想用的自定义头)等信息。
- 目标服务器必须响应这个预检请求:
- 设置响应头Access-Control-Allow-Origin,指定允许哪些源访问(https://www.myapp.com 或 * 表示允许任何源,但慎用)。
- 设置Access-Control-Allow-Headers,指定允许哪些请求头(如Content-Type,Authorization)。
- 设置Access-Control-Allow-Methods,指定允许哪些HTTP方法(如GET,POST,PUT,DELETE)。
- 设置Access-Control-Allow-Credentials(可选),如果前端请求带了凭据(如Cookies),则服务器必须设置为true且Access-Control-Allow-Origin不能为 *。
- 设置Access-control-Max-Age(可选),指定预检请求结果的有效期(秒),避免频繁发送预检。
-
前端视角:前端代码(使用fetch或axios等库)几乎不需要特殊处理(除了可能需要设置**credentials:‘include’**来发送Cookies)。主要工作在后端配置响应头。这是现代Web开发处理跨域API调用的标准方式。
2、JSONP(JSON with Padding)- 过时方案,了解即可
- 原理: 利用< script > 标签不受同源策略限制的特性(可以加载不同源的 JS 文件)。
- 机制:
- 前端动态创建一个< script >标签,其src指向目标API URL,并带上一个回调函数名作为参数(如callback=handleData)。
- 服务器收到请求,不返回JSON,而是返回一段JS代码:调用前端指定的回调函数,并把真正的JSON数据作为参数传入(如handleData({ ‘key’: ‘value’ });)。
- 浏览器加载并执行这个返回的JS代码,相当于调用了前端预先定义好的回调函数handleData,数据就传递进来了。
- 前端视角:
function handleData(data) {
console.log('Received data:', data);
}
const script = document.createElement('script');
script.src = 'https://api.other.com/data?callback=handleData';
document.body.appendChild(script);
- 严重缺点:
- 仅支持GET请求。
- **安全性差:**完全信任服务器返回的JS代码并执行,如果服务器被黑或返回恶意代码,前端会直接中招。无法处理错误(如404)。
- 难以调试。
- 现状: 在现代Web开发中已基本被CORS取代,只存在于一些非常老旧或特殊的系统中。
3、代理(proxy)- 开发环境中常用
- 原理:让请求看起来是同源的。前端请求发送到同源的代理服务器,代理服务器再将请求转发到真正的目标服务器,拿到响应后再返回给前端。对浏览器来说,它只看到了同源请求。
- 实现方式:
- 开发服务器代理: 使用webpack-dev-server(配置devServer.proxy),vite(配置server.proxy),create-react-app(配置proxy in package.json)等工具内置的代理功能。这是前端开发环境解决跨域最常用、最方便的方式。
- Nginx / Apache 反向代理: 在生产环境中部署。配置Nginx/Apache,将特定路径(如/api/)的请求转发到目标后端服务器。
- 前端视角:前端代码直接请求自己域下的代理路径(如fetch(‘/api/users’)),开发服务器或Nginx会负责转发到https://api.realserver.com/users。前端感知不到跨域。
4、WebSocket
- WebSocket协议本身不受同源策略限制。浏览器允许建立到任何源的WebSocket连接。
- 注意: 虽然连接可以建立,但服务器仍然可以根据Origin头来决定是否接受连接,以实现应用层的访问控制。
- 用途: 主要解决实时双向通信(如聊天、实时推送),不是通用的解决普通HTTP API跨域的方法。
前端需要特别注意的点
1、CORS错误信息解读: 浏览器控制台的CORS错误信息通常很明确,会告诉你缺少哪个响应头(Access-Control-Allow-)或者哪个条件不满足。这是调试的关键。
2、携带凭据(Credentials): 如果跨域请求需要发送Cookies或HTTP认真信息:
- 前端: 必须在请求中明确设置credentials模式。在fetch中是credentials:‘include’;在XMLHttpRequest中是xhr.withCredentials = true。
- 后端: 响应头必须 包含Access-Control-Alllow-Credentials:true,并且Access-Control-Allow-Origin 不能是通配符*, 必须是具体的请求来源域名(如https://www.myapp.com)。
3、简单请求 vs 预检请求: 理解哪些请求会触发预检请求很重要(使用了非简单方法PUT/DELETE、非简单Content-Type如application/json、使用了自定义请求头)。预检失败会导致真实请求根本发不出去。
4、Access-Cntrol-Allow-Origin:* 的陷阱:
- 虽然方便,但意味着任何网站都可以通过前端脚本访问你的API资源。这通常不安全,除非你的API确实是完全公开、无任何敏感信息的。
- 如果请求需要携带凭据(Cookies),* 是无效的,必须指定具体域名。
5、开发环境代理(Proxy): 这是本地开发时解决跨域的首选利器,配置简单且安全。务必掌握你所用开发工具(Webpack,Vite,CRA等)的代理配置方法。
总结
- 跨域是浏览器同源策略导致的限制。
- 根据目的是保障用户安全。
- CORS是现代Web处理跨域请求的标准、安全、强大的方式,需要后端配合设置响应头。
- JSONP是过时的、不安全的hack,仅支持GET,应避免使用。、
- 代理(尤其是开发服务器代理)是前端开发环境解决跨域最常用、最便捷的方法。
- WebSocket用于实时通信,不受同源策略限制连接建立(但服务器仍可检查Origin)。
- 前端需要关注CORS错误信息、凭据以及开发环境代理的配置。