個人博客:無奈何楊(wnhyang)
個人語雀:wnhyang
共享語雀:在線知識共享
Github:wnhyang - Overview
參考
Spring Cloud Gateway CORS 方案看這篇就夠了-騰訊雲開發者社區-騰訊雲
九種跨域方式實現原理(完整版) - 掘金
什麼是跨域,有哪些解決方法
以下為AI回答
跨域是指由於瀏覽器的安全機制,當一個頁面嘗試通過 XMLHttpRequest 或 Fetch API 等方式向不同於當前頁面源(協議、域名、端口三者之一不同的 URL)發起請求時,會被瀏覽器阻止,這稱為“同源策略”限制。這是為了防止惡意網站通過腳本對其他網站的數據進行非法訪問。
解決跨域問題的方法有很多種,以下是常見的幾種:
1、CORS(Cross-Origin Resource Sharing)
CORS 是一種 W3C 標準,允許服務器表明哪些其他源可以請求資源。服務器通過設置響應頭(如 Access-Control-Allow-Origin、Access-Control-Allow-Methods、Access-Control-Allow-Headers 等)來允許特定源或者所有源進行跨域請求。客户端(瀏覽器)在接收到響應後判斷是否符合 CORS 規則,若符合則允許數據交互。
2、JSONP(JSON with Padding)
JSONP 是一種非正式傳輸協議,它利用 <script> 標籤不受同源策略限制的特點,通過回調函數的形式來加載跨域數據。JSONP 只支持 GET 請求,並且要求服務器端配合提供 JSONP 形式的響應。
3、代理服務器(Proxy Server)
通過在服務器端設置一個代理,使得所有的請求都先經過這個代理服務器,然後由代理服務器轉發給目標服務器,這樣在瀏覽器看來,始終是在同一個源下進行通信。
4、document.domain + iframe
對於兩個同頂級域名下的頁面,可以通過修改各自的 document.domain 屬性來允許它們之間的通信。
5、window.postMessage()
HTML5 提供了 window.postMessage 方法,允許來自不同源的腳本採用異步方式進行有限的通信,可以在窗口間發送消息。
6、WebSockets
雖然 WebSocket 連接建立之初也會遇到同源策略限制,但它本身提供了機制來處理跨域連接,服務器可以在握手階段設置 Origin 頭部來控制哪些源可以建立連接。
現代 web 開發中,CORS 是最常用的跨域解決方案,因為它支持更廣泛的請求類型(不僅僅是 GET)和更安全的通信模式。然而,在某些特定場景下,如兼容舊版瀏覽器或者簡單的 GET 請求,JSONP 仍然是一個可行的選擇。
Spring Cloud Gateway微服務跨域問題
當我們在網關和子服務都配置了CORS允許特定源的跨域請求時,還是會出現以下問題。
The 'Access-Control-Allow-Origin' header contains multiple values '*, http://localhost:5173', but only one is allowed.
從提示來看,是Access-Control-Allow-Origin頭包含了多個值。
果然,Access-Control-Allow-Origin確實包含了多個值,這就是問題根源,具體的分析可以參考Spring Cloud Gateway CORS 方案看這篇就夠了-騰訊雲開發者社區-騰訊雲文章。
解決方案
既然是重複配置了,那麼去掉一個就行了,如下是我的子服務和網關的配置,我是刪去了子服務的配置。
當然還有一種方法是在response中去除重複header的。
子服務跨域配置
/**
* @author wnhyang
* @date 2024/3/14
**/
@Configuration
@Slf4j
public class WebConfig {
/**
* 跨域配置
*/
@Bean
public CorsFilter corsFilter() {
log.info("[CorsFilter][初始化corsFilter配置]");
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
// 設置訪問源地址
config.addAllowedOriginPattern("*");
// 設置訪問源請求頭
config.addAllowedHeader("*");
// 設置訪問源請求方法
config.addAllowedMethod("*");
// 有效期 1800秒
config.setMaxAge(1800L);
// 添加映射路徑,攔截一切請求
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
// 返回新的CorsFilter
return new CorsFilter(source);
}
}
網關跨域配置
/**
* 跨域配置
*
* @author wnhyang
* @date 2023/8/25
*/
@Component
public class GlobalCorsFilter implements WebFilter, Ordered {
/**
* 這裏為支持的請求頭,如果有自定義的header字段請自己添加
*/
private static final String ALLOWED_HEADERS = "X-Requested-With, Content-Language, Content-Type, Authorization, credential, X-XSRF-TOKEN, isToken, token, Admin-Token, App-Token";
private static final String ALLOWED_METHODS = "GET,POST,PUT,DELETE,OPTIONS,HEAD";
private static final String ALLOWED_ORIGIN = "*";
private static final String ALLOWED_EXPOSE = "*";
private static final String MAX_AGE = "18000L";
@Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
if (CorsUtils.isCorsRequest(request)) {
ServerHttpResponse response = exchange.getResponse();
HttpHeaders headers = response.getHeaders();
headers.add("Access-Control-Allow-Headers", ALLOWED_HEADERS);
headers.add("Access-Control-Allow-Methods", ALLOWED_METHODS);
headers.add("Access-Control-Allow-Origin", ALLOWED_ORIGIN);
headers.add("Access-Control-Expose-Headers", ALLOWED_EXPOSE);
headers.add("Access-Control-Max-Age", MAX_AGE);
headers.add("Access-Control-Allow-Credentials", "true");
if (request.getMethod() == HttpMethod.OPTIONS) {
response.setStatusCode(HttpStatus.OK);
return Mono.empty();
}
}
return chain.filter(exchange);
}
@Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE;
}
}
寫在最後
拙作艱辛,字句心血,望諸君垂青,多予支持,不勝感激。
個人博客:無奈何楊(wnhyang)
個人語雀:wnhyang
共享語雀:在線知識共享
Github:wnhyang - Overview