1. 程式人生 > >HTTP跨域研究

HTTP跨域研究

  • CORS(Cross-origin resource sharing) “跨域資源共享”

在出現CORS標準之前, 我們還只能通過jsonp(jsonp跨域請求詳解)的形式去向“跨源”伺服器去傳送 XMLHttpRequest 請求,這種方式吃力不討好,在請求方與接收方都需要做處理,而且請求的方式僅僅侷限於GET。所以 ,CORS標準必然是大勢所趨,並且市場上絕大多數瀏覽器都已經支援CORS。(IE10以上)

  • CORS概念

支援CORS請求的瀏覽器一旦發現ajax請求跨域,會對請求做一些特殊處理,對於已經實現CORS介面的服務端,接受請求,並做出迴應。

有一種情況比較特殊,如果我們傳送的跨域請求為“非簡單請求”,瀏覽器會在發出此請求之前首先發送一個請求型別為OPTIONS的“預檢請求”,驗證請求源是否為服務端允許源,這些對於開發這來說是感覺不到的,由瀏覽器代理。

總而言之,客戶端不需要對跨域請求做任何特殊處理。

  • 簡單請求與非簡單請求

瀏覽器對跨域請求區分為“簡單請求”與“非簡單請求”

“簡單請求”滿足以下特徵:

1) 請求方法是以下三種方法之一:
     HEAD
     GET
     POST
(2)HTTP的頭資訊不超出以下幾種欄位:
     Accept
     Accept-Language
     Content-Language
     Last-Event-ID
     Content-Type:application/x-www-form-urlencoded、 multipart/form-data
、text/plain

不滿足這些特徵的請求稱為“非簡單請求”,例如:content-type=applicaiton/json , method = PUT/DELETE…

Request Headers(請求頭)

Origin 表示跨域請求的原始域。

Access-Control-Request-Method 表示跨域請求的方式。(如GET/POST)

Access-Control-Request-Headers 表示跨域請求的請求頭資訊。

  • Response headers(響應頭 )
Access-Control-Allow-Origin

表示允許哪些原始域進行跨域訪問。(字元陣列)

Access-Control-Allow-Credentials

表示是否允許客戶端獲取使用者憑據。(布林型別) 使用場景:例如現在從瀏覽器發起跨域請求,並且要附帶Cookie資訊給伺服器。則必須具備兩個條件:1. 瀏覽器端:傳送AJAX請求前需設定通訊物件XHR的withCredentials 屬性為true。 2.伺服器端:設定Access-Control-Allow-Credentials為true。兩個條件缺一不可,否則即使伺服器同意傳送Cookie,瀏覽器也無法獲取。

Access-Control-Allow-Methods

表示跨域請求的方式的允許範圍。(例如只授權GET/POST)

Access-Control-Allow-Headers

表示跨域請求的頭部的允許範圍。

Access-Control-Expose-Headers

表示暴露哪些頭部資訊,並提供給客戶端。(因為基於安全考慮,如果沒有設定額外的暴露,跨域的通訊物件XMLHttpRequest只能獲取標準的頭部資訊)

Access-Control-Max-Age

表示預檢請求 [Preflight Request] 的最大快取時間。

  • CORS實現跨域訪問

授權方式

  • [ ] 方式1:返回新的CorsFilter
  • [ ] 方式2:重寫WebMvcConfigurer
  • [ ] 方式3:使用註解(@CrossOrigin)
  • [ ] 方式4:手工設定響應頭(HttpServletResponse )

注:CorsFilter / WebMvcConfigurer / @CrossOrigin 需要SpringMVC 4.2 以上的版本才支援,對應SpringBoot 1.3 版本以上都支援這些CORS特性。不過,使用SpringMVC4.2 以下版本的小夥伴也不用慌,直接使用方式4通過手工新增響應頭來授權CORS跨域訪問也是可以的。附:在SpringBoot 1.2.8 + SpringMVC 4.1.9 親測成功。

注:方式1和方式2屬於全域性CORS配置,方式3和方式4屬於區域性CORS配置。如果使用了局部跨域是會覆蓋全域性跨域的規則,所以可以通過@CrossOrigin註解來進行細粒度更高的跨域資源控制。

  1. 返回新的CorsFilter(全域性跨域)

在任意配置類,返回一個新的CorsFilter Bean,並新增對映路徑和具體的CORS配置資訊。

package com.hehe.yyweb.config;

@Configuration
public class GlobalCorsConfig {
    @Bean
    public CorsFilter corsFilter() {
        //1.新增CORS配置資訊
        CorsConfiguration config = new CorsConfiguration();
          //放行哪些原始域
          config.addAllowedOrigin("*");
          //是否傳送Cookie資訊
          config.setAllowCredentials(true);
          //放行哪些原始域(請求方式)
          config.addAllowedMethod("*");
          //放行哪些原始域(頭部資訊)
          config.addAllowedHeader("*");
          //暴露哪些頭部資訊(因為跨域訪問預設不能獲取全部頭部資訊)
          config.addExposedHeader("*");

        //2.新增對映路徑
        UrlBasedCorsConfigurationSource configSource = new UrlBasedCorsConfigurationSource();
        configSource.registerCorsConfiguration("/**", config);

        //3.返回新的CorsFilter.
        return new CorsFilter(configSource);
    }
}
  1. 重寫WebMvcConfigurer(全域性跨域)

在任意配置類,返回一個新的WebMvcConfigurer Bean,並重寫其提供的跨域請求處理的介面,目的是新增對映路徑和具體的CORS配置資訊。

package com.hehe.yyweb.config;

@Configuration
public class GlobalCorsConfig {
    @Bean
    public WebMvcConfigurer corsConfigurer() {
        return new WebMvcConfigurer() {
            @Override
            //重寫父類提供的跨域請求處理的介面
            public void addCorsMappings(CorsRegistry registry) {
                //新增對映路徑
                registry.addMapping("/**")
                        //放行哪些原始域
                        .allowedOrigins("*")
                        //是否傳送Cookie資訊
                        .allowCredentials(true)
                        //放行哪些原始域(請求方式)
                        .allowedMethods("GET","POST", "PUT", "DELETE")
                        //放行哪些原始域(頭部資訊)
                        .allowedHeaders("*")
                        //暴露哪些頭部資訊(因為跨域訪問預設不能獲取全部頭部資訊)
                        .exposedHeaders("Header1", "Header2");
            }
        };
    }
}
  1. 使用註解(區域性跨域)

在方法上(@RequestMapping)使用註解 @CrossOrigin :

   @RequestMapping("/hello")
    @ResponseBody
    @CrossOrigin("http://localhost:8080") 
    public String index( ){
        return "Hello World";
    }

或者在控制器(@Controller)上使用註解 @CrossOrigin :

@Controller
@CrossOrigin(origins = "http://xx-domain.com", maxAge = 3600)
public class AccountController {

    @RequestMapping("/hello")
    @ResponseBody
    public String index( ){
        return "Hello World";
    }
}
  1. 手工設定響應頭(區域性跨域 )

使用HttpServletResponse物件新增響應頭(Access-Control-Allow-Origin)來授權原始域,這裡Origin的值也可以設定為”*” ,表示全部放行。

@RequestMapping("/hello")
    @ResponseBody
    public String index(HttpServletResponse response){
        response.addHeader("Access-Control-Allow-Origin", "http://localhost:8080");
        return "Hello World";
    }

三、測試跨域訪問

首先使用 Spring Initializr 快速構建一個Maven工程,什麼都不用改,在static目錄下,新增一個頁面:index.html 來模擬跨域訪問。目標地址: http://localhost:8090/hello

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8"/>
    <title>Page Index</title>
</head>
<body>
<h2>前臺系統</h2>
<p id="info"></p>
</body>
<script src="webjars/jquery/3.2.1/jquery.js"></script>
<script>
    $.ajax({
        url: 'http://localhost:8090/hello',
        type: "POST",
        success: function (data) {
            $("#info").html("跨域訪問成功:"+data);
        },
        error: function (data) {
            $("#info").html("跨域失敗!!");
        }
    })
</script>
</html>

然後建立另一個工程,在Root Package新增Config目錄並建立配置類來開啟全域性CORS。

package com.hehe.yyweb.config;

@Configuration
public class GlobalCorsConfig {

    @Bean
    public WebMvcConfigurer corsConfigurer() {
        return new WebMvcConfigurer() {
            @Override
            public void addCorsMappings(CorsRegistry registry) {
                registry.addMapping("/**");
            }
        };
    }
}

接著,簡單編寫一個Rest介面 ,並指定應用埠為8090。

package com.hehe.yyweb;

@SpringBootApplication
@RestController
public class YyWebApplication {

    @Bean
    public TomcatServletWebServerFactory tomcat() {
        TomcatServletWebServerFactory tomcatFactory = new TomcatServletWebServerFactory();
        tomcatFactory.setPort(8090); //預設啟動8090埠
        return tomcatFactory;
    }

    @RequestMapping("/hello")
    public String index() {
        return "Hello World";
    }

    public static void main(String[] args) {
        SpringApplication.run(YyWebApplication.class, args);
    }
}