zoukankan      html  css  js  c++  java
  • 前后端分离 开发环境通过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,然后重启浏览器。

     

     会发现浏览器上方有如下提示,证明跨域配置生效。

  • 相关阅读:
    C++ MessageBox()
    magento 获取attribute的所有option
    在mangento后台调用wysiwyg编辑器
    正则式匹配数字字符串中的数字
    use magento default datepicker plugin in your project
    Colorbox cannot load the image added by js
    deal with 'non-admin area' warn
    How to use pagination in Magento
    Magento
    Magento布局layout.xml文件详解
  • 原文地址:https://www.cnblogs.com/zhangcybb/p/10409356.html
Copyright © 2011-2022 走看看