• java springmvc 前端 跨域问题


    有个朋友在写扇贝插件的时候遇到了跨域问题。
    于是我对解决跨域问题的方式进行了一番探讨。

    问题

    API:查询单词
    URL: https://api.shanbay.com/bdc/search/?word={word}
    请求方式: GET
    参数: {word}, 必须,要查询的单词

    报错为

    XMLHttpRequest cannot load http://localhost/home/saveCandidate. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'null' is therefore not allowed access. The response had HTTP status code 404.

    这就是典型的跨域问题。

    但是我在浏览器里输入URL是可以进行查询单词的操作的,有什么不同,即下面两个问题

    1. 为什么在浏览器地址栏输入URL不会出现跨域问题。
    2. 不在服务器运行的html是否可以完成一次http请求

    经过Google和自己测试

    1. 跨域限制是浏览器行为,不是服务器行为。 浏览器认为地址栏输入时安全的,所以不限制认为是跨域。
    2. 可以,只要服务器配置为所有域都可以进行请求,那么不在服务器运行的HTML就可以完成http请求。

    什么是跨域问题

    同源策略

    同源指的是域名(或IP)协议端口都相同,不同源的客户端脚本(javascript、ActionScript)在没明确授权的情况下,不能读写对方的资源。

    URL解释是否跨域
    http://www.morethink.cn 原来的URL  
    http://www.image.morethink.cn 子域名 跨域(cookie也无法访问)
    http://morethink.cn 不加www 跨域
    https://www.morethink.cn 更改协议 跨域
    http://www.morethink.cn:8080 更改端口号 跨域

    原因

    同源政策的目的,是为了保证用户信息的安全,防止恶意的网站窃取数据。
    设想这样一种情况:A网站是一家银行,用户登录以后,又去浏览其他网站。如果其他网站可以读取A网站的Cookie,会发生什么?
    很显然,如果Cookie包含隐私(比如存款总额),这些信息就会泄漏。更可怕的是,Cookie往往用来保存用户的登录状态,如果用户没有退出登录,其他网站就可以冒充用户,为所欲为。因为浏览器同时还规定,提交表单不受同源政策的限制。
    由此可见,"同源政策"是必需的,否则 Cookie 可以共享,互联网就毫无安全可言了。

    同源策略限制以下几种行为:

    1. Cookie、LocalStorage 和 IndexDB 无法读取
    2. DOM 和 Js对象无法获得
    3. AJAX 请求不能发送

    模拟跨域问题

    测试URL为 http://localhost:80/home/allProductions

    可以直接在浏览器console中执行

    var xhr = new XMLHttpRequest();
    xhr.open('GET', 'http://localhost:80/home/allProductions',true);
    xhr.send();
    xhr.onreadystatechange=function() {
        if(xhr.readyState == 4) {
            if(xhr.status == 200) {
              console.log(JSON.parse(xhr.responseText));
            }
        }
    }

    在任意网站打开控制台,执行此段代码可以模拟跨域请求。

    在知乎控制台打开报错如下

    Mixed Content: The page at 'https://www.zhihu.com/question/26376773' was loaded over HTTPS, but requested an insecure XMLHttpRequest endpoint 'http://localhost/home/allProductions'. This request has been blocked; the content must be served over HTTPS.

    因为知乎是https,报错与普通的http协议不同。

    再澄清一下跨域问题:

    1. 并非浏览器限制了发起跨站请求,而是跨站请求可以正常发起,但是返回结果被浏览器拦截了。最好的例子是CRSF跨站攻击原理,无论是否跨域,请求已经发送到了后端服务器!
    2. 但是,有些浏览器不允许从HTTPS的域跨域访问HTTP,比如Chrome和Firefox,这些浏览器在请求还未发出的时候就会拦截请求,这是一个特例。

    在博客园控制台打开报错如下

    XMLHttpRequest cannot load http://localhost/home/allProductions. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://www.cnblogs.com' is therefore not allowed access.

    怎么解决跨域问题

    解决方案有很多

    1. 通过jsonp跨域
    2. document.domain + iframe跨域
    3. location.hash + iframe
    4. window.name + iframe跨域
    5. postMessage跨域
    6. 跨域资源共享(CORS)
    7. 前端通过Nginx解决跨域问题
    8. nodejs中间件代理跨域
    9. WebSocket协议跨域

    这里主要介绍SpringMVC解决跨域问题的方式。

    1. JSONP
    2. CORS
    3. WebSocket

    JSONP

    可以直接参考Spring MVC 4.1 支持jsonp进行配置你的SpringMVC注解

    JSONP 原理

    我虽然请求不了json数据,但是我可以请求一个Content-Typeapplication/javascript的JavaScript对象,这样就可以避免浏览器的同源策略。

    就是当服务器接受到名为jsonp或者callback的参数时,返回Content-Type: application/javascript的结果,从而避免浏览器的同源策略检测。

    1. 在控制台中直接进行测试你的jsonp是否配置成功
                function println(data) {
                    console.log(data);
                }
    
                var url = "http://localhost:80/home/allProductions?&callback=println";
                // 创建script标签,设置其属性
                var script = document.createElement('script');
                script.setAttribute('src', url);
                // 把script标签加入head,此时调用开始
                document.getElementsByTagName('head')[0].appendChild(script);

    1. 使用JQuery测试你的jsonp是否配置成功(需要自己添加jQuery.js)
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
        <script type="text/javascript" src="js/jquery.min.js"></script>
        <script type="text/javascript">
            function println(data) {
                console.log(data);
                console.log('print');
            }
          function jsonp_test() {
                $.ajax({
                    type: "get",
                    url: "http://localhost:80/home/allProductions",
                    dataType: "jsonp",
                    jsonp: "callback",//传递给请求处理程序或页面的,用以获得jsonp回调函数名的参数名(一般默认为:callback)
                    jsonpCallback: "println", //返回后调用的处理函数
                    error: function () { //请求出错的处理
                        alert("请求出错");
                    }
                });
            }
        </script>
    </head>
    <body onload="jsonp_test()">
    </body>
    </html>

    CORS

    CORS是一个W3C标准,全称是"跨域资源共享"(Cross-origin resource sharing)。
    它允许浏览器向跨源服务器,发出XMLHttpRequest请求,从而克服了AJAX只能同源使用的限制。

    CORS需要浏览器和服务器同时支持。

    1. 所有浏览器都支持该功能,IE浏览器不能低于IE10。
      整个CORS通信过程,都是浏览器自动完成,不需要用户参与。 对于开发者来说,CORS通信与同源的AJAX通信没有差别,代码完全一样。浏览器一旦发现AJAX请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉。
    2. 实现CORS通信的关键是服务器。只要服务器实现了CORS接口,就可以跨源通信。

    即CORS与普通请求代码一样。

    CORS与JSONP相比

    1. JSONP只能实现GET请求,而CORS支持所有类型的HTTP请求。
    2. 使用CORS,开发者可以使用普通的XMLHttpRequest发起请求和获得数据,比起JSONP有更好的错误处理。
    3. JSONP主要被老的浏览器支持,它们往往不支持CORS,而绝大多数现代浏览器都已经支持了CORS。

    @CrossOrigin注解

    此注解既可用于方法也可用于类

    源码如下:

        @CrossOrigin(origins = "http://www.zhihu.com")
        @RequestMapping(value = "/allProductions", method = RequestMethod.GET)
        public Result getAllOldProductions() {
    
        }

    @CrossOrigin注解既可注解在方法上,也可注解在类上。

    完成配置之后

    XML全局配置

    所有跨域请求都可以访问

    <mvc:cors>
        <mvc:mapping path="/**" />
    </mvc:cors>

    更加细粒度的配置:

    <mvc:cors>
    
        <mvc:mapping path="/api/**"
            allowed-origins="http://domain1.com, http://domain2.com"
            allowed-methods="GET, PUT"
            allowed-headers="header1, header2, header3"
            exposed-headers="header1, header2" allow-credentials="false"
            max-age="123" />
    
        <mvc:mapping path="/resources/**"
            allowed-origins="http://domain1.com" />
    
    </mvc:cors>

    WebSocket

    WebSocket是一种通信协议,使用ws://(非加密)和wss://(加密)作为协议前缀,在2008年诞生,2011年成为国际标准。所有浏览器都已经支持了

    它的最大特点就是,服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,是真正的双向平等对话,属于服务器推送技术的一种。

    该协议不实行同源政策,只要服务器支持,就可以通过它进行跨源通信

    请求头信息:(多了个 origin)

    GET /chat HTTP/1.1
    Host: server.example.com
    Upgrade: websocket
    Connection: Upgrade
    Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==
    Sec-WebSocket-Protocol: chat, superchat
    Sec-WebSocket-Version: 13
    Origin: http://example.com

    响应头:(如果origin在白名单内)

    HTTP/1.1 101 Switching Protocols
    Upgrade: websocket
    Connection: Upgrade
    Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=
    Sec-WebSocket-Protocol: chat

    相比于HTTP/2

    HTTP/2只是对HTML、CSS等JS资源的传输方式进行了优化,并没有提供新的JS API,不能用于实时传输消息,也无法推送指定的信息。

    参考文档

    1. 跨域
    2. SpringMVC 跨域解决方法
    3. 前端常见跨域解决方案(全)

    1. CORS 简介

    同源策略(same origin policy)是浏览器安全的基石。在同源策略的限制下,非同源的网站之间不能发送 ajax 请求的。

    为了解决这个问题,w3c 提出了跨源资源共享,即 CORS(Cross-Origin Resource Sharing)。

    CORS 做到了两点:

    1. 不破坏即有规则
    2. 服务器实现了 CORS 接口,就可以跨源通信

    基于这两点,CORS 将请求分为两类:简单请求和非简单请求。

    1.1 简单请求

    可以先看下 CORS 出现前的情况:跨源时能够通过 script 或者 image 标签触发 GET 请求或通过表单发送一条 POST 请求,但这两种请求 HTTP 头信息中都不能包含任何自定义字段。

    简单请求对应该规则,因此对简单请求的定义为:

    请求方法是 HEADGET 或 POST 且 HTTP 头信息不超过以下几个字段:AcceptAccept-LanguageContent-LanguageLast-Event-IDContent-Type(只限于 application/x-www-form-urlencodedmultipart/form-datatext/plain)。

    比如有一个简单请求:

    1.  
      GET /test HTTP/1.1
    2.  
      Accept: */*
    3.  
      Accept-Encoding: gzip, deflate, sdch, br
    4.  
      Origin: http://www.examples.com
    5.  
      Host: www.examples.com

    对于这样的简单请求,CORS 的策略是请求时,**在头信息中添加一个 Origin 字段**,服务器收到请求后,根据该字段判断是否允许该请求。

    1. 如果允许,则在 HTTP 头信息中添加 Access-Control-Allow-Origin 字段,并返回正确的结果
    2. 如果不允许,则不在头信息中添加 Access-Control-Allow-Origin 字段。

    浏览器先于用户得到返回结果,根据有无 Access-Control-Allow-Origin 字段来决定是否拦截该返回结果。

    对于 CORS 出现前的一些服务,CORS 对他们的影响分两种情况:

    1. script 或者 image 触发的 GET 请求不包含 Origin 头,所以不受到 CORS 的限制,依旧可用。
    2. 如果是 ajax 请求,HTTP 头信息中会包含 Origin 字段,由于服务器没有做任何配置,所以返回结果不会包含 Access-Control-Allow-Origin,因此返回结果会被浏览器拦截,接口依旧不可以被 ajax 跨源访问。

    可以看出,CORS 的出现,没有对”旧的“服务造成任何影响。

    另外,除了提到的 Access-Control-Allow-Origin 还有几个字段用于描述 CORS 返回结果:

    1. Access-Control-Allow-Credentials: 可选,用户是否可以发送、处理 cookie。
    2. Access-Control-Expose-Headers:可选,可以让用户拿到的字段。有几个字段无论设置与否都可以拿到的,包括:Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma。

    1.2 非简单请求

    除了简单请求之外的请求,就是非简单请求。

    对于非简单请求的跨源请求,**浏览器会在真实请求发出前**,增加一次 OPTION 请求,称为预检请求(preflight request)。预检请求将真实请求的信息,包括请求方法、自定义头字段、源信息添加到 HTTP 头信息字段中,询问服务器是否允许这样的操作。

    比如对于 DELETE 请求:

    1.  
      OPTIONS /test HTTP/1.1
    2.  
      Origin: http://www.examples.com
    3.  
      Access-Control-Request-Method: DELETE
    4.  
      Access-Control-Request-Headers: X-Custom-Header
    5.  
      Host: www.examples.com

    与 CORS 相关的字段有:

    1. Access-Control-Request-Method: 真实请求使用的 HTTP 方法。
    2. Access-Control-Request-Headers: 真实请求中包含的自定义头字段。

    服务器收到请求时,需要分别对 Origin、Access-Control-Request-Method、Access-Control-Request-Headers 进行验证,验证通过后,会在返回 Http 头信息中添加

    1.  
      Access-Control-Allow-Origin: http://www.examples.com
    2.  
      Access-Control-Allow-Methods: GET, POST, PUT, DELETE
    3.  
      Access-Control-Allow-Headers: X-Custom-Header
    4.  
      Access-Control-Allow-Credentials: true
    5.  
      Access-Control-Max-Age: 1728000

    他们的含义分别是:

    1. Access-Control-Allow-Methods: 真实请求允许的方法
    2. Access-Control-Allow-Headers: 服务器允许使用的字段
    3. Access-Control-Allow-Credentials: 是否允许用户发送、处理 cookie
    4. Access-Control-Max-Age: 预检请求的有效期,单位为秒。有效期内,不会重复发送预检请求

    当预检请求通过后,浏览器会发送真实请求到服务器。这就实现了跨源请求。

    了解完 CORS,接下来我们来搭建简单的 Spring MVC 服务,并进一步了解 Spring MVC 如何配置 CORS。

    2. Spring MVC 环境搭建

    打开 http://start.spring.io/,添加 Web Dependency,然后选择 Generate Project,下载 zip 文件,就得到了一个 spring boot demo。

    图片

    解压 zip 文件,双击 pom.xml 打开或用 IDEA、Eclipse 将项目按照 maven 导入。

    根据 Group、Artifact、Dependencies 填写的不同,项目目录结构可能有些许差别,我的项目结构如下:

    1.  
      ├── src
    2.  
      │ ├── main/java
    3.  
      | └── net/xiayule/spring/cors
    4.  
      │ | └── SpringBootCorsTestApplication.java
    5.  
      | └── resources
    6.  
      | ├── static
    7.  
      | ├── templates
    8.  
      | └── application.properties
    9.  
      |
    10.  
      └── pom.xml
    11.  
       

    我们需要关心的只有 SpringBootCorsTestApplication.java。在 SpringBootCorsTestApplication.java 添加以下代码:

    1.  
      @RestController
    2.  
      @SpringBootApplication
    3.  
      public class SpringBootCorsTestApplication {
    4.  
       
    5.  
      @RequestMapping(value = "/test")
    6.  
      public String greetings() {
    7.  
      return "{"project":"just a test"}";
    8.  
      }
    9.  
       
    10.  
      public static void main(String[] args) {
    11.  
      SpringApplication.run(SpringBootCorsTestApplication.class, args);
    12.  
      }
    13.  
      }

    @RequestMapping(value = "/test") 的含义是该方法接受来自 /test 的请求,并且提供了对 HTTP 的 GET、POST、DELETE 等方法的支持。

    运行项目的方法为,在项目根目录执行 mvn spring-boot:run 启动应用,打开浏览器访问 http://localhost:8080 即可看到效果:

    图片

    后端服务搭建好了,接着实现前端。为了简单,直接创建一个 test.html 文件,添加以下内容:

    1.  
      <!DOCTYPE html>
    2.  
      <html>
    3.  
      <head>
    4.  
      <title>Hello CORS</title>
    5.  
      <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
    6.  
       
    7.  
      <script>
    8.  
      $(document).ready(function() {
    9.  
      $.ajax({
    10.  
      url: "http://localhost:8080/test",
    11.  
      method: "POST",
    12.  
      contentType: "application/json; charset=utf-8"
    13.  
      }).then(function(data, status, jqxhr) {
    14.  
      alert(data)
    15.  
      });
    16.  
      });
    17.  
      </script>
    18.  
      </head>
    19.  
      <body>
    20.  
      </body>
    21.  
      </html>

    使用浏览器打开该文件,就会触发一条向 http://localhost:8080 的请求。可以通过修改上面代码的 method,来触发不同类型的请求。

    由于是直接使用浏览器打开,**网页的源为 null**, 当向源 http://localhost:8080 请求时,就变成了跨源请求,因此如果后端不加 CORS 的配置,返回的 HTTP 头信息中不会包含 Access-Control-Allow-Origin,因此浏览器会报出如下错误:

    图片

    3. 配置 CORS

    一个应用可能会有多个 CORS 配置,并且可以设置每个 CORS 配置针对一个接口或一系列接口或者对所有接口生效。

    举例来说,我们需要:

    1. 让 /test 接口支持跨源访问,而 /test/1 或 /api 等其它接口不支持跨源访问
    2. 让 /test/* 这一类接口支持跨源访问,而 /api 等其它接口不支持跨源访问
    3. 站点所有的接口都支持跨源访问

    对第一种情况,如果想要对某一接口配置 CORS,可以在方法上添加 CrossOrigin 注解:

    1.  
      @CrossOrigin(origins = {"http://localhost:9000", "null"})
    2.  
      @RequestMapping(value = "/test", method = RequestMethod.GET)
    3.  
      public String greetings() {
    4.  
      return "{"project":"just a test"}";
    5.  
      }

    第二种情况,如果想对一系列接口添加 CORS 配置,可以在类上添加注解,对该类声明所有接口都有效:

    1.  
      CrossOrigin(origins = {"http://localhost:9000", "null"})
    2.  
      @RestController
    3.  
      @SpringBootApplication
    4.  
      public class SpringBootCorsTestApplication {
    5.  
      // xxx
    6.  
      }

    第三种情况,添加全局配置,则需要添加一个配置类:

    1.  
      @Configuration
    2.  
      public class WebConfig extends WebMvcConfigurerAdapter {
    3.  
       
    4.  
      @Override
    5.  
      public void addCorsMappings(CorsRegistry registry) {
    6.  
      registry.addMapping("/**")
    7.  
      .allowedOrigins("http://localhost:9000", "null")
    8.  
      .allowedMethods("POST", "GET", "PUT", "OPTIONS", "DELETE")
    9.  
      .maxAge(3600)
    10.  
      .allowCredentials(true);
    11.  
      }
    12.  
      }

    另外,还可以通过添加 Filter 的方式,配置 CORS 规则,并手动指定对哪些接口有效。

    1.  
      @Bean
    2.  
      public FilterRegistrationBean corsFilter() {
    3.  
      UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
    4.  
      CorsConfiguration config = new CorsConfiguration();
    5.  
      config.setAllowCredentials(true); config.addAllowedOrigin("http://localhost:9000");
    6.  
      config.addAllowedOrigin("null");
    7.  
      config.addAllowedHeader("*");
    8.  
      config.addAllowedMethod("*");
    9.  
      source.registerCorsConfiguration("/**", config); // CORS 配置对所有接口都有效
    10.  
      FilterRegistrationBean bean = newFilterRegistrationBean(new CorsFilter(source));
    11.  
      bean.setOrder(0);
    12.  
      return bean;
    13.  
      }

    4. 实现剖析

    无论是通过哪种方式配置 CORS,其实都是在构造 CorsConfiguration。
    一个 CORS 配置用一个 CorsConfiguration 类来表示,它的定义如下:

    1.  
      public class CorsConfiguration {
    2.  
      private List<String> allowedOrigins;
    3.  
      private List<String> allowedMethods;
    4.  
      private List<String> allowedHeaders;
    5.  
      private List<String> exposedHeaders;
    6.  
      private Boolean allowCredentials;
    7.  
      private Long maxAge;
    8.  
      }

    Spring MVC 中对 CORS 规则的校验,都是通过委托给 DefaultCorsProcessor 实现的。

    DefaultCorsProcessor 处理过程如下:

    1. 判断依据是 Header 中是否包含 Origin。如果包含则说明为 CORS 请求,转到 2;否则,说明不是 CORS 请求,不作任何处理。
    2. 判断 response 的 Header 是否已经包含 Access-Control-Allow-Origin,如果包含,证明已经被处理过了, 转到 3,否则不再处理。
    3. 判断是否同源,如果是则转交给负责该请求的类处理
    4. 是否配置了 CORS 规则,如果没有配置,且是预检请求,则拒绝该请求,如果没有配置,且不是预检请求,则交给负责该请求的类处理。如果配置了,则对该请求进行校验。

    校验就是根据 CorsConfiguration 这个类的配置进行判断:

    1. 判断 origin 是否合法
    2. 判断 method 是否合法
    3. 判断 header 是否合法
    4. 如果全部合法,则在 response header 中添加响应的字段,并交给负责该请求的类处理,如果不合法,则拒绝该请求。

    5. 总结

    本文介绍了 CORS 的知识以及如何在 Spring MVC 中配置 CORS。

    最终在项目中采用springmvc的cors 实现了跨域

  • 相关阅读:
    单个对象的内存管理分析
    在eclipse中引入jquery.js文件报错的解决方案
    ajax复习
    jquery介绍
    ajax调试小技巧
    ajax实现聊天室功能
    ajax(2)
    ajax经典案例--省市联动
    ajax技术返回json如何处理
    ajax如何处理返回的数据格式是xml的情况
  • 原文地址:https://www.cnblogs.com/cfas/p/9401599.html
Copyright © 2020-2023  润新知