問(wèn)題來(lái)源
web前后端工程的分離,給傳統(tǒng)的web一體式開(kāi)發(fā)或前后端工程師揉在一塊(前后端代碼在一個(gè)工程下,尤其前端開(kāi)發(fā)需要在本地啟動(dòng)后臺(tái)服務(wù),后臺(tái)也會(huì)被前端代碼分散注意力)的尷尬情況提出了解決方案,同時(shí)也引來(lái)了一些不可避免的問(wèn)題,而跨域問(wèn)題就是其中之一。
設(shè)想一下,前端工程師正在開(kāi)發(fā)登錄頁(yè)面的UI,基于nodejs(或基于nodejs的快速開(kāi)發(fā)工具,如vue-cli),可以很容易的在本地啟動(dòng)一個(gè)服務(wù)端口用于頁(yè)面的開(kāi)發(fā)。此時(shí),前端代碼需要調(diào)用后臺(tái)的登錄接口做測(cè)試,于是將參考API文檔,拿到了登錄接口的相關(guān)信息(url=/login,params={})準(zhǔn)備調(diào)用后臺(tái)接口測(cè)試,這時(shí)問(wèn)題來(lái)了,如何遠(yuǎn)程調(diào)用后臺(tái)接口,肯定不可能只配置一下axios(http工具)的baseUrl就完事,還要找到后臺(tái)的同學(xué),請(qǐng)他將后臺(tái)web服務(wù)設(shè)置允許跨域請(qǐng)求,不然你就會(huì)在瀏覽器控制臺(tái)收到類(lèi)似下面的警告:
OPTIONS http://localhost:8088/login 404
Access to XMLHttpRequest at 'http://localhost:8088/login' from origin 'http://localhost:8080' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: It does not have HTTP ok status.
解決方案
1、讓前端工程師安裝nginx,設(shè)置http請(qǐng)求轉(zhuǎn)發(fā),這樣可以繞過(guò)瀏覽器跨域警告(不推薦,因?yàn)槊恳粋€(gè)前端開(kāi)發(fā)都需要安裝nginx,還需要學(xué)習(xí)配置nginx,透明度不夠)
2、后端服務(wù)開(kāi)啟跨域請(qǐng)求設(shè)置(推薦,對(duì)于前端來(lái)說(shuō)基本是透明的)
spring security如何解決跨域問(wèn)題
環(huán)境說(shuō)明
- java8
- spring boot 2.2.0.RELEASE
官方說(shuō)明
通過(guò)官方文檔可以知道,配置是在spring security中,但是最終還是會(huì)交給spring mvc去處理
spring官方說(shuō)明
繞坑!
最開(kāi)始以為這是很常見(jiàn)的問(wèn)題,應(yīng)該很好解決,于是隨便baidu了一下,的確有一大堆相關(guān)的案例解決方案,基本思路和代碼大概有三種,這里就不詳細(xì)說(shuō)了,請(qǐng)自行百度相關(guān)問(wèn)題。
百度出來(lái)的結(jié)果都大概看了下,也都試過(guò),沒(méi)有一個(gè)可行,不知道是不是我的版本太新的原因。最后只好查看官方文檔(看英文有點(diǎn)慢,需要加強(qiáng)?。?,意外的是,這次官方給出的說(shuō)明很簡(jiǎn)單也很直接(show you the best practice code),但是也沒(méi)有解決問(wèn)題。于是再次結(jié)合百度出來(lái)的結(jié)果,然后分析瀏覽器給出的錯(cuò)誤警告內(nèi)容,將官方代碼稍作了調(diào)整后,跨域配置才終于生效。
code
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private ActiveProfile activeProfile;
@Override
protected void configure(HttpSecurity http) throws Exception {
//你的其他配置
......
//dev config
if (activeProfile.isDev()) {
//允許跨域請(qǐng)求
// by default uses a Bean by the name of corsConfigurationSource(官方說(shuō)明,使下面配置的bean生效)
http.cors(Customizer.withDefaults());
}
}
@Profile("dev")
@Bean
CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.addAllowedOrigin("*");//修改為添加而不是設(shè)置,* 最好改為實(shí)際的需要,我這是非生產(chǎn)配置,所以粗暴了一點(diǎn)
configuration.addAllowedMethod("*");//修改為添加而不是設(shè)置
configuration.addAllowedHeader("*");//這里很重要,起碼需要允許 Access-Control-Allow-Origin
configuration.setAllowCredentials(true);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
}
還沒(méi)有結(jié)束
上述配置沒(méi)有問(wèn)題,到目前為止,服務(wù)器端已經(jīng)完全支持跨域請(qǐng)求了,但是的確還沒(méi)有結(jié)束。應(yīng)為筆者在測(cè)試過(guò)程中又遇到了坑,下面來(lái)具體談一談。
提示:如果你的前端用到了axios,一定注意axios雖然默認(rèn)的content-type='application/x-www-form-urlencoded',但是body中的參數(shù)是以json字符串的格式提交的,請(qǐng)使用 URLSearchParams 封裝,如下:
const params = new URLSearchParams();
params.append('param1', 'value1');
params.append('param2', 'value2');
axios.post('/foo', params);
回到正題,基于上面的配置,現(xiàn)在登錄已經(jīng)沒(méi)有了問(wèn)題,但是在跨域訪問(wèn)api時(shí)會(huì)發(fā)現(xiàn)接口返回302,接口被重定向的原因經(jīng)過(guò)查看spring security的dubg日志發(fā)現(xiàn)是因?yàn)樵摻涌谑悄涿L問(wèn),也就是說(shuō)你未登錄。但是剛才也的確登錄成功了,而且瀏覽器cookie當(dāng)中還有我們的sessionid,再仔細(xì)觀察發(fā)現(xiàn)api接口發(fā)送未帶cookie,這才是接口被解決的真正原因而并非真的沒(méi)有登錄。查看官方對(duì)于跨域的說(shuō)明后,發(fā)現(xiàn)跨域訪問(wèn)默認(rèn)是不會(huì)攜帶cookie的,除非你設(shè)置參數(shù)告訴瀏覽器我要帶cookie去跨域訪問(wèn)資源,代碼如下:
//全局配置,告訴瀏覽器無(wú)論如何都要攜帶cookie去請(qǐng)求資源
axios.defaults.withCredentials=true
或者
//也可以在單獨(dú)的請(qǐng)求顯示指明 withCredentials=true
self.axios({
method: 'post',
url: '/api/getSomething',
data: params,
withCredentials: true//
}).then(function (rep) {
//your code......
});
如果你使用的是webpack或者基于webpack或nodejs構(gòu)建的開(kāi)發(fā)工具(如:vue-cli)
你可以通過(guò)配置代理,這樣的好處是你可以省略對(duì) withCredentials 參數(shù)的配置,例:
//vue.config.js
module.exports = {
devServer: {
proxy: 'http://localhost:4000'
}
}
關(guān)于該配置的詳細(xì)說(shuō)明參考:
http-proxy-middleware