1.基礎知識##
1.1 跨域###
跨域是瀏覽器的一種同源安全策略,是瀏覽器單方面限制的,所有僅在客戶端運行在瀏覽器中才需要考慮這個問題。
跨域分為三種情況,協議跨域(http->https)、端口跨域、主機跨域。
常用的解決跨域的三種方式,JSONP(只能支持GET跨域),NGINX代理轉發(一般前端同學愛這么用),CORS
1.2 CORS原理###
因為cors是由瀏覽器控制的,實際就是在HEADER中新增一些字段,跨域時和服務器端進行一個的握手協議,比如下面的request部分,就是瀏覽器告訴服務器,我要從origin這里訪問你了,要訪問你的xxMethod,還會帶xxHeader這個參數。你告訴我行不行吧。然后response會帶一堆東西回來。告訴你,我這里只能有Access-Control-Allow-Origin這些域名可以訪問,你自個兒先看看行不行,然后你的方法支持不支持,header支持不支持。。。等等東西
比如
request部分:
Origin //瀏覽器自己設置的,表示請求從哪個域名下發出來的
//下面兩個只有預檢請求會帶
Access-Control-Request-Method //告訴服務器我要用什么方法,服務器會根據配置做下篩選 再返回一個Access-Control-Allow-Methods告訴瀏覽器哪些可以用
Access-Control-Request-Headers //告訴服務器我要帶哪些header,服務器會根據配置做下篩選 再返回一個Access-Control-Allow-Headers告訴瀏覽器哪些可以用
response部分:
Access-Control-Allow-Origin //指定哪些客戶端的域名允許訪問這個資源
Access-Control-Allow-Credentials //服務器允許瀏覽器帶cookie上來,不設這個服務器端就無法獲得登錄信息
Access-Control-Max-Age //告訴瀏覽器多久不需要再發出預檢請求
Access-Control-Allow-Methods //服務器支持的方法 比如POST GET之類的
Access-Control-Allow-Headers //需要在正式請求中加入的header值,否則正式請求會被拒絕,也是預檢請求的響應中帶回去的
Access-Control-Expose-Headers //很少用,告訴客戶端哪些header可以使用
1.3 CORS的三種場景###
簡單請求
瀏覽器來決定請求的種類,比如不帶自定義請求頭信息的GET請求、HEAD請求以及Content-type為application/x-www-form-urlencoded、multipart/form-data或者text/plain的post請求,都是簡單請求。
簡單請求時,瀏覽器的request只有一個origin字段,后端的返回值也很簡單只要加一個Access-Control-Allow-Origin九可以了,瀏覽器拿到Access-Control-Allow-Origin的值后和自己的進行首部比對,通過就允許跨域了。
預檢請求(preflight)
預檢請求是瀏覽器發現不是簡單請求后,封裝一個OPTIONS請求到服務器,根據服務器的返回值來判斷是否可以進行跨域,可以的話,后面還有一次真正帶數據的請求。上面的response部分:就是預檢請求后服務器端的響應
帶憑據的請求
其實就是當預檢請求的返回值中 Access-Control-Allow-Credentials 的值為TRUE,那么后續的正式請求就會攜帶憑據信息(cookie等)
2.springsecurit中cors的實現##
springboot中是通過CorsFilter來實現的,具體干活的是DefaultCorsProcessor,一會看下代碼。開啟方式也很簡單,就是security的配置中通過HttpSecurity中的.cors()方法開啟。
DefaultCorsProcessor.java
//干活的代碼
public boolean processRequest(@Nullable CorsConfiguration config, HttpServletRequest request,
HttpServletResponse response) throws IOException {
//回寫三個vary屬性
response.addHeader(HttpHeaders.VARY, HttpHeaders.ORIGIN);
response.addHeader(HttpHeaders.VARY, HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD);
response.addHeader(HttpHeaders.VARY, HttpHeaders.ACCESS_CONTROL_REQUEST_HEADERS);
//如果不是跨域請求,直接返回true了
if (!CorsUtils.isCorsRequest(request)) {
return true;
}
//response已經有Access-Control-Allow-Origin 就是已經處理過了,就退回,兼容其他的自定義filter
if (response.getHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN) != null) {
logger.trace("Skip: response already contains \"Access-Control-Allow-Origin\"");
return true;
}
//判斷是否是預檢請求
boolean preFlightRequest = CorsUtils.isPreFlightRequest(request);
//缺少cors的配置
if (config == null) {
//預檢請求直接拒絕
if (preFlightRequest) {
rejectRequest(new ServletServerHttpResponse(response));
return false;
}
//簡單請求直接通過
else {
return true;
}
}
//具體處理
return handleInternal(new ServletServerHttpRequest(request), new ServletServerHttpResponse(response), config, preFlightRequest);
}
protected boolean handleInternal(ServerHttpRequest request, ServerHttpResponse response,
CorsConfiguration config, boolean preFlightRequest) throws IOException {
String requestOrigin = request.getHeaders().getOrigin();
String allowOrigin = checkOrigin(config, requestOrigin);
HttpHeaders responseHeaders = response.getHeaders();
//確保請求帶了origin這個屬性
if (allowOrigin == null) {
logger.debug("Reject: '" + requestOrigin + "' origin is not allowed");
rejectRequest(response);
return false;
}
//請求的method,復雜請求從Access-Control-Request-Method里面拿,簡單請求直接就是request.getMethod()
HttpMethod requestMethod = getMethodToUse(request, preFlightRequest);
List<HttpMethod> allowMethods = checkMethods(config, requestMethod);
//確保請求的method 確保在服務器端的配置中是允許的
if (allowMethods == null) {
logger.debug("Reject: HTTP '" + requestMethod + "' is not allowed");
rejectRequest(response);
return false;
}
//和方法類似,校驗下header
List<String> requestHeaders = getHeadersToUse(request, preFlightRequest);
List<String> allowHeaders = checkHeaders(config, requestHeaders);
if (preFlightRequest && allowHeaders == null) {
logger.debug("Reject: headers '" + requestHeaders + "' are not allowed");
rejectRequest(response);
return false;
}
//設置Access-Control-Allow-Origin
responseHeaders.setAccessControlAllowOrigin(allowOrigin);
//如果是預檢請求,設置下Access-Control-Allow-Methods
if (preFlightRequest) {
responseHeaders.setAccessControlAllowMethods(allowMethods);
}
//如果是預檢請求,並且allowHeaders不為空,設置下Access-Control-Allow-Headers
if (preFlightRequest && !allowHeaders.isEmpty()) {
responseHeaders.setAccessControlAllowHeaders(allowHeaders);
}
//設置Access-Control-Expose-Headers
if (!CollectionUtils.isEmpty(config.getExposedHeaders())) {
responseHeaders.setAccessControlExposeHeaders(config.getExposedHeaders());
}
//設置Access-Control-Allow-Credentials
if (Boolean.TRUE.equals(config.getAllowCredentials())) {
responseHeaders.setAccessControlAllowCredentials(true);
}
//設置Access-Control-Max-Age
if (preFlightRequest && config.getMaxAge() != null) {
responseHeaders.setAccessControlMaxAge(config.getMaxAge());
}
response.flush();
return true;
}
3. springboot如何開啟cors##
3.1 整體配置###
寫一個CorsConfigurationSource的bean就可以了,CorsConfigurer里面會自動注入這個bean
@Bean
public CorsConfigurationSource corsConfigurationSource(){
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList("https://www.baidu.com"));
configuration.setAllowedMethods(Arrays.asList("GET","POST"));
configuration.setAllowCredentials(true);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**",configuration);
return source;
}
3.2 具體配置###
在controller或者具體方法上面加注釋
@CrossOrigin(origins = Constant.originalHost, maxAge = 3600,allowCredentials = "true") 就可以了