做備案的網(wǎng)站推廣平臺網(wǎng)站
Springboot攔截器中跨域失效的問題
一、概述
1、具體場景
起因:
- 同一個接口,傳入不同參數(shù)進(jìn)行值的修改時,一個成功,另一個竟然失敗,而且是跨域問題
- 攔截器內(nèi)的request參數(shù)調(diào)用getHeader方法時,獲取不到前端設(shè)置的請求頭,且瀏覽器顯示有,但是后端輸出后只有對于的key,而且key變成了
access-control-request-headers
的value
同一個接口不同參數(shù)錯誤展示:
前端代碼展示:
瀏覽器請求頭顯示:
后端獲取request的header參數(shù)顯示:
全是null
輸出headers:
{sec-fetch-mode=cors, referer=http://localhost:8080/, sec-fetch-site=cross-site, accept-language=zh-CN,zh;q=0.9, origin=http://localhost:8080, access-control-request-method=POST, accept=*/*, host=127.0.0.1:8099, access-control-request-headers=content-type,headeruserid,headerusertoken, connection=keep-alive, accept-encoding=gzip, deflate, br, user-agent=Mozilla/5.0 (Linux; Android 8.0.0; SM-G955U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Mobile Safari/537.36, sec-fetch-dest=empty}
變成了這樣:access-control-request-headers=content-type,headeruserid,headerusertoken,
2、背景
前端:
- 是個uniapp項目,只會調(diào),不會寫,未設(shè)置跨域
后端:
- spring boot項目
- 后端使用了
@CrossOrigin(origins = "*")
,進(jìn)行了簡單的跨域設(shè)置 - 后端使用了攔截器進(jìn)行攔截認(rèn)證
3、嘗試改bug
發(fā)現(xiàn)前端的參數(shù)key,瀏覽器的參數(shù)key和后端的參數(shù)key大小寫不一致:
- 修改了多次,且嘗試了多次,無效果
String userId = request.getHeader("headerUserId");
String userId2 = request.getHeader("HeaderUserId");
String userId3 = request.getHeader("Headeruserid");
嘗試前端添加跨域:
- 統(tǒng)一設(shè)置跨域請求頭,不會,只會小改
- 前端添加:Access-Control-Allow-Origin: *,無效,后面認(rèn)真看才發(fā)現(xiàn)這是響應(yīng)頭,不是請求頭,sha唄了
嘗試后端的攔截器內(nèi)添加@CrossOrigin(origins = “*”)、具體攔截方法內(nèi)給響應(yīng)參數(shù)添加響應(yīng)頭:
- 無效
重啟前端項目、清除瀏覽器緩存、清除idea緩存、rebuild項目、重新運(yùn)行:
- 無效
二、解決辦法
試了很多方法,慢慢的就定位了問題:
- 前端設(shè)置的請求頭,瀏覽器可以接收,而且具體顯示,那就不是前端的問題
- 后端試了很多次,攔截器獲取的request header 的key和value還是null
- 如果取消攔截器,正??梢垣@取
- 那么可能是攔截器的問題,我的
@CrossOrigin(origins = "*")
加在我的接口上,但是攔截器先執(zhí)行,如果沒用通過那么直接返回,根本到不了我的接口,也就到不了我接口上的@CrossOrigin(origins = "*")
,那就沒用跨域了 - 但是我嘗試再攔截器內(nèi)的方法中手動給response響應(yīng)添加跨域的代碼,如下,但是還是無效
// 支持跨域
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Methods",
"GET,POST,PUT,DELETE,OPTIONS");
response.setHeader("Access-Control-Allow-Credentials", "true");
response.setHeader("Access-Control-Allow-Headers", "Content-Type,X-Token");
response.setHeader("Access-Control-Allow-Credentials", "true");
后面查詢跨域的請求流程:
跨域請求的流程通常分為兩個階段:預(yù)檢請求(Preflight Request)和實際請求。以下是跨域請求的一般流程:
-
預(yù)檢請求階段:
- 當(dāng)瀏覽器檢測到跨域請求時(例如請求方法不是簡單請求方法、請求包含自定義的請求頭等),會首先發(fā)送一個預(yù)檢請求(OPTIONS請求)給服務(wù)器。
- 預(yù)檢請求的目的是詢問服務(wù)器是否允許實際請求中包含特定的自定義請求頭字段和請求方法。
- 預(yù)檢請求會包含一些特定的請求頭,如
Access-Control-Request-Method
和Access-Control-Request-Headers
,用來詢問服務(wù)器的允許范圍。 - 服務(wù)器收到預(yù)檢請求后,根據(jù)預(yù)檢請求中的信息判斷是否允許實際請求,然后發(fā)送適當(dāng)?shù)腃ORS響應(yīng)頭給瀏覽器。
-
實際請求階段:
- 如果預(yù)檢請求得到了服務(wù)器的允許(即服務(wù)器返回了合適的CORS響應(yīng)頭),瀏覽器將發(fā)送實際的請求給服務(wù)器。
- 實際請求中包含了正常的請求方法(例如GET、POST、PUT等)、請求頭和請求體等信息。
- 服務(wù)器收到實際請求后,會處理請求并返回相應(yīng)的響應(yīng)給瀏覽器。
下圖展示了跨域請求的流程:
+-------------+ +-------------+| Browser | | Server |+-------------+ +-------------+| || 1. 發(fā)送預(yù)檢請求 |+----------------------->|| || 2. 接收預(yù)檢響應(yīng) ||<-----------------------+| || 3. 發(fā)送實際請求 |+----------------------->|| || 4. 接收實際響應(yīng) ||<-----------------------+
總的來說,跨域請求的流程就是瀏覽器先發(fā)送預(yù)檢請求詢問服務(wù)器是否允許跨域請求,然后根據(jù)服務(wù)器的響應(yīng)決定是否發(fā)送實際請求。如果預(yù)檢請求得到了服務(wù)器的允許,瀏覽器才會發(fā)送實際的請求。
跟著這個OPTIONS請求查找:
發(fā)現(xiàn),只需要我把這個請求過濾掉即可,讓它可以實際請求,使得我的自定義請求頭 - 特定的請求頭(access-control-request-headers=content-type,headeruserid,headerusertoken)可以接收到我就可以進(jìn)行判斷了。
if ("OPTIONS".equals(request.getMethod().toUpperCase())) {return true;
}
有效果,解決了。
三、拓展
此處是使用的@CrossOrigin(origins = "*")
注解同時過濾掉OPTIONS
請求實現(xiàn)了跨域
還可以通過只設(shè)置一個跨域過濾器解決跨域問題:
下列方法轉(zhuǎn)載于博客園作者小泉哥:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;/*** 全局跨域配置類* 跨域請求的配置,允許所有來源的跨域請求* * 跨域請求流程:* 1. 瀏覽器發(fā)送預(yù)檢請求(OPTIONS請求)給服務(wù)器,詢問是否允許實際請求中包含特定的自定義請求頭字段和請求方法。* 2. 服務(wù)器根據(jù)預(yù)檢請求的信息判斷是否允許實際請求,發(fā)送適當(dāng)?shù)腃ORS響應(yīng)頭給瀏覽器。* 3. 如果預(yù)檢請求得到了服務(wù)器的允許,瀏覽器發(fā)送實際的請求給服務(wù)器。* 4. 服務(wù)器收到實際請求后,處理請求并返回相應(yīng)的響應(yīng)給瀏覽器。* * 注:當(dāng)設(shè)置allowCredentials為true時,Access-Control-Allow-Origin響應(yīng)頭不能使用通配符"*",而是必須明確指定允許的來源。* * @author red-velvet* @since 2024/2/8*/
@Configuration
public class GlobalCorsConfig {/*** 配置CorsFilter* @return CorsFilter*/@Beanpublic CorsFilter corsFilter() {// 創(chuàng)建CorsConfiguration對象,配置CORS跨域規(guī)則CorsConfiguration config = new CorsConfiguration();// 允許所有來源的跨域請求config.addAllowedOrigin("*");// 允許攜帶憑據(jù)(例如Cookie)config.setAllowCredentials(false);// 允許所有請求方法的跨域請求config.addAllowedMethod("*");// 允許所有請求頭的跨域請求config.addAllowedHeader("*");// 創(chuàng)建UrlBasedCorsConfigurationSource對象,注冊CORS配置UrlBasedCorsConfigurationSource configSource = new UrlBasedCorsConfigurationSource();configSource.registerCorsConfiguration("/**", config);// 創(chuàng)建CorsFilter對象,傳入配置源return new CorsFilter(configSource);}
}
dCorsConfigurationSource configSource = new UrlBasedCorsConfigurationSource();configSource.registerCorsConfiguration("/**", config);// 創(chuàng)建CorsFilter對象,傳入配置源return new CorsFilter(configSource);}
}