關于跨域介紹
在前後分離的架構下,跨域問題難免會遇見比如,站點 http://domain-a.com 的某 HTML 頁面通過 的 src 請求 http://domain-b.com/image.jpg。網絡上的許多頁面都會加載來自不同域的CSS樣式表,圖像和腳本等資源。
出于安全原因,浏覽器限制從腳本内發起的跨源HTTP請求。 例如,XMLHttpRequest和Fetch API遵循同源政策。 這意味着使用這些API的Web應用程式隻能從加載應用程式的同一個域請求HTTP資源,除非使用CORS頭檔案。
跨域的展現,在于它的域名不同或者端口不同
CORS(跨域源資源共享)(CORS,Cross-origin resource sharing)
CORS是一個 W3C 标準,它是一份浏覽器技術的規範,提供了 Web 服務從不同網域傳來沙盒腳本的方法,以避開浏覽器的同源政策,這是 JSONP 模式的現代版。
情景複現
建兩個普通的 Spring Boot 項目,一個為 provider 提供服務,一個為 consumer 消費服務,第一個配置端口為 8080,第二個配置配置為 8081,然後在 provider 上提供兩個 hello 接口,一個 get,一個 post,如下:
@RestController
public class HelloController {
@GetMapping("/hello")
public String hello() {
return "hello";
}
@PostMapping("/hello")
public String hello2() {
return "post hello";
}
}
在 consumer 的 resources/static 目錄下建立一個 html 檔案,發送一個簡單的 ajax 請求,如下:
<div id="app"></div>
<input type="button" onclick="btnClick()" value="get_button">
<input type="button" onclick="btnClick2()" value="post_button">
<script>
function btnClick() {
$.get('http://localhost:8080/hello', function (msg) {
$("#app").html(msg);
});
}
function btnClick2() {
$.post('http://localhost:8080/hello', function (msg) {
$("#app").html(msg);
});
}
</script>
分别啟動兩個項目,發送請求按鈕,觀察浏覽器控制台如下:
Access to XMLHttpRequest at 'http://localhost:8080/hello' from origin 'http://localhost:8081' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
由于同源政策的限制,請求無法發送成功。
解決方案
使用 CORS 可以在前端代碼不做任何修改的情況下,實作跨域。在 provider 中配置。
@CrossOrigin 注解配置(不推薦)
注解配置某一個方法接受某一個域的請求,如下:
@RestController
public class HelloController {
@CrossOrigin(value = "http://localhost:8081",maxAge = 3600) //origin="*"代表所有域名都可通路 //maxAge飛行前響應的緩存持續時間的最大年齡,簡單來說就是Cookie的有效期 機關為秒
//若maxAge是負數,則代表為臨時Cookie,不會被持久化,Cookie資訊儲存在浏覽器記憶體中,浏覽器關閉Cookie就消失
@GetMapping("/hello")
public String hello() {
return "hello";
}
@CrossOrigin(value = "http://localhost:8081")
@PostMapping("/hello")
public String hello2() {
return "post hello";
}
}
這個注解表示這兩個接口接受來自 http://localhost:8081 位址的請求,配置完成後,重新開機 provider ,再次發送請求,浏覽器控制台就不會報錯,consumer 也能拿到資料。
此時浏覽器請求網絡控制台,可以看到響應頭中多了如下資訊:
這個表示服務端願意接收來自 http://localhost:8081 的請求,拿到這個資訊後,浏覽器就不會再去限制本次請求的跨域了。
全局配置
全局配置隻需要在 SpringMVC 的配置類中重寫 addCorsMappings 方法即可,如下:
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("http://localhost:8081")
.allowedMethods("*") //.allowedMethods("PUT", "DELETE","POST","GET")
.allowedHeaders("*").maxAge(3600);
}
}
/** 表示本應用的所有方法都會去處理跨域請求
allowedMethods 表示允許通過的請求數
allowedHeaders 則表示允許的請求頭
過濾器解決跨域
可以把之前的三個方法的注解或者@Configuration都去掉
@Configuration
public class CrosFilter {
@Bean
CorsFilter getCorsFilter(){
CorsConfiguration cors = new CorsConfiguration();
cors.setAllowCredentials(true);
cors.addAllowedMethod("*");
cors.addAllowedOrigin("*");
cors.setMaxAge(3600L);
cors.addAllowedHeader("*");
cors.applyPermitDefaultValues();
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**",cors);
CorsFilter corsFilter = new CorsFilter(source);
return corsFilter;
}
}
存在的問題
了解了整個 CORS 的工作過程之後,我們通過 Ajax 發送跨域請求,雖然使用者體驗提高了,但是也有潛在的威脅存在,常見的就是 CSRF(Cross-site request forgery)跨站請求僞造。跨站請求僞造也被稱為 one-click attack 或者 session riding,通常縮寫為 CSRF 或者 XSRF,是一種挾制使用者在目前已登入的 Web 應用程式上執行非本意的操作的攻擊方法,舉個例子:
假如一家銀行用以運作轉賬操作的URL位址如下:http://icbc.com/aa?bb=cc,那麼,一個惡意攻擊者可以在另一個網站上放置如下代碼: ,如果使用者通路了惡意站點,而她之前剛通路過銀行不久,登入資訊尚未過期,那麼她就會遭受損失。
基于此,浏覽器在實際操作中,會對請求進行分類,分為簡單請求,預先請求,帶憑證的請求等,預先請求會首先發送一個 options 探測請求,和浏覽器進行協商是否接受請求。預設情況下跨域請求是不需要憑證的,但是服務端可以配置要求用戶端提供憑證,這樣就可以有效避免 csrf 攻擊。