前后端分離 開發環境通過CORS實現跨域聯調


  通過JSONP實現跨域已是老生常談,JSONP跨域限制多,最近了解了一下CORS。

  參考:

    https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Access_control_CORS

    http://newhtml.net/using-cors/

    https://www.w3.org/TR/2013/PR-cors-20131205/

  CORS是W3c的一個工作草案,定義了在跨域訪問資源時瀏覽器和服務器應該如何溝通。CORS背后的基本思想是使用自定義的HTTP頭部讓瀏覽器和服務器進行溝通,從而決定請求或響應成功與否。

  比如一個簡單的使用get或post發送的請求,它沒有自定義的頭部,而主體內容是text/plain。在發送該請求時,需要給它附加一個額外的Origin頭部,其中包含請求頁面的源信息(協議,域名和端口),以便服務器根據這個頭部信息來決定是否給予相應。

  Origin:http://www.nczonline.net

  如果服務器認為這個請求可以接受,就在Access-Control-Allow-Origin頭部中回發相同的源信息(如果是公共資源,可以回發***)。

  Access-Control-Allow-Origin://www.nczonline.net

  如果沒有這個頭部,或者有這個頭部但源信息不匹配,瀏覽器就會駁回請求。正常情況下,瀏覽器會處理請求。注意請求和響應都不包含cookies信息。


  同源策略:是瀏覽器最核心也最基本的安全功能;同源指的是:同協議,同域名和同端口。精髓:認為自任何站點裝載的信賴內容是不安全的。當被瀏覽器半信半疑的腳本運行在沙箱時,它們應該只被允許訪問來自同一站點的資源,而不是那些來自其它站點可能懷有惡意的資源;參考:JavaScript 的同源策略
  JSON & JSONP:JSON 是一種基於文本的數據交換方式,或者叫做數據描述格式。JSONP是資料格式JSON的一種“使用模式”,可以讓網頁從別的網域要資料,由於同源策略,一般來說位於server1.example.com的網頁無法與不是 server1.example.com的服務器溝通,而HTML的script元素是一個例外。利用script元素的這個開放策略,網頁可以得到從其他來源動態產生的JSON資料,而這種使用模式就是所謂的JSONP

  對比JSONP和CORS發現以下幾點區別:

  1. JSONP只能實現GET請求,而CORS支持所有類型的HTTP請求
  2. 使用CORS,開發者可以使用普通的XMLHttpRequest發起請求和獲得數據,比起JSONP有更好的錯誤處理
  3. JSONP主要被老的瀏覽器支持,它們往往不支持CORS,而絕大多數現代瀏覽器都已經支持了CORS

  最近做項目,前端使用vue,后端使用spring-boot,前后端完全分離,開發聯調的時候碰到了跨域問題,用了CORS解決跨域。

  查看了spring中幾個cors相關的類:

  org.springframework.web.cors.CorsConfiguration

  org.springframework.web.servlet.config.annotation.CorsRegistry

  org.springframework.web.cors.DefaultCorsProcessor

  其中DefaultCorsProcessor源碼如下:

  1 /*
  2  * Copyright 2002-2016 the original author or authors.
  3  *
  4  * Licensed under the Apache License, Version 2.0 (the "License");
  5  * you may not use this file except in compliance with the License.
  6  * You may obtain a copy of the License at
  7  *
  8  *      http://www.apache.org/licenses/LICENSE-2.0
  9  *
 10  * Unless required by applicable law or agreed to in writing, software
 11  * distributed under the License is distributed on an "AS IS" BASIS,
 12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 13  * See the License for the specific language governing permissions and
 14  * limitations under the License.
 15  */
 16 
 17 package org.springframework.web.cors;
 18 
 19 import java.io.IOException;
 20 import java.nio.charset.Charset;
 21 import java.util.ArrayList;
 22 import java.util.List;
 23 import javax.servlet.http.HttpServletRequest;
 24 import javax.servlet.http.HttpServletResponse;
 25 
 26 import org.apache.commons.logging.Log;
 27 import org.apache.commons.logging.LogFactory;
 28 
 29 import org.springframework.http.HttpHeaders;
 30 import org.springframework.http.HttpMethod;
 31 import org.springframework.http.HttpStatus;
 32 import org.springframework.http.server.ServerHttpRequest;
 33 import org.springframework.http.server.ServerHttpResponse;
 34 import org.springframework.http.server.ServletServerHttpRequest;
 35 import org.springframework.http.server.ServletServerHttpResponse;
 36 import org.springframework.util.CollectionUtils;
 37 import org.springframework.web.util.WebUtils;
 38 
 39 /**
 40  * The default implementation of {@link CorsProcessor}, as defined by the
 41  * <a href="http://www.w3.org/TR/cors/">CORS W3C recommendation</a>.
 42  *
 43  * <p>Note that when input {@link CorsConfiguration} is {@code null}, this
 44  * implementation does not reject simple or actual requests outright but simply
 45  * avoid adding CORS headers to the response. CORS processing is also skipped
 46  * if the response already contains CORS headers, or if the request is detected
 47  * as a same-origin one.
 48  *
 49  * @author Sebastien Deleuze
 50  * @author Rossen Stoyanchev
 51  * @since 4.2
 52  */
 53 public class DefaultCorsProcessor implements CorsProcessor {
 54 
 55     private static final Charset UTF8_CHARSET = Charset.forName("UTF-8");
 56 
 57     private static final Log logger = LogFactory.getLog(DefaultCorsProcessor.class);
 58 
 59 
 60     @Override
 61     @SuppressWarnings("resource")
 62     public boolean processRequest(CorsConfiguration config, HttpServletRequest request, HttpServletResponse response)
 63             throws IOException {
 64 
 65         if (!CorsUtils.isCorsRequest(request)) {
 66             return true;
 67         }
 68 
 69         ServletServerHttpResponse serverResponse = new ServletServerHttpResponse(response);
 70         if (responseHasCors(serverResponse)) {
 71             logger.debug("Skip CORS processing: response already contains \"Access-Control-Allow-Origin\" header");
 72             return true;
 73         }
 74 
 75         ServletServerHttpRequest serverRequest = new ServletServerHttpRequest(request);
 76         if (WebUtils.isSameOrigin(serverRequest)) {
 77             logger.debug("Skip CORS processing: request is from same origin");
 78             return true;
 79         }
 80 
 81         boolean preFlightRequest = CorsUtils.isPreFlightRequest(request);
 82         if (config == null) {
 83             if (preFlightRequest) {
 84                 rejectRequest(serverResponse);
 85                 return false;
 86             }
 87             else {
 88                 return true;
 89             }
 90         }
 91 
 92         return handleInternal(serverRequest, serverResponse, config, preFlightRequest);
 93     }
 94 
 95     private boolean responseHasCors(ServerHttpResponse response) {
 96         try {
 97             return (response.getHeaders().getAccessControlAllowOrigin() != null);
 98         }
 99         catch (NullPointerException npe) {
100             // SPR-11919 and https://issues.jboss.org/browse/WFLY-3474
101             return false;
102         }
103     }
104 
105     /**
106      * Invoked when one of the CORS checks failed.
107      * The default implementation sets the response status to 403 and writes
108      * "Invalid CORS request" to the response.
109      */
110     protected void rejectRequest(ServerHttpResponse response) throws IOException {
111         response.setStatusCode(HttpStatus.FORBIDDEN);
112         response.getBody().write("Invalid CORS request".getBytes(UTF8_CHARSET));
113     }
114 
115     /**
116      * Handle the given request.
117      */
118     protected boolean handleInternal(ServerHttpRequest request, ServerHttpResponse response,
119             CorsConfiguration config, boolean preFlightRequest) throws IOException {
120 
121         String requestOrigin = request.getHeaders().getOrigin();
122         String allowOrigin = checkOrigin(config, requestOrigin);
123 
124         HttpMethod requestMethod = getMethodToUse(request, preFlightRequest);
125         List<HttpMethod> allowMethods = checkMethods(config, requestMethod);
126 
127         List<String> requestHeaders = getHeadersToUse(request, preFlightRequest);
128         List<String> allowHeaders = checkHeaders(config, requestHeaders);
129 
130         if (allowOrigin == null || allowMethods == null || (preFlightRequest && allowHeaders == null)) {
131             rejectRequest(response);
132             return false;
133         }
134 
135         HttpHeaders responseHeaders = response.getHeaders();
136         responseHeaders.setAccessControlAllowOrigin(allowOrigin);
137         responseHeaders.add(HttpHeaders.VARY, HttpHeaders.ORIGIN);
138 
139         if (preFlightRequest) {
140             responseHeaders.setAccessControlAllowMethods(allowMethods);
141         }
142 
143         if (preFlightRequest && !allowHeaders.isEmpty()) {
144             responseHeaders.setAccessControlAllowHeaders(allowHeaders);
145         }
146 
147         if (!CollectionUtils.isEmpty(config.getExposedHeaders())) {
148             responseHeaders.setAccessControlExposeHeaders(config.getExposedHeaders());
149         }
150 
151         if (Boolean.TRUE.equals(config.getAllowCredentials())) {
152             responseHeaders.setAccessControlAllowCredentials(true);
153         }
154 
155         if (preFlightRequest && config.getMaxAge() != null) {
156             responseHeaders.setAccessControlMaxAge(config.getMaxAge());
157         }
158 
159         response.flush();
160         return true;
161     }
162 
163     /**
164      * Check the origin and determine the origin for the response. The default
165      * implementation simply delegates to
166      * {@link org.springframework.web.cors.CorsConfiguration#checkOrigin(String)}.
167      */
168     protected String checkOrigin(CorsConfiguration config, String requestOrigin) {
169         return config.checkOrigin(requestOrigin);
170     }
171 
172     /**
173      * Check the HTTP method and determine the methods for the response of a
174      * pre-flight request. The default implementation simply delegates to
175      * {@link org.springframework.web.cors.CorsConfiguration#checkOrigin(String)}.
176      */
177     protected List<HttpMethod> checkMethods(CorsConfiguration config, HttpMethod requestMethod) {
178         return config.checkHttpMethod(requestMethod);
179     }
180 
181     private HttpMethod getMethodToUse(ServerHttpRequest request, boolean isPreFlight) {
182         return (isPreFlight ? request.getHeaders().getAccessControlRequestMethod() : request.getMethod());
183     }
184 
185     /**
186      * Check the headers and determine the headers for the response of a
187      * pre-flight request. The default implementation simply delegates to
188      * {@link org.springframework.web.cors.CorsConfiguration#checkOrigin(String)}.
189      */
190     protected List<String> checkHeaders(CorsConfiguration config, List<String> requestHeaders) {
191         return config.checkHeaders(requestHeaders);
192     }
193 
194     private List<String> getHeadersToUse(ServerHttpRequest request, boolean isPreFlight) {
195         HttpHeaders headers = request.getHeaders();
196         return (isPreFlight ? headers.getAccessControlRequestHeaders() : new ArrayList<String>(headers.keySet()));
197     }
198 
199 }

 

Spring 中對 CORS 規則的校驗,都是通過委托給 DefaultCorsProcessor實現的。

DefaultCorsProcessor 處理過程如下:

首先 CorsUtils.isCorsRequest(request) 通過request請求頭部是否包含origin頭部來判斷是否是cors請求,若不包含則返回true,若包含則繼續往下執行。

然后 responseHasCors(serverResponse) 判斷response頭部是否已經設置了Access-Control-Allow-Origin,如果已經設置了也不用再進行cors處理,返回true,若沒有設置Access-Control-Allow-Origin則繼續往下執行。

再判斷是否同源,判斷request帶的origin(請求的來源域)和forward(需要請求的資源)否一致(協議,域名,端口號一致),一致則證明不是跨域訪問返回true,不一致則繼續往下執行。

最后檢查是否是預請求,服務端是否有跨域配置,沒有跨域配置且不是預請求則返回true,沒有跨域配置但請求是預請求則拒絕該請求返回false。

若有跨域配置,則檢查請求中origin 是否合法,method 是否合法,header是否合法,如果全部合法,則在 response header中添加響應的字段,並交給負責該請求的類處理,如果不合法,則拒絕該請求。

spring-boot項目中通過繼承org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter,重寫addCorsMappings方法支持跨域

   @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                .allowedOrigins("*")
                .allowCredentials(true)
                .allowedMethods("GET", "POST", "DELETE", "PUT")
                .maxAge(3600);
    }

 

另外后端通過swagger提供接口參數說明及測試接口,瀏覽器也需要配置支持跨域,以chrome為例,設置支持跨域方式:

右鍵點擊chrome瀏覽器快捷方式,在屬性窗口中加入如下配置:--args --disable-web-security --user-data-dir=E:\chromeDevData,然后重啟瀏覽器。

 

 會發現瀏覽器上方有如下提示,證明跨域配置生效。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM