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註解來進行細粒度更高的跨域資源控制。
- 返回新的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);
}
}
- 重寫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");
}
};
}
}
- 使用註解(區域性跨域)
在方法上(@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";
}
}
- 手工設定響應頭(區域性跨域 )
使用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);
}
}