面試突擊81:什麼是跨域問題?如何解決?
ead>我報名參加金石計劃1期挑戰——瓜分10萬獎池,這是我的第2篇文章,點選檢視活動詳情
跨域問題指的是不同站點之間,使用 ajax 無法相互呼叫的問題。跨域問題本質是瀏覽器的一種保護機制,它的初衷是為了保證使用者的安全,防止惡意網站竊取資料。 但這個保護機制也帶來了新的問題,它的問題是給不同站點之間的正常呼叫,也帶來的阻礙,那怎麼解決這個問題呢?接下來我們一起來看。
1.跨域三種情況
在請求時,如果出現了以下情況中的任意一種,那麼它就是跨域請求:
- 協議不同,如 http 和 https;
- 域名不同;
- 埠不同。
也就是說,即使域名相同,如果一個使用的是 http,另一個使用的是 https,那麼它們也屬於跨域訪問。常見的跨域問題如下圖所示:
2.跨域問題演示
接下來,我們使用兩個 Spring Boot 專案來演示跨域的問題,其中一個是埠號為 8080 的前端專案,另一個埠號為 9090 的後端介面專案。
2.1 前端網站
前端專案只需要在 resources 下放兩個檔案,一個用於傳送 ajax 請求的 jquery.js,另一個是 html 前端頁面,工程目錄如下圖所示:
其中前端頁面 index.html 的程式碼如下:
```html
跨域測試
```
2.2 後端介面
後端介面專案首先先在 application.properties 配置檔案中,設定專案的埠號為 9090,如下所示:
properties
server.port=9090
然後建立一個後端控制器,返回一個 JSON 格式的資料,實現程式碼如下:
```java import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.HashMap;
@RestController
public class TestController {
@RequestMapping("/test")
public HashMap
以上兩個專案建立並啟動成功之後,使用前端專案訪問後端介面,因為埠不一樣,所以也屬於跨域訪問,執行結果如下圖所示:
3.解決跨域問題
在 Spring Boot 中跨域問題有很多種解決方案,比如以下 5 個:
- 使用 @CrossOrigin 註解實現跨域;
- 通過配置檔案實現跨域;
- 通過 CorsFilter 物件實現跨域;
- 通過 Response 物件實現跨域;
- 通過實現 ResponseBodyAdvice 實現跨域。
當然如果你願意的話,還可以使用過濾器來實現跨域,但它的實現和第 5 種實現類似,所以本文就不贅述了。
3.1 通過註解跨域
使用 @CrossOrigin 註解可以輕鬆的實現跨域,此註解既可以修飾類,也可以修飾方法。當修飾類時,表示此類中的所有介面都可以跨域;當修飾方法時,表示此方法可以跨域,它的實現如下:
```java import org.springframework.web.bind.annotation.CrossOrigin; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.HashMap;
@RestController
@CrossOrigin(origins = "*")
public class TestController {
@RequestMapping("/test")
public HashMap
以上程式碼的執行結果如下圖所示:
從上圖中可以看出,前端專案訪問另一個後端專案成功了,也就說明它解決了跨域問題。
優缺點分析
此方式雖然雖然實現(跨域)比較簡單,但細心的朋友也能發現,使用此方式只能實現區域性跨域,當一個專案中存在多個類的話,使用此方式就會比較麻煩(需要給所有類上都新增此註解)。
3.2 通過配置檔案跨域
接下來我們通過設定配置檔案的方式就可以實現全域性跨域了,它的實現步驟如下:
- 建立一個新配置檔案;
- 新增 @Configuration 註解,實現 WebMvcConfigurer 介面;
- 重寫 addCorsMappings 方法,設定允許跨域的程式碼。
具體實現程式碼如下:
```java import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.CorsRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration // 一定不要忽略此註解 public class CorsConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/") // 所有介面 .allowCredentials(true) // 是否傳送 Cookie .allowedOriginPatterns("") // 支援域 .allowedMethods(new String[]{"GET", "POST", "PUT", "DELETE"}) // 支援方法 .allowedHeaders("") .exposedHeaders("*"); } } ```
3.3 通過 CorsFilter 跨域
此實現方式和上一種實現方式類似,它也可以實現全域性跨域,它的具體實現程式碼如下:
```java import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.UrlBasedCorsConfigurationSource; import org.springframework.web.filter.CorsFilter;
@Configuration // 一定不能忽略此註解 public class MyCorsFilter { @Bean public CorsFilter corsFilter() { // 1.建立 CORS 配置物件 CorsConfiguration config = new CorsConfiguration(); // 支援域 config.addAllowedOriginPattern(""); // 是否傳送 Cookie config.setAllowCredentials(true); // 支援請求方式 config.addAllowedMethod(""); // 允許的原始請求頭部資訊 config.addAllowedHeader(""); // 暴露的頭部資訊 config.addExposedHeader(""); // 2.新增地址對映 UrlBasedCorsConfigurationSource corsConfigurationSource = new UrlBasedCorsConfigurationSource(); corsConfigurationSource.registerCorsConfiguration("/**", config); // 3.返回 CorsFilter 物件 return new CorsFilter(corsConfigurationSource); } } ```
3.4 通過 Response 跨域
此方式是解決跨域問題最原始的方式,但它可以支援任意的 Spring Boot 版本(早期的 Spring Boot 版本也是支援的)。但此方式也是區域性跨域,它應用的範圍最小,設定的是方法級別的跨域,它的具體實現程式碼如下:
```java import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletResponse; import java.util.HashMap;
@RestController
public class TestController {
@RequestMapping("/test")
public HashMap
3.5 通過 ResponseBodyAdvice 跨域
通過重寫 ResponseBodyAdvice 介面中的 beforeBodyWrite(返回之前重寫)方法,我們可以對所有的介面進行跨域設定,它的具體實現程式碼如下:
```java import org.springframework.core.MethodParameter; import org.springframework.http.MediaType; import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
@ControllerAdvice public class ResponseAdvice implements ResponseBodyAdvice { / * 內容是否需要重寫(通過此方法可以選擇性部分控制器和方法進行重寫) * 返回 true 表示重寫 */ @Override public boolean supports(MethodParameter returnType, Class converterType) { return true; } / * 方法返回之前呼叫此方法 / @Override public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) { // 設定跨域 response.getHeaders().set("Access-Control-Allow-Origin", ""); return body; } } ```
此實現方式也是全域性跨域,它對整個專案中的所有介面有效。
4.原理分析
為什麼通過以上方法設定之後,就可以實現不同專案之間的正常互動呢?
這個問題的答案也很簡單,我們之前在說跨域時講到:“跨域問題本質是瀏覽器的行為,它的初衷是為了保證使用者的訪問安全,防止惡意網站竊取資料”,那想要解決跨域問題就變得很簡單了,只需要告訴瀏覽器這是一個安全的請求,“我是自己人”就行了,那怎麼告訴瀏覽器這是一個正常的請求呢?
只需要在返回頭中設定“Access-Control-Allow-Origin”引數即可解決跨域問題,此引數就是用來表示允許跨域訪問的原始域名的,當設定為“*”時,表示允許所有站點跨域訪問,如下圖所示:
所以以上 5 種解決跨域問題的本質都是給響應頭中加了一個 Access-Control-Allow-Origin 的響應頭而已。
演示專案原始碼
https://gitee.com/mydb/springboot-examples/tree/master/spring-boot-cross
總結
跨域問題的本質是瀏覽器為了保證使用者的一種安全攔截機制,想要解決跨域問題,只需要告訴瀏覽器“我是自己人,不要攔我”就行。它的常見實現方式有 5 種:通過註解實現區域性跨域、通過配置檔案實現全域性跨域、通過 CorsFilter 物件實現全域性跨域、通過 Response 物件實現區域性跨域,通過 ResponseBodyAdvice 實現全域性跨域。
參考 & 鳴謝
blog.csdn.net/pjmike233/article/details/82461911
是非審之於己,譭譽聽之於人,得失安之於數。
公眾號:Java面試真題解析
- 面試官:什麼是雙親委派模型?
- 面試官:熔斷和降級有什麼區別?
- 寬表為什麼橫行?
- 有沒有完全自主的國產化資料庫技術
- 面試突擊64:瞭解 HTTP 協議嗎?
- 面試突擊80:說一下 Spring 中 Bean 的生命週期?
- 面試突擊89:事務隔離級別和傳播機制有什麼區別?
- 面試突擊82:SpringBoot 中如何操作事務?
- 面試突擊87:說一下 Spring 事務傳播機制?
- 面試突擊81:什麼是跨域問題?如何解決?
- 面試突擊71:GET 和 POST 有什麼區別?
- 面試突擊70:什麼是粘包和半包?怎麼解決?
- 面試突擊68:為什麼 TCP 需要 3 次握手?
- 面試突擊66:請求轉發和請求重定向有什麼區別?
- 面試突擊63:MySQL 中如何去重?
- 面試突擊65:為什麼要用HTTPS?它有什麼優點?
- 面試突擊62:group by 有哪些注意事項?
- 面試突擊53:常見的 HTTP 狀態碼有哪些?
- 面試突擊61:說一下MySQL事務隔離級別?
- 面試突擊52:什麼是三正規化?它有什麼用?