一、同源策略
【前言】
在開始實際操作之前,先來聊聊什麼是同源策略?許多人都對跨域有一定的誤解,認爲這是前端工程師的問題,和後端沒說什麼關係,其實並不是的,說到跨域,就不得不說一下瀏覽器的同源策略。
【簡介】
同源策略是由美國Netscape(網景)公司提出的一個著名的安全策略,它是目前市面上的瀏覽器最核心也是最基本的安全功能,現在所有支持JavaScript的瀏覽器都會使用這個策略。所謂同源是指協議、域名、端口都要相同。同源策略是基於安全層面而提出來了,其實這個策略本身並沒有什麼問題,但是開發者在開發中由於各種原因會存在跨域的需求。例如:一個大型項目由多個公司來完成,不同公司開發的項目域名和端口一般不同,但是各個公司之間又有數據交互的需求,這時就會存在跨域的需求。
【歷程】
傳統的跨域解決方案是JSONP
,JSONP雖然能解決跨域,但是它有一個很大的弊端,就是它只支持GET
請求,不支持其他類型的請求,但是我們平時常用的還有POST
請求,那這種解決方案肯定不行。
而今天我們說的CORS
(跨域源資源共享)(CORS,Cross-origin resource sharing)是一個W3C標準,它是一份瀏覽器技術的規範,提供了Web服務從不同網域傳來沙盒腳本的方法,以避開瀏覽器的同源策略,這是JSONP模式的現代版。
在Spring框架中,對於CORS
也提供瞭解決方案,今天來說說SpringBoot中如何實現CORS。
二、舉例
【第一步】先創建兩個SpringBoot項目,一個命名爲cors1提供服務,另一個命名爲cors2消費服務,第一個默認端口8080即可,第二配置端口爲8081,然後在cors1上新建一個Controller,並提供一個get接口,如下:
【cors1 -> HelloController.java】
@RestController
public class HelloController {
@GetMapping("/get")
public String getData() {
return "hello get cors";
}
}
【cors2 -> application.properties】
server.port=8081
在cors2項目下的resources/static
目錄創建一個index.html文件,發送一個簡單的get請求,如下:
【cors2 -> index.html】
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="js/jquery-3.3.1.js"></script>
</head>
<body>
<div id="result"></div>
<input type="button" value="GET" onclick="getData()">
<script>
function getData() {
$.get('http://localhost:8080/doGet',function (msg) {
$("#result").html(msg);
})
}
</script>
</body>
</html>
注:記得引入jquery文件。
然後,分別啓動兩個項目,打開瀏覽器訪問http://localhost:8081/index.html
,按F12打開瀏覽器控制檯,點擊GET
按鈕,注意觀察紅色提示,如下:
Access to XMLHttpRequest at 'http://localhost:8080/doGet' from origin 'http://localhost:8081' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
可以看到,由於同源策略,所以該請求無法發送成功。
使用CORS可以在前端代碼不做任何修改的前提下實現跨域,接下來我們應該在cors1項目中如何進行配置?在GET請求的方法上,添加@CrossOrigin
註解,配置某一個方法接收某一個域的請求,如下:
@RestController
public class HelloController {
@GetMapping("/doGet")
@CrossOrigin(origins = "http://localhost:8081")
public String getData() {
return "hello get cors";
}
}
添加了這個註解的接口,表示接受來自http://localhost:8081
地址的請求,重啓cors1項目,打開瀏覽器訪問,這次不會出現前面的錯誤了,cors2也能拿到數據了,如下:
這個表示服務端允許接收來自http://localhost:8081
的請求,拿到該信息後,瀏覽器就不會再去限制本次請求的跨域了。
【問題一】
在cors1項目中,如果每個請求對應一個接口,那每個方法上都要添加@CrossOrigin
註解,那太麻煩了吧???
【方法一】
但是,在SpringBoot中,可以全局配置一次性來解決這個問題,那就是把這個@CrossOrigin
註解放到這個類上即可,如下:
@RestController
@CrossOrigin(origins = "http://localhost:8081")
public class HelloController {
@GetMapping("/doGet")
public String getData() {
return "hello get cors";
}
}
爲啥這個註解可以加到類上呢?你爲什麼不早說?
【@CrossOrigin
源碼解讀】
下面,我們來看看源碼,我們進入這個@CrossOrigin
註解中看看,如下:
看到上面箭頭沒有,這就是爲啥,說明這個註解在類和方法上都能生效。
【問題二】
在cors1項目中,如果每個請求都對應一個類中的某個方法呢?那我是不是每個類都得添加@CrossOrigin
註解,也很麻煩啊???
【方法二】
其實,可以用一個配置類來解決這個問題,這個全局配置只需要在配置類中重寫addCorsMappings
方法即可,如下:
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**").allowedOrigins("http://localhost:8081")
.allowedHeaders("*")
.allowedMethods("*")
.maxAge(30*1000);
}
}
/**
表示本應用的的所有方法都會去處理跨域請求,allowedOrigins
表示允許處理跨域請求的協議、域名、端口,allowedHeaders
表示允許的請求頭,allowedMethods
表示允許的請求類型(如:GET、POST、PUT等),maxAge
表示有效時間(單位:ms,如:發送PUT請求時,第一次發送PUT請求會發送兩次請求,先發送一次探測請求,這時maxAge
方法就有作用了,如果在有效時間內完成,會再發一次正式的PUT請求)。
【小結】
綜上所述,經過這麼一次全局性的配置,就不需要在每個類,甚至每個方法上單獨進行配置了。
三、存在的問題
瞭解整個CORS工作過程後,我們通過Ajax發送跨域請求,雖然使得用戶體驗提高了,但同時也伴隨着其他的風險,比較常見的就是:CSRF(Cross-site request forgery)跨站請求僞造。跨站請求僞造也被稱爲one-click attack 或者 session riding,通常縮寫爲CSRF或者XSRF,是一種挾制用戶在當前已登錄的Web應用程序上執行非本意的操作的攻擊方法,舉例:
假如一家銀行用以運行轉賬操作的URL地址如下:https://icbc.com/accounts?name=xxx,那麼,一個惡意攻擊者可以在另一個網站上放置如下代碼:<imgsrc=“https://icbc.com/accounts?name=xxx”>,如果用戶無意中訪問了惡意站點,而她之前剛訪問過銀行不久,登錄信息尚未過期,那麼她就會遭受損失。
基於上述情況,瀏覽器在實際操作中,會對請求進行分類,分爲簡單請求,預先請求,帶憑證的請求等,預先請求會首先發送一個options探測請求(PUT請求就是如此),和瀏覽器進行協商是否接受請求。默認情況下跨域請求是不需要憑證的,但是服務端可以配置要求客戶端提供憑證,這樣就可以有效避免CSRF攻擊。