No ‘Access-Control-Allow-Origin’ header: 跨域問題踩坑記錄

前言

前兩周在伺服器上部署一個系統時,遇到了跨域問題,這也不是第一次遇到跨域問題了,本來以為解決起來會很順利,沒想到解決過程中遇到了很多坑,所以覺得有必要寫一篇部落格記錄一下這個坑。

問題產生原因

本來我們組的應用都有一個統一的網關服務進行介面請求轉發,相關的配置都做好了,並不存在跨域問題。但前兩周因為業務拓展,需要將部分應用拆分出來,部署到其他伺服器上,這裡面就包含我負責的兩個應用。

其中一個應用,因為是前後端不分離的項目,不存在跨域問題,所以部署起來比較順利,直接打了一個jar包丟到伺服器上面就順利跑起來了。另一個項目則是前後端分離的項目,打jar包部署的過程倒時挺順利的,可是當我把前端用nginx跑起來之後,卻看到了類似下圖這樣一堆報錯:

這一看就是跨域問題了。

解決過程

1、修改nginx配置(不起作用)

因為上一次遇到跨域問題,我就是通過nginx配置代理轉發解決的,所以這次我首先想到的還是通過修改nginx配置來解決這個問題,所以我在nginx配置文件中添加了類似這樣的配置:

server{
    listen 8888;
    server_name  192.168.1.100;

    location /{
        proxy_pass //192.168.1.100:8080;
    }

    location /api{
        proxy_pass //192.168.1.100:8081; // 以api開頭的介面請求,全部轉發到這裡
}
}

然而,並不起作用。之後,我又在nginx配置文件中添加了一些關於請求頭的設置,如下所示:

add_header 'Access-Control-Allow-Headers' '*';
add_header 'Access-Control-Allow-Methods' 'GET,POST,OPTIONS,HEAD,PUT';
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Credentials' 'true'

同樣的,也沒有解決問題。到這裡,我暫時放棄通過修改nginx配置文件來解決跨域問題的想法了。

2、在後端程式碼中添加跨域處理配置

關於如何在後端解決跨域問題,我之前也了解過,查了一下網上資料後,在程式碼中添加了一下兩個配置類:

WebMvcConfig:

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

/**
 * 全局處理介面跨域
 */
@Configuration
public class WebMvcConfig extends WebMvcConfigurerAdapter {
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
//                .allowedOrigins("*")
                // 這裡網上的資料大部分說的是.allowedOrigins("*"),但因為我的springboot版本是2.5.5,
                // allowCredentials為true時並且allowedOrigins不為空且為ALL(這個ALL就是*)時就會拋出異常
                // 所以這裡我設置的是.allowedOriginPatterns("*")
                .allowedOriginPatterns("*")
                .allowedMethods("POST", "GET", "PUT", "OPTIONS", "DELETE")
                .maxAge(3600)
                .allowCredentials(true);
    }
}

CORSFilter:

import org.springframework.stereotype.Component;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * 全局處理介面跨域
 */
@Component
public class CORSFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        Filter.super.init(filterConfig);
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse res = (HttpServletResponse) response;
        res.setHeader("Access-Control-Allow-Origin", "*");
        res.setHeader("Access-Control-Allow-Credentials", "true");
        res.setHeader("Access-Control-Allow-Methods", "GET,HEAD,OPTIONS,POST,PUT");
        res.setHeader("Access-Control-Allow-Headers", "*");
        chain.doFilter(request, response);
    }

    @Override
    public void destroy() {
        Filter.super.destroy();
    }

}

本來以為加上了這兩個配置類,這把絕對沒問題了,可惜,問題依然沒有解決。

但這次部署上去之後,報錯資訊發生了變化,提示某些請求頭(如client_id)沒能成功跨域,這也給了我一些啟發。

3、最終的解決方案

最後的最後,我將CORSFilter配置類中的:

res.setHeader("Access-Control-Allow-Headers", "*");

修改為:

res.setHeader("Access-Control-Allow-Headers", "client_id, Authorization, Access-Control-Allow-Headers, Origin, Accept, X-Requested-With, Content-Type, Access-Control-Request-Method, Access-Control-Request-Headers");

然後,打包部署上去,總算解決了跨域問題。

總結

這次問題解決後,我又查了一些資料,發現了以下兩個知識點:

1、Access-Control-Allow-Origin 請求頭的設置是有一些特殊限制的,當 Access-Control-Allow-Credentials 的值為 true 時會導致Access-Control-Allow-Origin 無法被設置為「*」。

2、因為我的springboot版本較新,所以網上通用的很多跨域配置對我不太適用。比如 Access-Control-Allow-Headers 不能直接設置為 *,而需要將特定的請求頭給明確列出來才可生效。

這次折騰了這麼久,嘗試了網上各種方法都不生效,主要就是因為以上兩點,好在最後總算順利解決了。