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

news/2024/6/28 18:23:04

问题描述

前端域名FE.com向后端域名BE.com分别请求访问优惠券的列表和提交新增的优惠券,API设计所用的Method分别为GetPost,结果为前一次访问成功而后一次访问失败。这两次请求都是跨域请求,其中请求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();}
}

Reference

  1. HTTP访问控制(CORS)
  2. Token endpoint should permit all HTTP OPTIONS requests to support CORS. #330
  3. how can i convince spring 4.2 to pass options request through to the controller

http://lihuaxi.xjx100.cn/news/279653.html

相关文章

酷炫 | 比较6种类型和14种数据可视化工具

作者 | 神秘的铁头娃来源 | FineReport编译 | AI算法与图像处理&#xff08;ID&#xff1a;AI_study&#xff09;【导读】随着大数据时代的降临&#xff0c;企业和组织对数据的需求发生变化&#xff0c;因此五花八门的可视化工具开始层出不穷。那么面对这么多的可视化工具&…

单片机学不会怎么办?单片机从哪里开始学?

大家好&#xff0c;我是无际单片机编程的徐导。 说起单片机学不会怎么办&#xff1f;就想起我自己一些学习的辛酸史。 我做单片机开发10余年了&#xff0c;单片机硬软件开发自然是很熟练了。 但做其他的工作可是一窍不通&#xff0c;在上家公司工作的时候&#xff0c;公司有…

Linux 技巧:让进程在后台可靠运行的几种方法

想让进程在断开连接后依然保持运行&#xff1f;如果该进程已经开始运行了该如何补救&#xff1f; 如果有大量这类需求如何简化操作&#xff1f; 我们经常会碰到这样的问题&#xff0c;用 telnet/ssh 登录了远程的 Linux 服务器&#xff0c;运行了一些耗时较长的任务&#xff0c…

Spring Cloud Alibba教程:Sentinel的使用

点击上方“方志朋”&#xff0c;选择“置顶公众号”技术文章第一时间送达&#xff01;什么是SentinelSentinel&#xff0c;中文翻译为哨兵&#xff0c;是为微服务提供流量控制、熔断降级的功能&#xff0c;它和Hystrix提供的功能一样&#xff0c;可以有效的解决微服务调用产生的…

入机器学习大坑,需要什么样的数学水平?

选自medium作者&#xff1a;Benjamin Obi Tayo机器之心编译 参与&#xff1a;小舟、杜伟作为一门基础性学科&#xff0c;数学在数据科学和机器学习领域都发挥着不可或缺的作用。数学基础是理解各种算法的先决条件&#xff0c;也将帮助我们更深入透彻地了解算法的内在原理。所以…

单片机要学多久才能自己开发?学单片机以后做什么?

大家好&#xff0c;我是无际单片机编程的徐工。 要说学多久才能自己开发&#xff0c;不妨我给大家分享一下我的单片机学习之路。 我学习单片机一方面是因为大一的时候进入了学校电子大赛实验室&#xff0c;另一方面是因为我从中学开始就对电子比较感兴趣&#xff0c;单片机又…

小小问题

zoom的使用触发IE浏览器的haslayout解决ie下的浮动&#xff0c;margin重叠等一些问题zoom只有在IE中才起作用background-image:url(名称)background-color:transparentbackground-position:*px *px;background-repeat:background:url(名称) no-repeat *px *px;写页面布局时&…

Python加速运行技巧

点击上方“小白学视觉”&#xff0c;选择加"星标"或“置顶”重磅干货&#xff0c;第一时间送达本文转自&#xff1a;机器学习算法那些事Python 是一种脚本语言&#xff0c;相比 C/C 这样的编译语言&#xff0c;在效率和性能方面存在一些不足。但是&#xff0c;有很多…