首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 綜合教學

Tomcat 跨域訪問 (CORS) 完美解決:Filter 實現 Access-Control-Allow-Origin

     編輯:Java 綜合教學

Tomcat 跨域訪問 (CORS) 完美解決:Filter 實現 Access-Control-Allow-Origin

前後端分離架構下,瀏覽器同源策略導致跨域請求被攔截。本文提供生產級 Servlet Filter 方案,在 Tomcat 級別統一處理 CORS 響應頭,徹底解決 Access-Control-Allow-Origin 配置失效、多頭配置混亂等問題。

核心方案:CORS Filter

1. 實現 Filter 接口

package com.example.filter;

import javax.servlet.*;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class CorsFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        // 可讀取初始化參數:允許的域、方法、頭等
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) 
            throws IOException, ServletException {
        HttpServletResponse httpResponse = (HttpServletResponse) response;
        
        // 核心響應頭設置
        httpResponse.addHeader("Access-Control-Allow-Origin", "*"); // 或具體域
        httpResponse.addHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS, HEAD");
        httpResponse.addHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, Authorization");
        httpResponse.addHeader("Access-Control-Allow-Credentials", "true"); // 支持 Cookie
        httpResponse.addHeader("Access-Control-Max-Age", "3600"); // 預檢請求緩存 1小時
        
        // 處理預檢請求 (OPTIONS) - 直接返回 200
        if ("OPTIONS".equalsIgnoreCase(((HttpServletRequest) request).getMethod())) {
            httpResponse.setStatus(HttpServletResponse.SC_OK);
            return;
        }
        
        chain.doFilter(request, response);
    }

    @Override
    public void destroy() {
        // 清理資源
    }
}

2. web.xml 配置 (全局攔截)

<filter>
    <filter-name>CorsFilter</filter-name>
    <filter-class>com.example.filter.CorsFilter</filter-class>
    <init-param>
        <param-name>allowedOrigins</param-name>
        <param-value>https://app.example.com,https://admin.example.com</param-value>
    </init-param>
</filter>
<filter-mapping>
    <filter-name>CorsFilter</filter-name>
    <url-pattern>/*</url-pattern> <!-- 攔截所有請求 -->
</filter-mapping>

3. Spring Boot / Spring MVC 替代方案 (推薦)

若使用 Spring 生態,優先使用框架內置支持,避免重複造輪子:

// Spring MVC 配置類
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                .allowedOrigins("https://app.example.com")
                .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
                .allowedHeaders("*")
                .allowCredentials(true)
                .maxAge(3600);
    }
}

// 或 Spring Boot 2.4+ 全局配置
@Bean
public WebMvcConfigurer corsConfigurer() {
    return new WebMvcConfigurer() {
        @Override
        public void addCorsMappings(CorsRegistry registry) {
            registry.addMapping("/**").allowedOrigins("*").allowedMethods("*").allowedHeaders("*");
        }
    };
}

// 或 Controller 級別精細控制
@CrossOrigin(origins = "https://app.example.com", allowCredentials = "true")
@RestController
public class ApiController { ... }

進階配置:動態域名驗證 (生產環境必備)

Access-Control-Allow-Origin: * 與 Access-Control-Allow-Credentials: true 不能共存 (瀏覽器拒絕)。生產環境需動態校驗 Origin 請求頭:

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) 
        throws IOException, ServletException {
    HttpServletRequest req = (HttpServletRequest) request;
    HttpServletResponse res = (HttpServletResponse) response;
    
    String origin = req.getHeader("Origin");
    // 白名單校驗 (可從配置中心/數據庫加載)
    Set allowedOrigins = Set.of("https://app.example.com", "https://admin.example.com");
    
    if (allowedOrigins.contains(origin)) {
        res.addHeader("Access-Control-Allow-Origin", origin); // 返回具體域而非 *
        res.addHeader("Access-Control-Allow-Credentials", "true");
    }
    
    res.addHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
    res.addHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, Authorization");
    res.addHeader("Access-Control-Max-Age", "3600");
    
    if ("OPTIONS".equalsIgnoreCase(req.getMethod())) {
        res.setStatus(HttpServletResponse.SC_OK);
        return;
    }
    
    chain.doFilter(request, response);
}

為什麼不推薦 Tomcat server.xml 配置?

方式缺點
server.xml <Valve> / <Filter>容器級配置,耦合部署環境,難以動態化、項目隔離、版本控制
web.xml 內置 CorsFilter (Tomcat 7.0.41+)配置繁瑣,不支持動態域名、Spring 整合差
應用級 Filter (本文方案)✅ 代碼可控、動態配置、Spring 兼容、項目隔離、單元測試友好
Spring 框架內置 CORS✅ 最佳實踐:聲明式、統一管理、支持預檢、與 Security 整合

常見坑點排查清單

  • ❌ Access-Control-Allow-Origin: * + Allow-Credentials: true → 瀏覽器報錯
  • ❌ 預檢請求 (OPTIONS) 被攔截器/安全框架攔截 → 403/401 → CORS 失效
  • ❌ 非簡單請求 (自定義 Header、JSON Content-Type) 未觸發預檢 → 後台無響應頭
  • ❌ 多層 Filter/Interceptor 順序問題 → CORS 頭被覆蓋/遺漏
  • ❌ Nginx/網關層也配置 CORS → 雙重頭衝突

最佳實踐總結

  1. Spring 項目:直接用 WebMvcConfigurer / @CrossOrigin,零侵入
  2. 非 Spring / 舊項目:應用級 CorsFilter,配置動態域名白名單
  3. 生產環境:禁用 *,嚴格白名單,分離靜態資源域 (CDN) 與 API 域
  4. 微服務:網關層 (Gateway/Zuul/Nginx) 統一處理 CORS,下游服務無感
  5. 調試利器:瀏覽器 Network 面板查看 Origin 請求頭與 Access-Control-Allow-* 響應頭,curl -H "Origin: xxx" -v 驗證

徹底搞定 CORS,關鍵在於理解預檢機制、區分簡單/非簡單請求、正確處理憑證模式、選對配置層級。拒絕在 server.xml 硬編碼,擁抱應用級可控方案。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved