小白初涉跨域问题记录

初涉跨域问题----后台

前言

  • 这次项目是我第一次体验多人合作开发项目,以往做个人练手项目是没有跨域问题的,自然也没有解决此问题的经验,一切还得靠百度搜索他人分享的解决方法,不断采坑,补坑,再继续走,继续踩。

第一次报跨域错误

  • 前前后后报了好多次跨域错误,每次解决完之后继续完善项目,修改代码,再次跑项目的时候跨域问题就又报出来了。
  • 第一次暴露这个问题是我们后台写完接口,报接口文档给前端,前端一调用接口就被拦截了,报了跨域的错。于是我去网上找解决方法,找到了简单的解决跨域问题;这篇博客的解决方法简单有效,还简单介绍了跨域的原因,我照做导入了依赖,在web.xml里面做了配置了之后前端就能正常调用我们的接口了。

再次遇见

问题描述及解决方法

  • 没有项目开发经验的我们,还是模范之前学习servlet和springMVC的时候老师教的不同servlet共享数据的方法----把数据放到session域中。之后才知道,如果我们项目的用户群体大的话,这种做法对后台压力也是非常大的,现在的开发中已经不怎么用这种方法了。不过我们已经用了,就想之后完善的时候再寻求别的实现方式。
  • 而当前首要问题是将数据放到session域中,再一次请求服务器的另一个接口时,session域中找不到之前的放的数据。session域中数据的生命周期是一次会话,而两次请求应该是同一次会话的,但还是找不到,之后发现问题还是因为跨域了。我找到了这篇博客CORS跨域导致session域中数据获取不到

原因:因为服务器判断前端的请求是同一个session的依据是通过网页默认的一个sessionid的cookie判断的,如果存在跨域,cookie值传不过来,也就当下一个请求过来时服务端无法识别为同一个会话,会被当做一个新的会话处理,故找不到session原保存的值。
解决方式:CORS请求默认不发送Cookie和HTTP认证信息。若要发送Cookie,浏览器和服务端都要做设置 。

前端设置:


$.ajax({
        url: url,
        type: 'post',
        data: JSON.stringify(obj),
 
        //加上 xhrFields及crossDomain
        xhrFields: { 
            withCredentials: true//允许带上cookies
        },
        crossDomain: true,
 
        success:function(res){
        },
        error:function(){
        }
    });

说明:这里前端用的是ajax,我们项目前端同学用的是vue,也是可以做这个允许发送cookie的设置的。

后台设置

在用session共享数据的controller上加上@CrossOrigin注解(ssm和springboot都可以)

补充

我们当时按这篇博客教的做之后问题确实是解决了,项目又正常跑起来了。但是当时前端开发用的浏览器是火狐,之后发现火狐可以这么做把问题解决了,但是谷歌浏览器是不行的。同样的代码,用不同的浏览器运行结果是不一样的,又学到了一个知识点。

我们从师兄那里得知,从近几年开始,谷歌的运行机制做了整改,不允许用session来共享数据,可能是为了更好的做前后端分离的模式开发吧。
我们不可能限定用户用什么网站来访问我们的网站。所以session肯定是不能用了,我们还是得用其他方式来共享数据。

再再次遇见

问题描述

  • 谷歌浏览器不支持session共享数据是后来整个项目基本完成的时候才发现的。而我们当时用火狐测试的时候没有问题,所以就先继续做下去了。
  • 原本项目还没有用到token进行权限管理和免密登录,所以跨域问题也没再出现。之后流程跑通了我们了解到了token的功能,便决定用jwt加密方式实现token来进行权限管理和免密登录。加了token之后跨域问题就再再次出现了。
  • 再次遇到问题我们的反应是寻求新的解决方法,而没有想到去完善之前解决跨域的方法。

解决历程

  • 我先是找到了这篇博客解决方法一
    在这篇博客里的解决方式是
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
     //跨域访问CORS
     response.addHeader("Access-Control-Allow-Origin", "*");
     response.addHeader("Access-Control-Allow-Methods", "POST,OPTIONS,PUT,HEAD");
//        response.addHeader("Access-Control-Allow-Headers", "*");
     response.addHeader("Access-Control-Max-Age", "3600000");
     response.addHeader("Access-Control-Allow-Credentials", "true");
     response.addHeader("Access-Control-Allow-Headers", "Authentication,Origin, X-Requested-With, Content-Type, Accept,token");
     //让请求,不被缓存,
     response.setHeader("Cache-Control", "no-cache");
     response.setHeader("Cache-Control", "no-store");
     response.setHeader("Pragma", "no-cache");
     response.setDateHeader("Expires", 0);

     if (request.getMethod().equals("OPTIONS"))
         response.setStatus(HttpServletResponse.SC_OK);

     Login annotation;
     if(handler instanceof HandlerMethod) {
         annotation = ((HandlerMethod) handler).getMethodAnnotation(Login.class);
     }else{
         return true;
     }

     if(annotation == null){
         return true;
     }

     //从header中获取token
     String token = request.getHeader("token");
     //如果header中不存在token,则从参数中获取token
     if(StringUtils.isBlank(token)){
         token = request.getParameter("token");
     }

     //token为空
     if(StringUtils.isBlank(token)){
         throw new RRException("token不能为空");
     }

     //查询token信息
     TokenEntity tokenEntity = tokenService.queryByToken(token);
     System.out.println(tokenEntity.getExpireTime().getTime());
     System.out.println( System.currentTimeMillis());
     System.out.println(tokenEntity.getExpireTime().getTime() < System.currentTimeMillis());
     if(tokenEntity == null || tokenEntity.getExpireTime().getTime() < System.currentTimeMillis()){
         throw new RRException("token失效,请重新登录");
     }

     //设置userId到request里,后续根据userId,获取用户信息
     request.setAttribute(USER_KEY, tokenEntity.getUserId());

     return true;
 }

我们当时一心想先解决问题,想之后再去仔细学习背后的原理,也没注意到,其实这里对response的设置和我们之前在web.xml做的配置是一样的。也就是说有些东西时重复的。可能也就是因为如此导致我们按这篇博客的做法做完并没有解决问题

  • 问题没有解决,我们以为是博客的方法不适用我们当前的情况,于是便继续寻找其他的解决方法。接着便发现了这篇解决方式二 ,添加配置类,重写WebMvcConfigurer的addCorsMappings方法
@Configuration
public class CrossConfig {
    @Bean
    public WebMvcConfigurer getWebMvcConfigurer() {
        WebMvcConfigurer webMvcConfigurer = new WebMvcConfigurer() {
            @Override
            public void addCorsMappings(CorsRegistry registry) {
                registry.addMapping("/**")
                        .allowedOrigins("*")
                        .allowedHeaders("*")
                        .allowedMethods("*")
                        .allowCredentials(false);
            }
        };
        return webMvcConfigurer;
    }

}

之后学习springboot之后才知道这个是解决springboot项目的cors跨域问题的方式,在springbot项目中有没有还不清楚,我们当前用的还是ssm,要等之后重构才知道。
在当时是不行的,原因一个可能是因为我们重复配置了的问题;另一个可能是因为springboot是有自动配置功能的,而当时我们用的的ssm是没有的。

  • 接着,问题依旧没有解决,我们继续寻求新的解决方法。又找到了一篇博客让我们添加一个CORSFilter跨域过滤器

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class CorsFilter implements Filter {


    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
        HttpServletResponse httpServletResponse = (HttpServletResponse) servletResponse;
        //允许所有域名访问
        httpServletResponse.setHeader("Access-Control-Allow-Origin", "*");
        //允许的方法
        httpServletResponse.setHeader("Access-Control-Allow-Methods", "POST,OPTIONS,PUT,HEAD");
        //Access-Control一Max一Age用于CORS相关配置的缓存
        httpServletResponse.setHeader("Access-Control-Max-Age", "3600");
        httpServletResponse.setHeader("Access-Control-Allow-Headers", "Authentication,Origin, X-Requested-With, Content-Type, Accept");
        System.out.println("this is Myfilter,url:"+httpServletRequest.getRequestURI());
        filterChain.doFilter(servletRequest,servletResponse);
    }

    @Override
    public void destroy() {
    }
}

仔细看这个过滤器会发现,这个过滤器的实现思想和我们最最最开始,第一次遇见跨域问题的时候那种在web.xml中配置的思想是一样的,写法也十分相似,只是过滤器的两种不同的添加方式罢了。
但我们也没办法,只能死马当活马医,看见一种方式就尝试一种方式了。结果自然是还是不起作用。

小结:前面的几种方式不起作用还是归咎于他们对跨域的处理方式是一样的,只是实现方式不同。重要的是它们是能解决跨域问题,但却并不能解决加入token导致的跨域情况。我们把错误的方式迭代了三次,注定是要以失败告终的。说这些是踩完坑之后的后话了,身处其中的时候哪知道这些。

  • 接下来这种方式也并没有解决我们的问题,但思路是对的,适用我们遇见的情况,这是我们的细节没有规范好,这是我们自身的问题。而且这篇博客整理的非常详细,里面还有非常详尽的原理讲解,由于当前水平不够,有些地方领会不了,还得之后再来仔细研究。终极解决方案

通过引依赖的方式引一个过滤器

<dependency>
    <groupId>com.thetransactioncompany</groupId>
    <artifactId>cors-filter</artifactId>
</dependency>

再在下图的位置添加上自定义的请求头字段:Authorization
自定义修改位置

或者直接在web.xml中添加过滤器

<filter>
    <filter-name>CORS</filter-name>
    <filter-class>com.thetransactioncompany.cors.CORSFilter</filter-class>
    <init-param>
        <param-name>cors.allowOrigin</param-name>
        <param-value>*</param-value>
    </init-param>
    <init-param>
        <param-name>cors.supportedMethods</param-name>
        <param-value>GET, POST, HEAD, PUT, DELETE</param-value>
    </init-param>
    <init-param>
    <!--这里是重点-->
        <param-name>cors.supportedHeaders</param-name>
        <param-value>Authorization,Accept, Origin,X-Requested-With, Content-Type, Last-Modified</param-value>
    </init-param>
    <init-param>
        <param-name>cors.exposedHeaders</param-name>
        <param-value>Set-Cookie</param-value>
    </init-param>
    <init-param>
        <param-name>cors.supportsCredentials</param-name>
        <param-value>true</param-value>
    </init-param>
</filter>
<filter-mapping>
    <filter-name>CORS</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

这和最开始遇到跨域问题我们的处理方式是基本是一样----在web.xml中的添加过滤器。
不同点也是重点是cors.supportedHeaders这个参数的取值多了一个Authorization。这是解决token导致跨域问题的核心点,加上这个就允许跨域请求中的请求头携带token的字符串。
最开始遇到跨域问题的时候我们并没有在请求中的请求头里携带token的字符串,所以我们配置cors.supportedHeaders的参数里没有Authorization也可以解决跨域问题。
而之后我们需要携带token的字符串了,却没有对应的修改配置,所以跨域问题就又出现了。前文说那前面的几种方式思路是错的就是因为它们也都没有触碰到这个核心点,或许它们对于不携带token的跨域问题都可以有效解决,但只要携带了token,无论迭代多少次都会是徒劳。
到这里已经发现问题的核心点了但还是没有解决问题的原因----前文说的我们不规范,是我们携带token字符串的时候,请求头里的参数名是token,而约定俗成的应该是Authorization。
最后师兄提点了一下,终于是从坑里爬出来了。

总结

  • 当问题解决了之后才知道,卡着我们的点其实真的很简单就能处理好,当然,这是对理解跨域,有经验的人来说。我们确实是被耍得团团转。
  • 我们一开始想着先把问题解决了,之后再去研究原理也是有问题的。因为我们这种想法,导致我们很大一部分时间在做苦力搬运工,把别人的解决方案照搬到自己的项目中,不管它是否重复了;配置是否适用于我们的当前的情况;甚至没去仔细看那些配置的讲解。这种急于求成的思想确实不可取。
  • 我们看一篇博客,把方法搬到项目中用,发现没起作用就抛弃掉寻求他法的做法也不恰当。博主会分享就是自身使用了之后确定无误了。倘若我们早点意识到这一点,去仔细看看博主对于解决方案的讲解,分析他遇见的情况和我们的情况的不同点,为什么他成功了,而到我们这里却不行,或许这问题也不至于卡着我们这么久。
  • 跨域问题可大可小,后续再加深对其原理的学习吧。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值