面試突擊81:什麼是跨域問題?如何解決?

語言: CN / TW / HK
ead>

我報名參加金石計劃1期挑戰——瓜分10萬獎池,這是我的第2篇文章,點選檢視活動詳情

跨域問題指的是不同站點之間,使用 ajax 無法相互呼叫的問題。跨域問題本質是瀏覽器的一種保護機制,它的初衷是為了保證使用者的安全,防止惡意網站竊取資料。 但這個保護機制也帶來了新的問題,它的問題是給不同站點之間的正常呼叫,也帶來的阻礙,那怎麼解決這個問題呢?接下來我們一起來看。

1.跨域三種情況

在請求時,如果出現了以下情況中的任意一種,那麼它就是跨域請求:

  1. 協議不同,如 http 和 https;
  2. 域名不同;
  3. 埠不同。

也就是說,即使域名相同,如果一個使用的是 http,另一個使用的是 https,那麼它們也屬於跨域訪問。常見的跨域問題如下圖所示: image.png

2.跨域問題演示

接下來,我們使用兩個 Spring Boot 專案來演示跨域的問題,其中一個是埠號為 8080 的前端專案,另一個埠號為 9090 的後端介面專案。

2.1 前端網站

前端專案只需要在 resources 下放兩個檔案,一個用於傳送 ajax 請求的 jquery.js,另一個是 html 前端頁面,工程目錄如下圖所示: image.png 其中前端頁面 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 test() { return new HashMap() {{ put("state", 200); put("data", "success"); put("msg", ""); }}; } } ```

以上兩個專案建立並啟動成功之後,使用前端專案訪問後端介面,因為埠不一樣,所以也屬於跨域訪問,執行結果如下圖所示: image.png

3.解決跨域問題

在 Spring Boot 中跨域問題有很多種解決方案,比如以下 5 個:

  1. 使用 @CrossOrigin 註解實現跨域;
  2. 通過配置檔案實現跨域;
  3. 通過 CorsFilter 物件實現跨域;
  4. 通過 Response 物件實現跨域;
  5. 通過實現 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 test() { return new HashMap() {{ put("state", 200); put("data", "success"); put("msg", ""); }}; } } ```

以上程式碼的執行結果如下圖所示: image.png 從上圖中可以看出,前端專案訪問另一個後端專案成功了,也就說明它解決了跨域問題。 優缺點分析 此方式雖然雖然實現(跨域)比較簡單,但細心的朋友也能發現,使用此方式只能實現區域性跨域,當一個專案中存在多個類的話,使用此方式就會比較麻煩(需要給所有類上都新增此註解)。

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 test(HttpServletResponse response) { // 設定跨域 response.setHeader("Access-Control-Allow-Origin", "*"); return new HashMap() {{ put("state", 200); put("data", "success"); put("msg", ""); }}; } } ```

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”引數即可解決跨域問題,此引數就是用來表示允許跨域訪問的原始域名的,當設定為“*”時,表示允許所有站點跨域訪問,如下圖所示: image.png 所以以上 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面試真題解析

面試合集:https://gitee.com/mydb/interview