SpringBoot系列——CORS(跨源資源共享)
前言
出於安全原因,瀏覽器禁止ajax呼叫當前源之外的資源(同源策略),我們之前也有寫個幾種跨域的簡單實現(還在問跨域?本文記錄js跨域的多種實現例項),本文主要詳細介紹CORS,跨源資源共享,以及如何在SpringBoot的幾種實現方式
這裡主要參考spring的這篇:https://docs.spring.io/spring/docs/5.1.8.RELEASE/spring-framework-reference/web.html#mvc-cors
以及:https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Access_control_CORS
CORS介紹
跨源資源共享(Cross-Origin Resource Sharing, CORS)是由大多數瀏覽器實現的W3C規範,它允許指定授權了哪種跨域請求,而不是使用基於IFRAME(內嵌框架)或JSONP的不太安全且功能不太強大的方法。
CORS分為簡單請求、非簡單請求兩種跨域請求方式,Spring MVC HandlerMapping的實現提供了對CORS的內建支援。成功地將請求對映到處理程式之後,HandlerMapping的實現將檢查CORS配置並不同的請求進行操作:預檢請求直接處理,而簡單請求和非簡單請求被攔截、驗證,並設定了所需的CORS響應頭。為了啟用跨源請求,您需要一些顯式宣告的CORS配置。如果沒有找到匹配的CORS配置,預檢請求將被拒絕。沒有將CORS標頭新增到簡單、非簡單CORS請求的響應中,瀏覽器會拒絕這個跨域請求。
簡單請求不會觸發預檢請求,而非簡單請求在發起之前會先發起預檢請求,以獲知伺服器是否允許該實際請求,"預檢請求“的使用,可以避免跨域請求對伺服器的使用者資料產生未預期的影響。
跨域響應欄位含義:https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Access_control_CORS#HTTP_%E5%93%8D%E5%BA%94%E9%A6%96%E9%83%A8%E5%AD%97%E6%AE%B5
跨域請求欄位含義:https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Access_control_CORS#HTTP_%E8%AF%B7%E6%B1%82%E9%A6%96%E9%83%A8%E5%AD%97%E6%AE%B5
簡單請求
若請求滿足所有下述條件,則該請求可視為“簡單請求”:
- 使用下列方法之一:
GET
HEAD
POST
- Fetch 規範定義了對 CORS 安全的首部欄位集合,不得人為設定該集合之外的其他首部欄位。該集合為:
Accept
Accept-Language
Content-Language
Content-Type
(需要注意額外的限制)DPR
Downlink
Save-Data
Viewport-Width
Width
Content-Type
的值僅限於下列三者之一:text/plain
multipart/form-data
application/x-www-form-urlencoded
- 請求中的任意
XMLHttpRequestUpload
物件均沒有註冊任何事件監聽器;XMLHttpRequestUpload
物件可以使用XMLHttpRequest.upload
屬性訪問。 - 請求中沒有使用
ReadableStream
物件。
例如:
非簡單請求
當請求滿足下述任一條件時,即視為非簡單請求,應首先發送預檢請求:
- 使用了下面任一 HTTP 方法:
PUT
DELETE
CONNECT
OPTIONS
TRACE
PATCH
- 人為設定了對 CORS 安全的首部欄位集合之外的其他首部欄位。該集合為:
Accept
Accept-Language
Content-Language
Content-Type
(需要注意額外的限制)DPR
Downlink
Save-Data
Viewport-Width
Width
-
Content-Type
的值不屬於下列之一:application/x-www-form-urlencoded
multipart/form-data
text/plain
- 請求中的
XMLHttpRequestUpload
物件註冊了任意多個事件監聽器。 - 請求中使用了
ReadableStream
物件。
例如:
PS:如果ajax的contentType:"application/json;charset=UTF-8",設定成了json格式傳輸,那麼你的data就要這樣傳JSON.stringify({id:1}),並且後端接參要加上@RequestBody,用物件去接MVC會幫我們自動注入引數,用字串去接,會得到json字串
附帶身份憑證的請求
CORS 的一個有趣的特性是,可以基於 HTTP cookies 和 HTTP 認證資訊傳送身份憑證。一般而言,對於跨域 XMLHttpRequest
請求,瀏覽器不會發送身份憑證資訊。如果要傳送憑證資訊,需要設定 XMLHttpRequest
的某個特殊標誌位 withCredentials=true,就可以向伺服器傳送Cookies,但是,如果伺服器端的響應中未攜帶 Access-Control-Allow-Credentials: true,瀏覽器將不會把響應內容返回給請求的傳送者。
如果前端設定了true,後端為false,則會
實現方式
@CrossOrigin
TestController介面測試
package cn.huanzi.qch.springbootcors.controller; import org.springframework.web.bind.annotation.*; @RequestMapping("cors/") @RestController public class TestController { /* 通過註解配置CORS跨域測試 $.ajax({ type:"POST", url:"http://localhost:10095/cors/corsByAnnotation", data:{id:1}, dataType:"text",//因為我們響應的是不是json,這裡要改一下 contentType:"application/x-www-form-urlencoded", //contentType:"application/json;charset=UTF-8",//如果用這個,則為非簡單請求 xhrFields:{ withCredentials:true }, success:function(data){ console.log(data); }, error:function(data){ console.log("報錯啦"); } }) */ @CrossOrigin( origins = "https://www.cnblogs.com", allowedHeaders = "*", methods = {RequestMethod.POST}, allowCredentials = "true", maxAge = 3600 ) @PostMapping("corsByAnnotation") public String corsByAnnotation(String id) { return "corsByAnnotation," + id; } }
配置Config
Java Configuration
MyConfiguration配置類
package cn.huanzi.qch.springbootcors.config; import org.springframework.context.annotation.Bean; 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 MyConfiguration { @Bean public WebMvcConfigurer corsConfigurer() { return new WebMvcConfigurer() { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/cors/corsByConfig") .allowedOrigins("https://www.cnblogs.com") .allowedMethods("POST") .allowedHeaders("*") .allowCredentials(true).maxAge(3600); } }; } }
TestController介面測試
package cn.huanzi.qch.springbootcors.controller; import org.springframework.web.bind.annotation.*; @RequestMapping("cors/") @RestController public class TestController { /* 通過Config配置CORS跨域測試 $.ajax({ type:"POST", url:"http://localhost:10095/cors/corsByConfig", data:{id:2}, dataType:"text",//因為我們響應的是不是json,這裡要改一下 contentType:"application/x-www-form-urlencoded", //contentType:"application/json;charset=UTF-8",//如果用這個,則為非簡單請求 xhrFields:{ withCredentials:true }, success:function(data){ console.log(data); }, error:function(data){ console.log("報錯啦"); } }) */ @PostMapping("corsByConfig") public String corsByConfig(String id) { return "corsByConfig," + id; } }
XML Configuration
xml格式我就不試了,大家看文件就好了:https://docs.spring.io/spring/docs/5.1.8.RELEASE/spring-framework-reference/web.html#mvc-cors-global-xml
CORS Filter
配置攔截器在啟動專案的時候會報一個bean已存在,叫我們改名或啟用覆蓋預設bean
Description: The bean 'myCorsFilter', defined in null, could not be registered. A bean with that name has already been defined in file [C:\Users\Administrator\Desktop\雜七雜八\springBoot\springboot-cors\target\classes\cn\huanzi\qch\springbootcors\filter\MyCorsFilter.class] and overriding is disabled. Action: Consider renaming one of the beans or enabling overriding by setting spring.main.allow-bean-definition-overriding=true
配置覆蓋
#啟用覆蓋預設bean spring.main.allow-bean-definition-overriding=true
MyCorsFilter
package cn.huanzi.qch.springbootcors.filter; import org.springframework.boot.web.servlet.ServletComponentScan; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; import javax.servlet.*; import javax.servlet.annotation.WebFilter; import javax.servlet.annotation.WebInitParam; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.Arrays; import java.util.List; @Component @ServletComponentScan @WebFilter(filterName = "myCorsFilter", //過濾器名稱 urlPatterns = "/cors/corsByMyCorsFilter",//url路徑 initParams = { @WebInitParam(name = "allowOrigin", value = "https://www.cnblogs.com"),//允許的請求源,可用,分隔,*表示所有 @WebInitParam(name = "allowMethods", value = "POST"),//允許的請求方法,可用,分隔,*表示所有 @WebInitParam(name = "allowCredentials", value = "true"), @WebInitParam(name = "allowHeaders", value = "*"), @WebInitParam(name = "maxAge", value = "3600"),//60秒 * 60,相當於一個小時 }) public class MyCorsFilter implements Filter { private String allowOrigin; private String allowMethods; private String allowCredentials; private String allowHeaders; private String maxAge; @Override public void init(FilterConfig filterConfig) { //讀取@WebFilter的initParams allowOrigin = filterConfig.getInitParameter("allowOrigin"); allowMethods = filterConfig.getInitParameter("allowMethods"); allowCredentials = filterConfig.getInitParameter("allowCredentials"); allowHeaders = filterConfig.getInitParameter("allowHeaders"); maxAge = filterConfig.getInitParameter("maxAge"); } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) servletRequest; HttpServletResponse response = (HttpServletResponse) servletResponse; if (!StringUtils.isEmpty(allowOrigin)) { if (allowOrigin.equals("*")) { response.setHeader("Access-Control-Allow-Origin", allowOrigin); } else { List<String> allowOriginList = Arrays.asList(allowOrigin.split(",")); if (allowOriginList.size() > 0) { //如果來源在允許來源內 String currentOrigin = request.getHeader("Origin"); if (allowOriginList.contains(currentOrigin)) { response.setHeader("Access-Control-Allow-Origin", currentOrigin); } } } } if (!StringUtils.isEmpty(allowMethods)) { response.setHeader("Access-Control-Allow-Methods", allowMethods); } if (!StringUtils.isEmpty(allowCredentials)) { response.setHeader("Access-Control-Allow-Credentials", allowCredentials); } if (!StringUtils.isEmpty(allowHeaders)) { response.setHeader("Access-Control-Allow-Headers", allowHeaders); } if (!StringUtils.isEmpty(maxAge)) { response.setHeader("Access-Control-Max-Age", maxAge); } //執行 filterChain.doFilter(servletRequest, servletResponse); } @Override public void destroy() { } }
TestController介面測試
package cn.huanzi.qch.springbootcors.controller; import org.springframework.web.bind.annotation.*; @RequestMapping("cors/") @RestController public class TestController {/* 通過攔截器配置CORS跨域測試 $.ajax({ type:"POST", url:"http://localhost:10095/cors/corsByMyCorsFilter", data:{id:3}, dataType:"text",//因為我們響應的是不是json,這裡要改一下 contentType:"application/x-www-form-urlencoded", //contentType:"application/json;charset=UTF-8",//如果用這個,則為非簡單請求 xhrFields:{ withCredentials:true }, success:function(data){ console.log(data); }, error:function(data){ console.log("報錯啦"); } }) */ @PostMapping("corsByMyCorsFilter") public String corsByMyCorsFilter(String id) { return "corsByMyCorsFilter," + id; } }
測試
開啟部落格園,F12開啟控制檯,開始測試
註解
java配置
corsFilter
後記
暫時記錄到這裡
程式碼開源
程式碼已經開源、託管到我的GitHub、碼雲:
GitHub:https://github.com/huanzi-qch/springBoot
碼雲:https://gitee.com/huanzi-qch/spring