首頁 > 軟體

SpringBoot之跨域過濾器設定允許跨域存取方式

2022-07-01 22:02:48

SpringBoot跨域過濾器設定允許跨域存取

跨域請求

當一個資源從與該資源本身所在的伺服器不同的域或埠請求一個資源時,資源會發起一個跨域 HTTP 請求。

出於安全原因,瀏覽器限制從指令碼內發起的跨源HTTP請求。 例如,XMLHttpRequest和Fetch API遵循同源策略。 這意味著使用這些API的Web應用程式只能從載入應用程式的同一個域請求HTTP資源,除非使用CORS標頭檔案。

問題背景

如果前端提示”Access-Control-Allow-Origin”問題

XMLHttpRequest cannot load http://xxxxxxxxxx/. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'null' is therefore not allowed access.

跨域過濾器

那麼需要再SpringBoot2設定跨域過濾器允許跨域存取。

import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Component;
@Component  
public class CorsFilter implements Filter {  
    @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {  
        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) res;  
        response.setHeader("Access-Control-Allow-Origin", "*");  
        response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE, PUT, GET");  
        response.setHeader("Access-Control-Max-Age", "3600");  
        response.setHeader("Access-Control-Allow-Headers", "x-requested-with");  
        chain.doFilter(req, res);  
    }  
    @Override
    public void init(FilterConfig filterConfig) {}  
    @Override
    public void destroy() {}  
}  

跨域功能改進

如果需要顯示跨域地址,還可以在裡面加上存取來源列印語句,供排查

String curOrigin = request.getHeader("Origin");
System.out.println("###跨域過濾器->當前存取來源->"+curOrigin+"###");  

如果需要跨域許可權,可以判斷一下來源

String curOrigin = request.getHeader("Origin");
System.out.println("###跨域過濾器->當前存取來源->"+curOrigin+"###");  
if(curOrigin.indexOf("127.0.0.1:8080")>-1){
    response.setHeader("Access-Control-Allow-Origin", "*");
}

關於跨域存取更專業的內容,可以存取Mozilla官方的一個關於CROS文章

https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Access_control_CORS

SpringBoot跨域設定(CORS)

一、什麼是跨域

請求url的協定、域名、埠三者有任意一個不同即為跨域。跨域問題是因為瀏覽器的同源策略的限制而產生的。

  • 同源:請求url的協定、域名、埠三者都相同即為同源(同一個域)。
  • 同源策略:同源策略(Sameoriginpolicy)是一種約定,他是瀏覽器最核心也最基本的安全功能。同源策略會阻止非同源(同一個域)的內容進行互動。

同源策略的限制:

  • 無法讀取非同源網頁的Cookie、LocalStorage和IndexedDB
  • 無法接觸非同源網頁的DOM
  • 無法向非同源地址傳送AJAX請求

瀏覽器的同源策略會限制跨域請求,限制的方式一般有兩種:

  • 瀏覽器限制發起跨域請求;
  • 跨域請求可以正常發起,但是返回的結果被瀏覽器攔截了。

一般瀏覽器都是第二種方式限制跨域請求,那就是說請求已到達伺服器,並有可能對資料庫裡的資料進行了操作,但是返回的結果被瀏覽器攔截了,那麼我們就獲取不到返回結果,這是一次失敗的請求,但是可能對資料庫裡的資料產生了影響。

為了防止這種情況的發生,規範要求,對這種可能對伺服器資料產生副作用的HTTP請求方法,瀏覽器必須先使用OPTIONS方法發起一個預檢請求,從而獲知伺服器是否允許該跨域請求:如果允許,就傳送帶資料的真實請求;如果不允許,則阻止傳送帶資料的真實請求。

二、跨域資源共用(CORS)

解決非同源內容無法互動的問題,目前主流的解決方案就是:CORS(跨域資源共用)。

跨域資源共用(Cross-origin Resource Sharing)簡稱CORS,它突破了一個請求在瀏覽器發出只能在同源的情況下向伺服器獲取資料的限制。

CORS約定伺服器端和瀏覽器在HTTP協定之上,通過一些額外HTTP頭部資訊,進行跨域資源共用的協商。伺服器端和瀏覽器都必需遵循規範中的要求。

CORS把HTTP的跨域請求分成兩類,簡單請求和非簡單請求,不同請求按照不同的策略進行跨域資源共用協商。

1. 簡單請求

簡單跨域請求需滿足的條件:

1.請求方法是GET、HEAD或者POST(POST時,Content-Type的值必須是application/x-www-form-urlencoded、multipart/form-data、text/plain中的一個值);

2.請求中沒有自定義HTTP請求頭。

HTTP頭只能時下面這些欄位:

  • Accept
  • Accept-Language
  • Content-Language
  • DPR
  • Downlink
  • Save-Data
  • Viewport-Width
  • Width
  • Content-Type

以上兩點都滿足才是簡單跨域請求。

對於簡單跨域請求,處理方式如下:

1.瀏覽器要做的就是在HTTP請求頭中新增Origin,將JavaScript指令碼所在域填充進去,向其他域的伺服器請求資源。

Origin: http://www.joker.com

Origin欄位用來說明,本次請求來自哪個源(協定 + 域名 + 埠)。伺服器根據這個值,決定是否同意這次請求。

2.伺服器端收到一個簡單跨域請求後,根據資源許可權設定,在響應頭中新增Access-Control-Allow-Origin。

如果Origin指定的源,不在許可範圍內,伺服器會返回一個正常的HTTP迴應。 但這個響應頭資訊沒有包含Access-Control-Allow-Origin欄位,瀏覽器就知道該域名不在許可範圍內。

如果Origin指定的域名在許可範圍內,伺服器返回的響應,會多出幾個頭資訊欄位:

Access-Control-Allow-Origin: http://www.joker.com
Access-Control-Allow-Credentials: true
Access-Control-Expose-Headers: My-Token
  • Access-Control-Allow-Origin:該欄位是必須的。它的值要麼是請求時Origin欄位的值,要麼是一個*值,表示接受任意域名的請求。
  • Access-Control-Allow-Credentials: 該欄位是可選的。它的值是一個布林值,表示是否允許傳送Cookie。預設情況下,Cookie不包括在CORS請求之中。設為true,即表示伺服器明確許可,Cookie可以包含在請求中,一起發給伺服器。這個值也只能設為true,如果伺服器不要瀏覽器傳送Cookie,刪除該欄位即可。
  • Access-Control-Expose-Headers:該欄位是可選的。CORS請求時,XMLHttpRequest物件的getResponseHeader()方法只能拿到6個基本欄位:Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma。如果想拿到其他欄位,就必須在Access-Control-Expose-Headers裡面指定。

3.瀏覽器收到響應後,通過獲取響應頭中的Access-Control-Allow-Origin欄位,來判斷如果當前域已經得到授權,則將結果返回給JavaScript。否則瀏覽器忽略此次響應。

2. 非簡單請求

非簡單跨域請求需滿足的條件:

  • 除GET、HEAD和POST(Content-Type的值是:application/x-www-form-urlencoded、multipart/form-data、text/plain中的一個值)以外的其他HTTP方法
  • 如:PUT、DELETE、TRACE、PATCH、POST(Content-Type的值是:application/json)。
  • 請求中有自定義HTTP頭部。

以上兩點只要至少滿足其中一點就是非簡單跨域請求。

對於非簡單跨域請求,處理方式如下:

1.瀏覽器在傳送真實HTTP請求之前先傳送一個OPTIONS的預檢請求,檢測伺服器端是否支援真實請求進行跨域資源存取。

真實請求的資訊在OPTIONS請求中通過請求頭中的Access-Control-Request-Method和Access-Control-Request-Headers欄位來描述。此外與簡單跨域請求一樣,請求頭中也會有Origin欄位。

Origin: http://www.joker.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: Header1,Header2
  • Origin:必須欄位,用於指定請求源。
  • Access-Control-Request-Method:必須欄位,用於描述真實請求的方法(PUT、DELETE等)。
  • Access-Control-Request-Headers:指定真實請求會額外傳送的請求頭欄位資訊。

2.伺服器端接到預檢請求後,會檢查了Origin、Access-Control-Request-Method和Access-Control-Request-Headers欄位,檢驗是否允許跨源請求。

如果不允許該跨域請求,會返回一個正常的HTTP迴應,但這個響應頭資訊沒有包含Access-Control-Allow-Origin欄位,瀏覽器就知道該域名不在許可範圍內。

如果允許該跨域請求,就會在響應頭中放入Access-Control-Allow-Origin、Access-Control-Allow-Methods和Access-Control-Allow-Headers,分別表示允許跨域資源請求的域、請求方法和請求頭。此外,伺服器端還可以在響應頭中放入Access-Control-Max-Age,允許瀏覽器在指定時間內,無需再傳送預檢請求進行協商,直接用本次協商結果即可。

Access-Control-Allow-Origin: http://www.joker.com
Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: Header1,Header2,Header3
Access-Control-Allow-Credentials: true
Access-Control-Max-Age: 1728000
  • Access-Control-Allow-Methods:該欄位必需,它的值是逗號分隔的一個字串,表明伺服器支援的所有跨域請求的方法。注意,返回的是所有支援的方法,而不單是瀏覽器請求的那個方法。這是為了避免多次"預檢"請求。
  • Access-Control-Allow-Headers:如果瀏覽器請求包括Access-Control-Request-Headers欄位,則Access-Control-Allow-Headers欄位是必需的。它也是一個逗號分隔的字串,表明伺服器支援的所有頭資訊欄位,不限於瀏覽器在"預檢"中請求的欄位。
  • Access-Control-Allow-Credentials: 該欄位與簡單請求時的含義相同。它的值是一個布林值,表示是否允許傳送Cookie。預設情況下,Cookie不包括在CORS請求之中。設為true,即表示伺服器明確許可,Cookie可以包含在請求中,一起發給伺服器。這個值也只能設為true,如果伺服器不要瀏覽器傳送Cookie,刪除該欄位即可。
  • Access-Control-Max-Age: 該欄位可選,用來指定本次預檢請求的有效期,單位為秒。上面結果中,有效期是20天(1728000秒),即允許快取該條迴應1728000秒(即20天),在此期間,不用發出另一條預檢請求。

瀏覽器根據OPTIONS請求返回的結果來決定是否繼續傳送真實的請求進行跨域資源存取。這個過程對真實請求的呼叫者來說是透明的。

三、SpringBoot設定CORS

SpringBoot設定CORS的的本質都是通過設定響應頭資訊來告訴前端該請求是否支援跨域。

SpringBoot設定CORS的方式主要有以下三種。

1. 設定過濾器CorsFilter

@Configuration
public class CorsConfig {
    
    @Bean
    CorsFilter corsFilter() {
        CorsConfiguration configuration = new CorsConfiguration();
        configuration.setAllowedOrigins(Arrays.asList("*"));
        configuration.setAllowedMethods(Arrays.asList("*"));
        configuration.setAllowedHeaders(Arrays.asList("*"));
        configuration.setAllowCredentials(true);
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", configuration);
        return new CorsFilter(source);
    }
}

2. 實現介面WebMvcConfigurer

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
    
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                .allowedOrigins("*")
                .allowedHeaders("*")
                .allowedMethods("*")
                .allowCredentials(true);
    }
}

3. 使用註解@CrossOrigin

@CrossOrigin註解可以用在類或者方法上

用在控制器類上,表示 該類的所有方法都允許跨域

@RestController
@CrossOrigin
public class TestController {
    
    @GetMapping("test")
    public String test() {
        return "success";
    }
}

用在控制器方法上,表示該方法都允許跨域

@RestController
public class TestController {
    @CrossOrigin
    @GetMapping("test")
    public String test() {
        return "success";
    }
}

@CrossOrigin註解原始碼

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CrossOrigin {
  
  /**
   * 這origins和value是一樣的
   * 允許來源域名的列表,例如 www.baidu.com,匹配的域名是跨域預請求Response頭中的Access-Control-Aloow_origin欄位值。
   * 不設定確切值時預設支援所有域名跨域存取。
   */
  @AliasFor("origins")
  String[] value() default {};
  @AliasFor("value")
  String[] origins() default {};
  /**
   * 高版本下Spring2.4.4使用originPatterns而不是value和origins
   */
  String[] originPatterns() default {};
  /**
   * 跨域請求中允許的請求頭中的欄位型別, 該值對應跨域預請求Response頭中的Access-Control-Allow-Headers欄位值。
   * 不設定確切值預設支援所有的header欄位(Cache-Controller、Content-Language、Content-Type、Expires、Last-Modified、Pragma)跨域存取
   */
  String[] allowedHeaders() default {};
  /**
   * 跨域請求請求頭中允許攜帶的除Cache-Controller、Content-Language、Content-Type、Expires、Last-Modified、Pragma這六個基本欄位之外的其他欄位資訊,
   * 對應的是跨域請求Response頭中的Access-control-Expose-Headers欄位值
   */
  String[] exposedHeaders() default {};
  /**
   * 跨域HTTP請求中支援的HTTP請求型別(GET、POST...),
   * 不指定確切值時預設與 Controller 方法中的 methods 欄位保持一致。
   */
  RequestMethod[] methods() default {};
  /**
   * 瀏覽器是否將本域名下的cookie資訊攜帶至跨域伺服器中。預設攜帶至跨域伺服器中,但要實現cookie共用還需要前端在AJAX請求中開啟withCredentials屬性。
   * 該值對應的是是跨域請求 Response 頭中的 'Access-Control-Allow-Credentials' 欄位值。
   */
  String allowCredentials() default "";
  /**
   * 該值的目的是減少瀏覽器預檢請求/響應互動的數量。預設值1800s。設定了該值後,瀏覽器將在設定值的時間段內對該跨域請求不再發起預請求。
   * 該值對應的是是跨域請求Response頭中的Access-Control-Max-Age欄位值,表示預檢請求響應的快取持續的最大時間。
   */
  long maxAge() default -1;
}

以上為個人經驗,希望能給大家一個參考,也希望大家多多支援it145.com。


IT145.com E-mail:sddin#qq.com