CodeRoadMap
首页路线题库资源库文章社区
CodeRoadMap

程序员的学习成长路线图。内容免费开放,登录后可同步路线图与课时进度。

由 程序员柏风 独立维护 · 关于作者

学习

  • 路线图
  • 课程
  • 面试题
  • 文章
  • 我

更多

  • 资源
  • 社区
  • 关于作者
  • 内容说明

© 2026 CodeRoadMap

津ICP备2026012044号-1·津公网安备12011002026155号·coderoadmap@126.com
HTTP 协议与缓存/课时/CORS
RawBlame
课时目录12 节

HTTP 协议与缓存

  1. 01HTTP 是什么
  2. 02方法与语义
  3. 03状态码
  4. 04请求与响应头
  5. 05HTTPS 与 TLS
  6. 06HTTP 缓存
  7. 07Cookie 与会话
  8. 08CORS
  9. 09REST 与资源设计
  10. 10HTTP/2 与 HTTP/3
  11. 11应用层缓存对照
  12. 12综合练习

CORS

预检 OPTIONS、跨域与前后端分离。

30 分钟

CORS

这一课你将能做到

  1. 用「协议 + 域名 + 端口」三要素定义源(Origin),并快速判断任意两串 URL 是否跨源
  2. 区分简单请求与预检 OPTIONS,列出至少三种会触发预检的特征,并读懂 Access-Control-Allow-* 四个响应头
  3. 遇到 Console 里的 CORS 报错时,到 Network 定位失败的 OPTIONS/主请求,判断该改 Spring 哪处配置,还是该改成同域反代

为什么要学

本地前端 localhost:3000 调后端 localhost:8080,或者前后端挂在不同子域,浏览器会直接拦截「读跨源响应」——Console 一串红色 blocked by CORS policy,新手最容易在这里耗一整天:改前端、换请求库、装禁用 CORS 的插件,全都不对。

根因永远是服务端 CORS 配置(或干脆同域部署)。面试常问「为什么 curl 没有跨域问题」「CORS 预检是什么」——本质考察你是否理解:CORS 是浏览器的安全机制,不是服务器拒绝了你。这一课学完,跨域问题从玄学变成流程。

先建立图像

源 = 协议 + 域名 + 端口,三者全同才算同源:

https://coderoadmap.cn  vs  https://api.coderoadmap.cn  → 跨源(域名不同)
http://localhost:3000   vs  http://localhost:8080       → 跨源(端口不同)
https://a.com           vs  http://a.com                → 跨源(协议不同)
https://coderoadmap.cn/x  vs  https://coderoadmap.cn/y  → 同源(路径不算)
http://127.0.0.1:3000   vs  http://localhost:3000       → 跨源(127.0.0.1 ≠ localhost)

两类请求的处理方式:

简单请求:直接发出 → 浏览器看响应头有没有 CORS 允许 → 决定 JS 能不能读
预检请求:先发 OPTIONS「我能不能发这种请求?」→ 通过后才发真正的 GET/POST/PUT…

触发预检的三类典型特征:

特征例子
方法非简单方法(GET/HEAD/POST)PUT、DELETE、PATCH
Content-Type 非简单值application/json(表单三兄弟才是简单值)
带了自定义头Authorization、X-Request-Id

服务器预检/响应里应出现(按需):

Access-Control-Allow-Origin: http://localhost:3000
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Allow-Credentials: true

红线:Allow-Origin: * 与 Allow-Credentials: true 互斥——带 Cookie 的跨源必须回显具体 Origin。

跟着做

步骤 1:给 Spring Boot 配一段真实可用的 CORS

@Configuration
public class CorsConfig implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/api/**")
                // 生产写白名单;本地开发放 3000
                .allowedOrigins("http://localhost:3000")
                .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
                .allowedHeaders("Content-Type", "Authorization")
                .allowCredentials(true)          // 与具体 Origin 配对,不能配 "*"
                .maxAge(3600);                   // 预检结果缓存 1 小时,减少 OPTIONS
    }
}

重启后,localhost:3000 的前端调 /api/** 即可跨源工作。

步骤 2:用 curl 模拟一次预检

浏览器才会自动发 OPTIONS,curl 可以替浏览器问一次:

curl -i -X OPTIONS http://localhost:8080/api/notes \
  -H "Origin: http://localhost:3000" \
  -H "Access-Control-Request-Method: GET" \
  -H "Access-Control-Request-Headers: authorization"

期望响应:200 或 403 之外的正常状态 + 上面四个 Access-Control-* 头。把 Origin 换成 http://evil.com 再试——白名单外应看不到 Allow-Origin 头,这就是拦截机制。

步骤 3:亲手制造一次 CORS 失败并读懂它

临时把配置里的 allowedOrigins 注释掉(或改成别的端口),然后:

  1. 本地起一个 :3000 页面(或直接在 Console 执行跨源 fetch)
  2. Console 出现 has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present
  3. Network 面板:先找 OPTIONS 请求(可能 403/404/无 CORS 头),再看主请求

关键认知:主请求可能已经成功到达服务器并返回 200(看 Spring 日志),只是浏览器不让 JS 读结果——服务器日志有记录、前端却在报 CORS,就是这么来的。

步骤 4:带 Cookie 的跨源三件套

// 前端:显式要求带凭证
fetch('http://localhost:8080/api/auth/me', {
  credentials: 'include',
  headers: { 'Content-Type': 'application/json' }
})

服务端必须同时满足(Cookie 课的「卡点急救 3」在此展开):

  1. allowedOrigins 回具体 Origin(不许 *)
  2. allowCredentials(true) → 响应带 Access-Control-Allow-Credentials: true
  3. Cookie 自身的 SameSite/Domain 允许该跨站场景

步骤 5:生产同域反代——从根上消灭 CORS

浏览器只访问 https://coderoadmap.cn(同源!)
Nginx:
  /api/  → proxy_pass http://127.0.0.1:8080
  /      → 前端页面服务
→ 页面与 API 同源 → 浏览器根本不启动 CORS 检查

这是本站生产架构:开发期跨源要配 CORS,生产尽量同域。CORS 当开发与多域场景的工具,而不是永久架构负担。

步骤 6:排除「假 CORS」

有时真凶是 500/502:异常响应(HTML 错误页)没有 CORS 头,浏览器报 CORS——但它其实是上游挂了。排错二分法:

# 绕过浏览器看裸响应:状态码与 Content-Type 一目了然
curl -i -X OPTIONS http://localhost:8080/api/notes -H "Origin: http://localhost:3000" \
  -H "Access-Control-Request-Method: GET"
curl -i http://localhost:8080/api/notes

curl 显示 5xx → 先修服务端;curl 显示 200 但无 Allow-Origin 头 → 才是真的 CORS 配置问题。

步骤 7:过滤器全局方案(对照记忆)

网关/安全框架场景常用 CorsFilter(优先级要高于鉴权过滤器,否则 OPTIONS 先被 401):

@Bean
public CorsFilter corsFilter() {
    CorsConfiguration config = new CorsConfiguration();
    config.setAllowedOrigins(List.of("http://localhost:3000"));
    config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "OPTIONS"));
    config.setAllowedHeaders(List.of("Content-Type", "Authorization"));
    config.setAllowCredentials(true);
    UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
    source.registerCorsConfiguration("/**", config);
    return new CorsFilter(source);
}

两种方案别混用同一组路径——头重复或覆盖会让预检结果时好时坏,极难排查。

讲清楚要点

  1. 因为同源策略保护的是用户,所以「别的网站的 JS」默认不能读你的响应——CORS 是服务器签发的通行证:决定权在响应头,不在前端代码。
  2. 因为预检发生在真请求之前,所以只放行了 GET 却忘了 OPTIONS/自定义头,会表现为「预检失败、主请求根本没发」——allowedMethods 和 allowedHeaders 要覆盖前端实际会用的组合。
  3. 因为 curl/Postman 没有同源策略,所以「curl 通、浏览器不通」优先查 CORS 与 Cookie——这不是玄学,是两套运行环境的安全差异。
  4. 因为 * 与 credentials 互斥,所以带 Cookie 的 SPA 必须回显具体 Origin——要么维护白名单,要么同域反代。
  5. 因为自定义头和 JSON Content-Type 几乎必触发预检,所以开发期 Network 里 OPTIONS 多是正常现象——maxAge 可以减少 OPTIONS 频率,但配置变更后要让浏览器缓存过期。
  6. 因为同域反代从架构上消灭跨源,所以能合并域名时优先合并——Nginx 也能直接加 CORS 头,但业务白名单通常放应用层,两层都配容易互相覆盖。

你可能会懵的地方

  1. CORS 报错是不是服务器拒绝了请求? 不一定,而且经常不是。主请求可能已到达并 200,只是响应缺 Allow-Origin 头,浏览器不让 JS 读 body。判断方法:看服务器日志有没有这次请求的处理记录。

  2. mode: 'no-cors' 能当修复手段吗? 不能。它把请求降级为「不透明响应」,JS 完全读不到内容,只能用于图片、beacon 这类「发出去就行」的场景。跨域要读数据,正路是配 CORS 或同域反代。

  3. 为什么 Postman/后端服务间调用从来不报 CORS? 同源策略是浏览器行为。服务器之间、curl、原生 App 都不受限。所以「后端说接口好的,前端说跨域」时,两边说的都是真话。

  4. Allow-Origin: * 到底能不能用? 纯公开只读数据(如公开课表)可以接受。涉及用户身份/Cookie/私有数据时危险——任何网站都能借用户浏览器读你的接口,且与 credentials 互斥。生产建议显式白名单。

  5. 预检 OPTIONS 请求打到后端,被 Spring Security 401 了怎么办? 预检请求不带凭证,不能要求鉴权。把 CORS 处理放在认证过滤器之前(CorsFilter 高优先级),或对 OPTIONS 放行:.requestMatchers(HttpMethod.OPTIONS).permitAll()。

错法对照

错法问题正解
前端改 mode: 'no-cors' 当修复响应变不透明,JS 读不到任何数据服务端配 CORS 或改同域反代
Allow-Origin: * + Allow-Credentials: true规范互斥,浏览器直接判失败具体 Origin 回显 + credentials
CORS 只配了 allowedMethods("GET")JSON POST/PUT 的预检挂掉,接口「假死」方法、头、OPTIONS 按实际需求放全
开发期装浏览器插件「禁用 CORS」掩盖问题,上线即复发,还引入安全风险本地正确配置白名单或走 dev proxy 同域化
接口 500 前端却报 CORS,修了半天 CORS错误页无 CORS 头,浏览器归因到 CORScurl 先看真实状态码,5xx 先修服务端

卡点急救

  1. Console 报 No 'Access-Control-Allow-Origin' header:Network 里点开该请求看 Response Headers 有没有 CORS 头。没有 → 检查 Spring 配置是否生效(配置类有没有被扫描、路径是否匹配);换 Origin 为白名单内地址再 curl 一次对比。
  2. 预检 OPTIONS 返回 401/403:CORS 处理被安全过滤器抢在了前面。用 CorsFilter 注册并设最高优先级,或 Security 配置里对 OPTIONS permitAll。
  3. 网关/nginx 层和应用层都配了 CORS,头重复冲突:Access-Control-Allow-Origin 出现两次浏览器直接报错。只留一层:业务白名单放应用,网关层删掉 CORS 相关 add_header;或反指定一层统一处理。

面试常问

  • 什么是同源策略?CORS 又是什么? 同源策略:浏览器限制「脚本只能读同源响应」,防止恶意网站借用户身份读别的站的数据。CORS(跨源资源共享):服务器通过一组 Access-Control-* 响应头,显式授权指定源的页面读取自己的资源——是同源策略的标准化豁免机制。关键:这是浏览器行为,curl/服务端调用不受限。

  • 什么情况下浏览器会发预检 OPTIONS 请求? 非简单请求:方法不是 GET/HEAD/POST;或 Content-Type 不是 application/x-www-form-urlencoded、multipart/form-data、text/plain 之一;或带了自定义头(如 Authorization)。预检通过后浏览器才发真实请求,结果可被 Access-Control-Max-Age 缓存。

  • 生产上如何彻底避免 CORS 问题? 同域部署:Nginx 把页面与 /api 反代到同一域名下,浏览器全程同源。无法同域时维护 Origin 白名单 + 按需 credentials,并保证预检处理在鉴权之前。避免双层配 CORS 头。

自测三问

  1. 判断 http://127.0.0.1:3000 与 http://localhost:3000 是否同源,为什么?
  2. 列出三种触发预检的请求特征,并说出前端哪种常用写法「必然」触发预检。
  3. 生产同域 Nginx 反代如何消灭 CORS?开发期仍跨源时,最小正确的 Spring 配置长什么样?

小结一句话

CORS 是浏览器读跨源响应前要拿的通行证:OPTIONS 预检先问可不可以——根治靠服务端正确响应头或同域反代,不靠关掉浏览器安全。

延伸

  • MDN:CORS
  • MDN:同源策略
  • 下一课:REST 与资源设计——把方法、状态码、路径参数拼成一套前后端都省心的 API 设计骨架。
加载本节练习…
← 上一课Cookie 与会话下一课 →REST 与资源设计