SpringBoot之跨域過濾器配置允許跨域訪問方式

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;
}

以上為個人經驗,希望能給大傢一個參考,也希望大傢多多支持WalkonNet。

推薦閱讀: