一、跨域問題的本質
1.1 同源策略的三要素
瀏覽器的同源策略(Same-Origin Policy)要求請求的 協議、域名、端口 完全一致,否則視為跨域:
- 協議不同:
http
與https
- 域名不同:
a.com
與b.com
- 端口不同:
http://a.com:80
與http://a.com:8080
1.2 跨域請求的分類
- 簡單請求(Simple Request):
- HTTP方法:
GET
、POST
、HEAD
- 請求頭:僅允許
Accept
、Accept-Language
、Content-Type
(且Content-Type
僅限text/plain
、multipart/form-data
、application/x-www-form-urlencoded
)
- HTTP方法:
- 預檢請求(Preflight Request):
- 當請求包含自定義頭(如
Authorization
)或使用非簡單方法(如PUT
、DELETE
)時,瀏覽器會先發送OPTIONS
請求,詢問服務器是否允許該請求。
- 當請求包含自定義頭(如
二、解決方案一:CORS 標準實現
2.1 CORS 工作原理
CORS 通過 預檢協商機制 解決跨域問題:
- 預檢請求(OPTIONS):
- 瀏覽器發送
OPTIONS
請求,攜帶請求方法(Access-Control-Request-Method
)和頭字段(Access-Control-Request-Headers
)。 - 服務端響應中必須包含允許的
origin
、methods
、headers
,否則瀏覽器阻止后續請求。
- 瀏覽器發送
- 實際請求:
- 若預檢通過,瀏覽器發送真實請求,并攜帶
Origin
頭。 - 服務端在響應頭中明確允許的
Access-Control-Allow-Origin
,瀏覽器才會將數據返回給前端。
- 若預檢通過,瀏覽器發送真實請求,并攜帶
2.2.1 Spring Boot 實現
// 全局CORS配置(application.properties)
spring.mvc.cors.enabled=true
spring.mvc.cors.allow-origins=http://client.example.com
spring.mvc.cors.allow-methods=GET,POST,PUT,DELETE,OPTIONS
spring.mvc.cors.allow-headers=Authorization,Content-Type,X-Requested-With
spring.mvc.cors.exposed-headers=X-Total-Count
spring.mvc.cors.allow-credentials=true
spring.mvc.cors.max-age=3600// 或通過Java配置
@Configuration
public class CorsConfig implements WebMvcConfigurer {@Overridepublic void addCorsMappings(CorsRegistry registry) {registry.addMapping("/**").allowedOrigins("http://client.example.com").allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS").allowedHeaders("Authorization", "Content-Type").exposedHeaders("X-Total-Count").allowCredentials(true).maxAge(3600);}
}
2.2.2 預檢請求處理(關鍵點)
Spring Boot 默認會自動處理 OPTIONS
請求,但需確保:
allowedMethods
包含OPTIONS
allowedHeaders
包含所有自定義頭字段
2.3 安全加固
- 動態驗證來源:
@Bean public CorsWebFilter corsFilter() {return (serverWebExchange, chain) -> {String origin = serverWebExchange.getRequest().getHeaders().getOrigin();if (allowedOrigins.contains(origin)) {// 設置CORS頭serverWebExchange.getResponse().getHeaders().add("Access-Control-Allow-Origin", origin);return chain.filter(serverWebExchange);}return Mono.error(new AccessDeniedException("Invalid origin"));}; }
三、解決方案二:JSONP
3.1 JSONP 工作原理
JSONP 利用 <script>
標簽的跨域特性,通過動態注入腳本實現數據回傳:
- 前端動態注入:
- 創建
<script>
標簽,src
指向服務端接口,附加callback
參數。
- 創建
- 服務端封裝數據:
- 將數據包裝在
callback
函數中,返回類似handleResponse({data: "value"})
的字符串。
- 將數據包裝在
- 前端執行回調:
- 瀏覽器解析腳本,執行
handleResponse
函數,獲取數據。
- 瀏覽器解析腳本,執行
3.2 后端代碼
@RestController
public class JsonpController {@GetMapping("/jsonp")public String handleJsonp(@RequestParam String callback // 接收前端傳遞的回調函數名) {// 生成模擬數據Map<String, Object> data = new HashMap<>();data.put("name", "Alice");data.put("age", 25);// 防XSS攻擊:校驗callback參數格式if (!callback.matches("^[a-zA-Z0-9_]+$")) {throw new IllegalArgumentException("Invalid callback parameter");}// 將數據封裝到回調函數中return callback + "(" + new Gson().toJson(data) + ")";}
}
3.3 優缺點對比
優點 | 缺點 |
---|---|
無需服務端配置CORS | 僅支持GET請求 |
兼容性好(支持舊瀏覽器) | 存在XSS風險(需嚴格校驗callback) |
實現簡單 | 不支持復雜認證(如JWT) |
四、解決方案三:Nginx反向代理
4.1 反向代理原理
Nginx 作為反向代理,將客戶端請求轉發到后端服務,使瀏覽器認為請求與當前頁面同源:
- 請求轉發:
- 客戶端請求
http://frontend.com/api/data
- Nginx 將請求轉發到
http://backend.com:3000/data
- 客戶端請求
- 響應頭偽造:
- Nginx 可修改響應頭,如
Access-Control-Allow-Origin
,使瀏覽器認為請求是同源的。
- Nginx 可修改響應頭,如
4.2 配置示例(支持WebSocket與HTTPS)
server {listen 443 ssl;server_name frontend.com;ssl_certificate /path/to/cert.pem;ssl_certificate_key /path/to/key.pem;location /api/ {# 反向代理到后端proxy_pass http://backend:3000;proxy_set_header Host $host;proxy_set_header X-Real-IP $remote_addr;# CORS配置add_header Access-Control-Allow-Origin $http_origin;add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS";add_header Access-Control-Allow-Headers "Authorization";add_header Access-Control-Allow-Credentials "true";# WebSocket支持proxy_http_version 1.1;proxy_set_header Upgrade $http_upgrade;proxy_set_header Connection "upgrade";}# 處理OPTIONS預檢location ~ ^/api/ {if ($request_method = 'OPTIONS') {add_header 'Access-Control-Allow-Origin' '$http_origin';add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS';add_header 'Access-Control-Max-Age' 86400;return 204;}}
}
4.3 安全加固
- 限制來源:
map $http_origin $allowed_origin {default '';~^(http://client\.example\.com|https://another\.domain\.com)$ $http_origin; }server {add_header Access-Control-Allow-Origin $allowed_origin always;if ($allowed_origin = '') {return 403;} }
五、解決方案四:API網關(微服務場景)
5.1 API網關核心原理
API網關作為微服務的統一入口,集中處理跨域、認證、限流等邏輯:
- 集中配置CORS:
- 在網關層統一設置
Access-Control-Allow-Origin
,避免每個微服務重復配置。
- 在網關層統一設置
- 路由與安全策略:
- 根據請求路徑路由到對應服務,同時執行身份驗證(如JWT校驗)。
5.2 Spring Cloud Gateway 實現
// 配置全局CORS
@Bean
public CorsWebFilter corsFilter() {CorsConfiguration config = new CorsConfiguration();config.setAllowedOrigins(List.of("http://client.example.com"));config.setAllowedMethods(List.of("GET", "POST"));config.setAllowedHeaders(List.of("Authorization"));UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();source.registerCorsConfiguration("/**", config);return new CorsWebFilter(source);
}// 動態路由配置
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {return builder.routes().route("user-service", r -> r.path("/api/user/**").filters(f -> f.addRequestHeader("X-Forwarded-Proto", "https")).uri("lb://user-service")).build();
}
5.3 優勢與適用場景
優點 | 適用場景 |
---|---|
集中式管理跨域與安全策略 | 微服務架構、需要統一鑒權的系統 |
支持復雜路由與負載均衡 | 高并發、多服務交互的場景 |
六、解決方案五:代理服務器(Node.js示例)
6.1 代理服務器原理
代理服務器(如Nginx、Node.js)接收客戶端請求,轉發到目標服務,并修改響應頭以繞過跨域限制。
6.2 Node.js實現(http-proxy-middleware)
// proxy.config.js
module.exports = {'/api': {target: 'http://backend.example.com:3000',changeOrigin: true,onProxyReq: (proxyReq, req, res) => {// 動態修改請求頭proxyReq.setHeader('X-Forwarded-For', req.ip);proxyReq.setHeader('X-Real-IP', req.ip);},onProxyRes: (proxyRes, req, res) => {// 添加CORS頭proxyRes.headers['Access-Control-Allow-Origin'] = req.headers.origin;}}
};
6.3 安全建議
- 限制來源:
const allowedOrigins = ['http://client.example.com']; if (!allowedOrigins.includes(req.headers.origin)) {return res.status(403).send('Forbidden'); }
七、解決方案六:服務器端渲染(SSR)
7.1 SSR原理
在服務器端生成完整HTML頁面,直接返回給瀏覽器,避免瀏覽器發起跨域AJAX請求。
7.2 Next.js 實現
// pages/index.js
export async function getServerSideProps() {const res = await fetch('http://api.example.com/data', {headers: {Authorization: 'Bearer YOUR_TOKEN'}});const data = await res.json();return { props: { data } };
}export default function Home({ data }) {return <div>{JSON.stringify(data)}</div>;
}
7.3 優勢與局限
優點 | 局限 |
---|---|
首屏加載快、SEO友好 | 僅適用于靜態或半動態頁面 |
無跨域問題 | 開發復雜度較高 |
八、方案選擇決策樹
場景 | 推薦方案 | 原因 | 技術棧 |
---|---|---|---|
單頁應用(SPA)開發 | Nginx反向代理 / 代理服務器 | 開發與生產環境統一配置,避免前后端分離的復雜性 | Node.js, Nginx |
微服務架構 | API網關統一處理 | 集中式管理,支持動態路由與權限控制 | Spring Cloud Gateway, Kong |
舊項目兼容第三方API | JSONP | 無需后端改造,快速集成 | Vanilla JS |
需要嚴格安全控制 | CORS標準實現 + 白名單 | 細粒度配置,支持所有HTTP方法 | Spring Boot, Express.js |
WebSocket跨域 | Nginx反向代理 + WebSocket支持 | 需要處理Upgrade頭和Connection頭 | Nginx |
服務端渲染(SSR) | 服務器端直接請求 | 避免瀏覽器發起跨域請求 | Next.js, Nuxt.js |
九、常見問題與最佳實踐
9.1 預檢請求(OPTIONS)的深度處理
- 問題:當請求包含自定義頭或使用非簡單方法(如PUT/DELETE)時,瀏覽器會先發送OPTIONS請求。
- 解決方案:
- 在后端顯式返回
Access-Control-Allow-Methods
和Access-Control-Allow-Headers
- 對OPTIONS請求返回204 No Content狀態碼
- 在后端顯式返回
9.1.1 Spring Boot示例
@Configuration
public class CorsConfig implements WebMvcConfigurer {@Overridepublic void addCorsMappings(CorsRegistry registry) {registry.addMapping("/**").allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS").allowedHeaders("Authorization", "Content-Type", "X-Requested-With");}
}
9.2 安全性建議
- 避免使用
*
與allowCredentials
同時開啟:// 錯誤配置 app.use(cors({ origin: '*', credentials: true }));
- 限制
allowedOrigins
為可信域名列表:allowedOrigins: ["http://client.example.com", "https://another-domain.com"]
- 對敏感接口啟用CSRF防護:
app.use(csrf()); app.use((req, res, next) => {res.cookie('XSRF-TOKEN', req.csrfToken());next(); });
十、擴展知識點
10.1 WebSocket跨域解決方案
通過Nginx配置支持WebSocket:
location /ws/ {proxy_pass http://backend-ws.example.com;proxy_http_version 1.1;proxy_set_header Upgrade $http_upgrade;proxy_set_header Connection "upgrade";add_header Access-Control-Allow-Origin $http_origin;
}
10.2 跨域Cookie處理
- 前端設置:
fetch('http://api.example.com', {credentials: 'include' // 允許攜帶Cookie });
- 后端配置:
add_header Set-Cookie "SameSite=None; Secure"; // HTTPS下強制跨域Cookie
十一、總結
跨域問題的解決需要結合項目架構、安全需求與開發效率綜合考量。CORS作為標準方案應優先采用,而Nginx、API網關等則適用于復雜場景。