zoukankan      html  css  js  c++  java
  • 记一次springMVC的跨域解决方案

    日期:2019年5月18日

    事情原因:由于微信小程序的开发只有测试环境,而后台提供接口的环境是开发环境;两个环境的域名不同,导致前端开发产生了跨域问题;

    理论概念:

      1、同源策略:同源策略是浏览器的安全基石,存储在浏览器中的数据(cookie)必须在同域(相同域名)下才能任意的读取,而非同域下的浏览器资源是不允许任意操作的。即同源策略下,非同源的网站之间不能发送Ajax请求;如果没有同源策略,会导致浏览器中的数据可以被任何来源请求进行访问与资源操作,存储在浏览器中的数据就没有任何安全可言;

          2、域名:相同域名(www.baidu .com;所有的请求都来自于这个域名下,即为同域下);

                       不同域名(www.baidu.com,www.google.com;来自google的请求不能直接操作baidu域下的资源);

          3、同源:两个页面的【协议】、【端口(如果指定了)】、【主机】都相同,则这两个页面具有相同的源。有一个元素不同,则是不同源的;

      4、跨域资源共享(CORS):

                 1)它是一份浏览器技术的规范,提供Web服务从不同域传来沙盒脚本的方法,泳衣避开浏览器的同源策略,JSONP模式的现代版(相关JSONP的知识请自己查阅,本文不再做解释说明);

         2)实现思路是使用自定义的HTTP头部制定一个可以互相认可的约定(例如浏览器给服务器传送一个头信息A,服务器给浏览器传送一个头信息B,就可以不受同源策略的限制),从而决定请求与响应成功与否;

         3)CORS不关心安全问题,只解决资源共享的问题;安全性可以从请求时效性,token验证,IP验证,来源验证等方面考虑安全问题;

      5、CORS与JSONP的区别:

                  1)JSONP只能实现GET请求,而CORS支持所有的请求;

          2)使用CORS,前端开发者可以使用普通的XMLHttpRequest发起的请求和响应的数据,比JSONP有更好的错误处理;

          3)JSONP主要被版本比较老的浏览器支持,这些老版本的浏览器往往不支持CORS(IE6,IE7,Open mini),而绝大多数现代浏览器都已经支持了CORS;

      6、注:在跨域请求中,请求是可以发送到服务器的,服务器也可以响应数据,但服务器响应到浏览器的数据,浏览器拒绝解析(不太确定是拒绝接受还是拒绝解析?),导致的资源无法共享;

    解决方案

    只要解决跨域资源共享即可,CROS解决方案主要有以下几种:

      1、自定义CORSFilter(Intercepter):适用于设置单一的(全部)授权访问,所有配置固定,操作简单,不根据请求类型做不同的处理,粒度比较大;

        
     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 }
    View Code

      2、Nginx代理配置(配置在location中)

        
     1 #
     2 # Wide-open CORS config for nginx
     3 #
     4 location / {
     5      if ($request_method = 'OPTIONS') {
     6         add_header 'Access-Control-Allow-Origin' '*';
     7         add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
     8         #
     9         # Custom headers and headers various browsers *should* be OK with but aren't
    10         #
    11         add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';
    12         #
    13         # Tell client that this pre-flight info is valid for 20 days
    14         #
    15         add_header 'Access-Control-Max-Age' 1728000;
    16         add_header 'Content-Type' 'text/plain charset=UTF-8';
    17         add_header 'Content-Length' 0;
    18         return 204;
    19      }
    20      if ($request_method = 'POST') {
    21         add_header 'Access-Control-Allow-Origin' '*';
    22         add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
    23         add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';
    24      }
    25      if ($request_method = 'GET') {
    26         add_header 'Access-Control-Allow-Origin' '*';
    27         add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
    28         add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';
    29      }
    30 }
    View Code(细粒度配置)
        
    1 #js跨域支持
    2                 #add_header 'Access-Control-Allow-Origin' '*';
    3                 #add_header 'Access-Control-Allow-Credentials' 'true';
    4                 #add_header 'Access-Control-Allow-Headers' 'Authorization,Content-Type,Accept,Origin,User-Agent,DNT,Cache-Control,X-Mx-ReqToken,X-Requested-With';
    5                 #add_header 'Access-Control-Allow-Methods' 'GET,POST,OPTIONS';
    View Code(粗粒度配置)

      3、使用spring框架提供的注解@CrossOrigin

        1)第一种情况,对接口的粒度进行配置    

        
    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 }
    View Code(接口)

        2)第二种情况,对类的粒度进行配置  

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

      4、全局配置

        
     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 }
    View Code

      5、支持多域名配置的CORSFilter

        因为知道已经有可以用的库可以解决,所以就没重复造轮子了。其实因为懒,看看别人的源码算了.在mvnrepository搜索cors-filter,目前也就两个可以用

        org.ebaysf.web 的 cors-filter,项目地址:https://github.com/ebay/cors-filter

                com.thetransactioncompany的 cors-filter,项目地址:http://software.dzhuvinov.com/cors-filter.htm

    -------------------------------------------------------------------------------------------------

    Ps:与前端的交互:

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

      比如对于 DELETE 请求:

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

      与 CORS 相关的字段有:

                         Access-Control-Request-Method: 真实请求使用的 HTTP 方法;

                         Access-Control-Request-Headers: 真实请求中包含的自定义头字段。

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

                        Access-Control-Allow-Origin: http://www.examples.com
                        Access-Control-Allow-Methods: GET, POST, PUT, DELETE
                        Access-Control-Allow-Headers: X-Custom-Header
                        Access-Control-Allow-Credentials: true
                        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: 预检请求的有效期,单位为秒。有效期内,不会重复发送预检请求

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

    -------------------------------------------------------------------------------------------------

    总结:以上提供的几种方案都可以解决跨域问题,视自己业务的实际情况选择方案;个人选择的是最简单的Nginx,不对代码进行任何的修改,直接配置在代理中;个人觉得要达到比较细粒度的各种优化,推荐使用第五种方案,但同时会会增加学习成本,对时间比较充足的同学可以研究研究;

      虽然跨域调用的问题解决了,但是后来发现以上方法并不能解决cookies的跨域问题,导致在A域名下点击登录后直接跳转到B域名下已登陆后的一个页面,会失败。主要问题是在A域名下获取到的cookie不能共享到B域名下,导致跳转B域名下已登录的页面会被判定没有coolie(没有登录),会导致直接跳转到B域名下的登录页面进行重新登录,即在A域名下的登录失败了;思考了几种解决方案:①后端修改登录接口的代码逻辑,适配出现的这种情况(创建新的cookie,或者由后端进行路由),②在A域名下点击登录时,直接跳转到B域名下的一个和A域名页面一样的登录页面,不给用户感知,但域名会发生变化,在B域名下的登录页面进行实际的登录操作,就不会再产生cookie跨域的问题了,③直接使用nginx进行路由:

    以上三种解决思路可供参考,我自己选用的第二种,如果后端和运维支持比较给力,推荐选用第一种或者第三种解决方案!!!!

    参考Blog:1、https://www.cnblogs.com/Coding-net/p/6207122.html

                 2、http://www.cnblogs.com/sloong/p/cors.html

  • 相关阅读:
    NFC性价比高频读卡器首选方案:FM17550
    关于ESP8266和ESP8285的对比
    有没有比NRF51822更好的智能穿戴蓝牙方案
    zigbee CC2530首选方案模组:TZU06A1
    PAN3501兼容AS3933-BTST
    NRF51822和NRF52832的主要区别
    集成模拟温度传感器低成本2.4G CC2500RGPR 中文手册
    USB2.0主机控制器 UPD720114 简单详解
    存储器HK1225-7EQ 使用说明书资料
    爬虫 + 数据
  • 原文地址:https://www.cnblogs.com/wwcxBlog/p/10884998.html
Copyright © 2011-2022 走看看