spring boot 中通過CORS實現跨域
- 2019 年 10 月 26 日
- 筆記
一、跨域問題出現的原因
出現跨域問題的原因是同源策略。
同源策略
主要是三同:同協議、同域名、同埠,
同源策略目的
- 保證用戶資訊安全,防止惡意網站竊取數據。同源策略是必須的,否則cookie可以共享。
同源策略的限制範圍
- cookie、localstorage、indexdb無法讀取。
- DOM無法獲取。
- ajax請求不能發送。
規避策略
- cookie:設置
document.domain
共享cookie。 - DOM獲取:(父子頁面通訊)H5引入了一個API,這個API為windows對象新增了一個
window.postMessage
方法,允許跨窗口通訊,無論這兩個窗口是否同源。 -
window.opener.postMessage(content,origin) content是消息的具體內容,origin是協議 + 域名 + 埠
- AJAX:
- JSONP:JSONP是伺服器無客戶端跨源通訊的常用方法。基本思想是網頁通過添加一個
<script>
元素,向伺服器請求json數據,這種做法不受同源政策的限制,伺服器收到請求後,將數據放在一個指定名字的回調函數裡面傳回來。(只能發GET請求) - WebSocket:WebSocket是一種通訊協議。使用
ws://
(非加密)和wss://
(加密)作為協議前綴。該協議不實行同源政策,只要伺服器支援,就可以通過它進行跨源通訊。 - CORS:詳解如下。
跨域資源共享(corss-origin resource sharing):CORS需要瀏覽器和伺服器同時支援。目前所有瀏覽器都支援該功能,IE瀏覽器不能低於IE10。整個CORS通訊過程,都是瀏覽器自動完成,不需要用戶參與。對於開發者來說,CORS通訊與同源的AJAX通訊沒有差別,程式碼完全一樣。瀏覽器一旦發現AJAX請求跨源,就會自動添加一些附加的頭資訊,有時還會多出一次附加的請求,但用戶不會有感覺。因此,實現CORS通訊的關鍵是伺服器。只要伺服器實現了CORS介面,就可以跨源通訊。
兩種請求
- 瀏覽器將CORS請求分成兩類:簡單請求(simple request)和非簡單請求(not-so-simple request)。
CORS請求
1. 簡單請求
- 對於簡單請求,瀏覽器直接發出CORS請求。具體來說,就是在Header中增加一個
Origin
欄位。如果瀏覽器發現跨源AJAX請求是簡單請求,就自動在頭資訊之中,添加一個Origin
欄位。
GET /cors HTTP/1.1 Origin: http://localhost:8081 //瀏覽器添加欄位,說明本次請求來自哪個源(協議+域名+埠)。 Host: 119.23.214.114 Accept-Language: en-US Connection: keep-alive User-Agent: Mozilla/5.0...
- 如果
Origin
指定的源在不在後端的許可白名單範圍內,伺服器會返回一個正常的http回應。瀏覽器接收後發現,這個response
的Header沒有包含Access-Control-Allow-Origin
欄位,就知道出錯了,從而拋出一個錯誤,被XMLHttpRequest
的onerror
回調函數捕獲。這種錯誤無法通過狀態碼識別,因此HTTP response的狀態碼有可能是200。
2. 非簡單請求
- 非簡單請求是那種對伺服器有特殊要求的請求,比如請求方法是
PUT
或DELETE
,或者Content-Type
欄位的類型是application/json
。 - 非簡單請求的CORS請求,會在正式通訊之前,增加一次HTTP查詢請求,稱為”預檢”請求(preflight)。瀏覽器先詢問伺服器,當前網頁所在的域名是否在伺服器的許可名單之中,以及可以使用哪些HTTP動詞和頭資訊欄位。只有得到肯定答覆,瀏覽器才會發出正式的
XMLHttpRequest
請求,否則就報錯。
二、spring boot 通過CORS解決跨域問題
先創建一個項目server作為伺服器端,默認埠為8080,並在服務端提供兩個介面:
//方便client分別發送不同類型請求的時候可以 觀察一下簡單請求和非簡單請求的區別
@RestController public class HelloController { @GetMapping("gethello") //get 類型public String getHello(){ return "get hello"; } @PutMapping("puthello") //put類型 public String putHello(){ return "put hello"; } }
再創建一個項目client作為請求端,並設置請求端的埠為8081:
server.port=8081
在請求端的resources/static目錄下增加本次訪問所需要的頁面index.html,記得同時自己找一個jquery.min.js 也放到static目錄下:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <script src="jquery.min.js"></script> </head> <body> <div id="div"></div> <input type="button" value="獲取" onclick="gethello()"> //用於測試簡單請求 <input type="button" value="更新" onclick="puthello()"> //用戶測試非簡單請求 <script> function gethello() { $.get('http://localhost:8080/gethello',function (msg) { $("#div").html(msg); //將調用介面返回值在頁面上顯示 }); } function puthello() { $.ajax({ type:'put', url:'http://localhost:8080/puthello', success:function (msg) { $("#div").html(msg); } }) } </script> </body> </html><!DOCTYPE html>
接下來看看跨域失敗的的時候:首先啟動server端,再啟動client端。在瀏覽器中訪問:http://localhost:8081/index.html,點擊獲取按鈕:
然後我們在服務端中通過CORS解決跨域的問題,有兩種解決辦法。
1.在介面類或者方法上面添加 @CrossOrigin註解
@GetMapping("gethello") @CrossOrigin(value = "http://localhost:8081") //指定可以訪問的白名單 public String getHello(){ return "get hello"; }
2.實現WebMvcConfigurer介面重寫addCorsMappings方法
@Configuration
public class WebMvcConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**").allowedOrigins("http://localhost:8081") .allowedMethods("*") .allowedHeaders("*")
.maxAge(30000);
} }
解決了跨域問題後,重啟服務端:再進行獲取和更新操作:
1.獲取操作 成功
2.更新操作
正如前面所說的非簡單請求的CORS請求,會在正式通訊之前,增加一次HTTP查詢請求,稱為”預檢”請求(preflight)。瀏覽器先詢問伺服器,當前網頁所在的域名是否在伺服器的許可名單之中,以及可以使用哪些HTTP動詞和頭資訊欄位。只有得到肯定答覆,瀏覽器才會發出正式的XMLHttpRequest
請求,否則就報錯。