【SpringMVC】与权限拦截器冲突导致的Cors跨域设置失效问题

问题描述

前端域名FE.com向后端域名BE.com分别请求访问优惠券的列表和提交新增的优惠券,API设计所用的Method分别为Get和Post,结果为前一次访问成功而后一次访问失败。这两次请求都是跨域请求,其中请求1包含一个Get请求,请求2本应该包含一个Options请求和一个Post请求,但是只发生了Options请求。

后端Cors配置

CORS使用SpringMVC自带的mvc:cors标签全局配置,权限检测则实现自定义的拦截器校验Cookie中的Token信息。

<mvc:cors>
        <mvc:mapping allow-credentials="true" allowed-headers="Content-Type" 
        allowed-methods="POST, GET, OPTIONS, DELETE, PUT, HEAD" 
        allowed-origins="http://test.i.meituan.com" 
        max-age="3600" path="/**"/>
</mvc:cors>
<mvc:interceptors>
   <mvc:interceptor>
       <mvc:mapping path="/ajax/shop/**"/> <!--sso回调处理-->
       <bean class="com.dianping.orderdish.framework.interceptor.ShopSsoInterceptor"/>
   </mvc:interceptor>
</mvc:interceptors>

在复杂请求的情况下(即请求2),由于预检请求不会包含Cookie信息(浏览器本身的实现决定其是否发送Cookie,前端无法控制,并且Chrome是不发送的),因此被权限拦截器提前结束,没有输出包含指定头部信息的响应。而一个被浏览器认为合格的预检请求响应必须包含如下的Http头部。

Access-Control-Allow-Origin: http://test.i.meituan.com
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: Content-Type
Access-Control-Max-Age: 86400

为什么会发生提前结束这种情况?可以对dispatch()方法进行分析。

Handler和拦截器的执行顺序

DispatchServlet.doDispatch()方法是SpringMVC的核心入口方法,分析发现所有的拦截器的preHandle()方法的执行都在实际Handler的方法(比如某个API对应的业务方法)之前,其中任意拦截器返回false都会跳过后续所有处理过程。而SpringMVC对预检请求的处理则在PreFlightHandler.handleRequest()中处理,在整个处理链条中出于后置位。由于预检请求中不带Cookie,因此先被权限拦截器拦截。

// Determine handler for the current request.
mappedHandler = getHandler(processedRequest);
//省略代码
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
    return;
}
// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

@CrossCors时序分析

除了在XML中设置全局的CORS配置,Spring提供了@CrossCors,可以注解在类和方法上,是一种更细粒度的配置方法。SpringMVC把其处理细节包装在getHandler(processedRequest);中,具体逻辑可以简述为如果请求是预检请求,则返回PreFlightHander;否则在拦截器末尾添加一个CorsInterceptor。因此可以看出,@CrossCors相关的执行和全局的mvc:cors类似,也是滞后于权限拦截器的。

解决方案

方案1:使用Spring-Web自带的CorsFilter

由于CorsFilter是定义在Web容器中的过滤器(实现了javax.servlet.Filter),因此其执行顺序先于Servlet,而SpringMVC的入口是DispatchServlet,因此该Filter会先于SpringMVC的所有拦截器执行。分析代码可知,CorsFilter会获取单个请求对应的Cors配置做相应的处理。因此可以和mvc:cors很好的结合,不需要增加额外的代码。(勘误:CorsFilter的构造需要CorsConfigurationSource实例,并且发生在SpringMVC配置文件解析之前,因此只能放在Spring的配置文件中,否则会发生找不到bean的异常;又由于mvc:cors的解析和Spring核心的解析不共享相同的ParserContext上下文,因此SpringMVC的跨域设置不能植入到CorsFilter中)

if (CorsUtils.isCorsRequest(request)) {
    CorsConfiguration corsConfiguration = this.configSource.getCorsConfiguration(request);
    if (corsConfiguration != null) {
        boolean isValid = this.processor.processRequest(corsConfiguration, request, response);
        if (!isValid || CorsUtils.isPreFlightRequest(request)) {
            return;
        }
    }
}
filterChain.doFilter(request, response);
方案2:自己实现Interceptor

与方案1类似,把插入Http头的功能实现为SpringMVC的拦截器,然后在mvc:interceptors中声明为第一顺位。

public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
   if (request.getHeader(HttpHeaders.ORIGIN) != null) {
        response.addHeader("Access-Control-Allow-Origin", "http://test.i.meituan.com");
        response.addHeader("Access-Control-Allow-Credentials", "true");
        response.addHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE, PUT, HEAD");
        response.addHeader("Access-Control-Allow-Headers", "Content-Type");
        response.addHeader("Access-Control-Max-Age", "3600");
   }
   return true;
}
方案3:增强自定义Interceptor

利用AOP环绕增强所有自定义拦截器的preHandle()方法,令其跳过预检请求的拦截。

@Around(value = "cut()")
public Object processTx(ProceedingJoinPoint jp) throws Throwable {
   HttpServletRequest request = (HttpServletRequest) jp.getArgs()[0];
   if (request != null && CorsUtils.isPreFlightRequest(request)) {
       return true;
   } else {
       return jp.proceed();
   }
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
提供的源码资源涵盖了安卓应用、小程序、Python应用和Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值