跨域(CORS)深入分析
1. 什么是跨域?
跨域(Cross-Origin)是指浏览器执行的某些请求时,当前网页的源(Origin)与请求资源的源不一致。例如:
- 同源(不会跨域)
http://example.com/page.html
访问http://example.com/data.json
- 跨域(浏览器限制)
http://example.com
访问http://api.example.com
http://localhost:3000
访问http://localhost:5000
http://example.com:80
访问http://example.com:8080
2. 浏览器如何访问服务器?
当你在浏览器中打开 http://example.com
,浏览器会向 example.com
服务器发起一个 HTTP 请求,服务器返回 HTML、CSS、JavaScript 等资源,然后浏览器解析和渲染网页。
3. 网页如何访问另一个服务器?
网页本身并不是服务器,它是浏览器渲染的内容,但网页中的 JavaScript 代码可以发起 HTTP 请求。例如,一个网页可能会使用 fetch()
或 XMLHttpRequest
请求 API 获取数据:
fetch("http://api.example.com/data")
.then(response => response.json())
.then(data => console.log(data));
这时,浏览器会尝试从 http://api.example.com
获取数据。但如果这个 API 服务器与网页来源 (http://example.com
) 不同,就会触发同源策略,默认情况下浏览器会拦截请求。
4. 为什么浏览器会拦截?
同源策略(Same-Origin Policy, SOP) 是浏览器的安全机制,防止恶意网站随意读取你在其他网站上的数据。例如,如果没有同源限制,一个黑客网站 http://malicious.com
可能会使用 JavaScript 访问你的银行账户 API http://bank.com/api
,并获取你的个人数据。
4.1 影响的操作
- XMLHttpRequest / Fetch 请求(被拦截)
- WebSocket(部分受限)
- DOM 访问(受限,如
iframe
) - Cookie、LocalStorage、IndexedDB 访问(受限)
但以下情况不会被拦截:
<script>
、<img>
、<link>
、<iframe>
- JSONP 方式
- CORS 允许的请求
5. 服务器如何允许跨域访问?
如果 http://api.example.com
服务器希望 http://example.com
可以访问它,需要在响应头中加上 Access-Control-Allow-Origin
,例如:
Access-Control-Allow-Origin: http://example.com
这样,浏览器才会放行这次请求。
5.1 浏览器如何获取 Access-Control-Allow-Origin
浏览器在处理跨域请求时,首先会发送一个请求,其中包含 Origin
头,该头表示发起请求的源。例如:
GET /data HTTP/1.1
Host: api.example.com
Origin: http://example.com
服务器接收到请求后,会检查 Origin
头是否在允许的域列表中。如果允许跨域访问,服务器会在响应中返回 Access-Control-Allow-Origin
头,例如:
HTTP/1.1 200 OK
Access-Control-Allow-Origin: http://example.com
Content-Type: application/json
当浏览器接收到这个响应并看到 Access-Control-Allow-Origin
头包含了请求的 Origin
值,它就会放行该请求,允许 JavaScript 访问返回的数据。
如果 Access-Control-Allow-Origin
头缺失或不匹配 Origin
,浏览器就会阻止 JavaScript 访问响应内容,并在控制台报跨域错误。
6. CORS(跨域资源共享)
CORS(Cross-Origin Resource Sharing)是一种允许跨域访问的机制。浏览器通过HTTP 响应头来决定是否允许跨域访问。
6.1 CORS 主要响应头
Header | 作用 |
---|---|
Access-Control-Allow-Origin | 指定允许的域,如 * 代表所有域 |
Access-Control-Allow-Methods | 允许的 HTTP 方法(如 GET, POST ) |
Access-Control-Allow-Headers | 允许的请求头 |
Access-Control-Allow-Credentials | 是否允许携带 Cookie |
Access-Control-Expose-Headers | 允许前端访问的额外响应头 |
6.2 CORS 请求类型
1. 简单请求
只包含:
GET
,POST
,HEAD
方法- 不包含自定义头部(
Content-Type
限于text/plain
,multipart/form-data
,application/x-www-form-urlencoded
)
请求示例:
GET /api/data HTTP/1.1
Origin: http://example.com
服务器响应:
HTTP/1.1 200 OK
Access-Control-Allow-Origin: http://example.com
2. 预检请求(Preflight)
- 当请求方法是 PUT/DELETE 或包含自定义头部时,浏览器会先发送 OPTIONS 请求
- 服务器需要响应允许的方法和头部
请求示例(浏览器自动发送的 OPTIONS
请求):
OPTIONS /api/update HTTP/1.1
Origin: http://example.com
Access-Control-Request-Method: PUT
服务器响应:
HTTP/1.1 200 OK
Access-Control-Allow-Origin: http://example.com
Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: Content-Type, Authorization
7. 解决跨域问题的方案
7.1 服务器端开启 CORS
Spring Boot 配置 CORS
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class CorsConfig {
@Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("http://example.com")
.allowedMethods("GET", "POST", "PUT", "DELETE")
.allowedHeaders("*")
.allowCredentials(true);
}
};
}
}
7.2 代理服务器(Nginx 反向代理)
在 Nginx 配置文件中添加:
server {
location /api/ {
proxy_pass http://backend-service;
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
}
}
7.3 JSONP(仅适用于 GET
请求)
前端示例:
<script src="http://example.com/api?callback=handleResponse"></script>
<script>
function handleResponse(data) {
console.log(data);
}
</script>
8. 总结
- 同源策略 限制了跨域请求。
- CORS 通过 HTTP 头允许跨域访问。
- 预检请求 适用于复杂请求(如
PUT
、自定义头)。 - 服务器配置 CORS 或使用代理 可以解决跨域问题。
- JSONP 是一种仅限
GET
请求的跨域方案。