今天和小伙伴們來聊一聊通過CORS解決跨域問題。
同源策略
很多人對跨域有一種誤解,以為這是前端的事,和后端沒關系,其實不是這樣的,說到跨域,就不得不說說瀏覽器的同源策略。
同源策略是由 Netscape 提出的一個著名的安全策略,它是瀏覽器最核心也最基本的安全功能,現在所有支持 JavaScript 的瀏覽器都會使用這個策略。所謂同源是指協議、域名以及端口要相同。同源策略是基於安全方面的考慮提出來的,這個策略本身沒問題,但是我們在實際開發中,由於各種原因又經常有跨域的需求,傳統的跨域方案是 JSONP,JSONP 雖然能解決跨域但是有一個很大的局限性,那就是只支持 GET 請求,不支持其他類型的請求,而今天我們說的 CORS(跨域源資源共享)(CORS,Cross-origin resource sharing)是一個 W3C 標准,它是一份瀏覽器技術的規范,提供了 Web 服務從不同網域傳來沙盒腳本的方法,以避開瀏覽器的同源策略,這是 JSONP 模式的現代版。
在 Spring 框架中,對於 CORS 也提供了相應的解決方案,今天我們就來看看 SpringBoot 中如何實現 CORS。
實踐
接下來我們就來看看 Spring Boot 中如何實現這個東西。
首先創建兩個普通的 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")
@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
的請求,拿到這個信息后,瀏覽器就不會再去限制本次請求的跨域了。
provider 上,每一個方法上都去加注解未免太麻煩了,有的小伙伴想到可以講注解直接加在 Controller 上,不過每個 Controller 都要加還是麻煩,在 Spring Boot 中,還可以通過全局配置一次性解決這個問題,全局配置只需要在 SpringMVC 的配置類中重寫 addCorsMappings 方法即可,如下:
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("http://localhost:8081")
.allowedMethods("*")
.allowedHeaders("*");
}
}
/**
表示本應用的所有方法都會去處理跨域請求,allowedMethods 表示允許通過的請求數,allowedHeaders 則表示允許的請求頭。經過這樣的配置之后,就不必在每個方法上單獨配置跨域了。
存在的問題
了解了整個 CORS 的工作過程之后,我們通過 Ajax 發送跨域請求,雖然用戶體驗提高了,但是也有潛在的威脅存在,常見的就是 CSRF(Cross-site request forgery)跨站請求偽造。跨站請求偽造也被稱為 one-click attack 或者 session riding,通常縮寫為 CSRF 或者 XSRF,是一種挾制用戶在當前已登錄的 Web 應用程序上執行非本意的操作的攻擊方法,舉個例子:
假如一家銀行用以運行轉賬操作的URL地址如下:
http://icbc.com/aa?bb=cc
,那么,一個惡意攻擊者可以在另一個網站上放置如下代碼:<img src="http://icbc.com/aa?bb=cc">
,如果用戶訪問了惡意站點,而她之前剛訪問過銀行不久,登錄信息尚未過期,那么她就會遭受損失。
基於此,瀏覽器在實際操作中,會對請求進行分類,分為簡單請求,預先請求,帶憑證的請求等,預先請求會首先發送一個 options 探測請求,和瀏覽器進行協商是否接受請求。默認情況下跨域請求是不需要憑證的,但是服務端可以配置要求客戶端提供憑證,這樣就可以有效避免 csrf 攻擊。
好了,這個問題就說這么多,關於 Spring Boot 中的 CORS ,松哥還有一個小小的視頻教程
關注公眾號【江南一點雨】,專注於 Spring Boot+微服務以及前后端分離等全棧技術,定期視頻教程分享,關注后回復 Java ,領取松哥為你精心准備的 Java 干貨!