解读SpringBoot中addCorsMappings配置跨域与拦截器互斥问题的原因

 更新时间:2023年12月26日 16:49:18   作者:huangyaa729  
这篇文章主要介绍了解读SpringBoot中addCorsMappings配置跨域与拦截器互斥问题的原因,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教

SpringBoot中addCorsMappings配置跨域与拦截器互斥

如题,前两天在做前后端分离项目时,碰到了这个问题,登录token验证的拦截器使项目中配置的跨域配置失效,导致浏览器抛出跨域请求错误,跨域配置如下:

public WebMvcConfigurer corsConfigurer() {
        return new WebMvcConfigurerAdapter() {
            @Override
            public void addCorsMappings(CorsRegistry registry) {
                        registry.addMapping("/**")
                        .allowedOrigins(origins)
                        .allowedHeaders("*")
                        .allowCredentials(true)
                        .allowedMethods("*")
                        .maxAge(3600);
            }
        };
    }

通过在网上的查询,发现了如下解释

  • 但是使用此方法配置之后再使用自定义拦截器时跨域相关配置就会失效。
  • 原因是请求经过的先后顺序问题,当请求到来时会先进入拦截器中,而不是进入Mapping映射中,所以返回的头信息中并没有配置的跨域信息。浏览器就会报跨域异常。

然后参考了网上给出的方法,重新引入了跨域过滤器配置,解决了这个问题。

那最终这个问题产生的原因是什么的,真的如上诉所说吗,我通过调试与研究源码,找了原因。

在springMvc中,我们都知道路径的映射匹配是通过DispatcherServlet这个类来实现的,最终的函数执行在doDispatch()这个方法中:

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
		HttpServletRequest processedRequest = request;
		HandlerExecutionChain mappedHandler = null;
		boolean multipartRequestParsed = false;

		WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

		try {
			ModelAndView mv = null;
			Exception dispatchException = null;

			try {
				processedRequest = checkMultipart(request);
				multipartRequestParsed = (processedRequest != request);

				// Determine handler for the current request. 
		(1)mappedHandler = getHandler(processedRequest);
				if (mappedHandler == null || mappedHandler.getHandler() == null) {
					noHandlerFound(processedRequest, response);
					return;
				}

				// Determine handler adapter for the current request.
		(2)HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

				// Process last-modified header, if supported by the handler.
				String method = request.getMethod();
				boolean isGet = "GET".equals(method);
				if (isGet || "HEAD".equals(method)) {
					long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
					if (logger.isDebugEnabled()) {
						logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
					}
					if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
						return;
					}
				}

		(3)if (!mappedHandler.applyPreHandle(processedRequest, response)) {
					return;
				}

				// Actually invoke the handler.
		(4)mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

				if (asyncManager.isConcurrentHandlingStarted()) {
					return;
				}

				applyDefaultViewName(processedRequest, mv);
		(5)mappedHandler.applyPostHandle(processedRequest, response, mv);
			}

在这个类中,我们关注(1)-(5)这几句代码,基本上整个映射执行的逻辑就明了了:

  • (1)根据请求request获取执行器链(包括拦截器和最终执行方法Handler)
  • (2)根据Handler获取handlerAdapter;
  • (3)执行执行器链中的拦截方法(preHandle);
  • (4)执行handler方法;
  • (5)执行执行器链中的拦截方法(postHandle);

在这个函数中我们并没有看到什么时候执行addCorsMappings这一配置内容,那它到底是什么时候添加的呢,那就需要仔细分析步骤(1)了:获取整个执行器链。

通过调试定位,我发现getHandle()最终执行的AbstractHandlerMapping这个类的函数

public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
(1)Object handler = getHandlerInternal(request);
		if (handler == null) {
			handler = getDefaultHandler();
		}
		if (handler == null) {
			return null;
		}
		// Bean name or resolved handler?
		if (handler instanceof String) {
			String handlerName = (String) handler;
			handler = getApplicationContext().getBean(handlerName);
		}

(2)HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
		if (CorsUtils.isCorsRequest(request)) {
			CorsConfiguration globalConfig = this.globalCorsConfigSource.getCorsConfiguration(request);
			CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
			CorsConfiguration config = (globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig);
(3)	executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
		}
		return executionChain;
	}

这个函数中我也标记了(1)、(2)、(3)这三条语句:

  • (1)获取request所需执行的handler,具体逻辑不再细说,有兴趣的可以参考我的另一篇文章
  • (2)获取执行器链,简单来说就是把具体的执行器和整个拦截器链组成一个链队形,方便后续执行;
  • (3)这个就是关键点,可能有的同学已经看明白了,addCorsMapping配置就是在这块引入的;

进入这个方法后,一切都明了了;

protected HandlerExecutionChain getCorsHandlerExecutionChain(HttpServletRequest request,
			HandlerExecutionChain chain, CorsConfiguration config) {

		if (CorsUtils.isPreFlightRequest(request)) {
			HandlerInterceptor[] interceptors = chain.getInterceptors();
			chain = new HandlerExecutionChain(new PreFlightHandler(config), interceptors);
		}
		else {
			chain.addInterceptor(new CorsInterceptor(config));
		}
		return chain;
	}

先判断request是否是预检请求(不明白什么是预检请求的可以自身搜索相关解释,很多,不再赘述),是预检请求则生成个预检执行器PreFlightHandler,然后在doDispatch函数(4)中执行;

否则生成一个跨域拦截器加入拦截器链中,最终再doDispatch函数(3)处执行,而因为拦截器是顺序执行的,如果前面执行失败异常返回后,后面的则不再执行。

所以当跨越请求在拦截器那边处理后就异常返回了,那么响应的response报文头部关于跨域允许的信息就没有被正确设置,导致浏览器认为服务不允许跨域,而造成错误;而当我们使用过滤器时,过滤器先于拦截器执行,那么无论是否被拦截,始终有允许跨域的头部信息,就不会出问题了。

另注:

对于预检请求,一般token验证时是不会拦截此请求的,因为预检请求不会附带任何参数信息,也就没有所需的token信息,所以拦截时需过滤预检请求

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • PageHelper在springboot+mybatis框架中的使用步骤及原理解析

    PageHelper在springboot+mybatis框架中的使用步骤及原理解析

    这篇文章主要介绍了PageHelper在springboot+mybatis框架中的使用步骤及原理解析,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2023-03-03
  • Java实题演练二叉搜索树与双向链表分析

    Java实题演练二叉搜索树与双向链表分析

    这篇文章主要介绍了Java二叉搜索树与双向链表,总的来说这并不是一道难题,那为什么要拿出这道题介绍?拿出这道题真正想要传达的是解题的思路,以及不断优化探寻最优解的过程。希望通过这道题能给你带来一种解题优化的思路
    2022-12-12
  • Spring3.1.1+MyBatis3.1.1的增、删、查、改以及分页和事务管理

    Spring3.1.1+MyBatis3.1.1的增、删、查、改以及分页和事务管理

    这篇文章主要介绍了Spring3.1.1+MyBatis3.1.1的增、删、查、改以及分页和事务管理的相关资料,需要的朋友可以参考下
    2016-01-01
  • 基于spring boot 的配置参考大全(推荐)

    基于spring boot 的配置参考大全(推荐)

    下面小编就为大家带来一篇基于spring boot 的配置参考大全(推荐)。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-09-09
  • java教程之java注解annotation使用方法

    java教程之java注解annotation使用方法

    这篇文章主要介绍了java注解annotation使用方法,注解可以定义到方法上,类上,一个注解相当与一个类,就相当于实例了一个对象,加上了注解,就相当于加了一个标志
    2014-01-01
  • JVM jstack实战之死锁问题详解

    JVM jstack实战之死锁问题详解

    如果在生产环境发生了死锁,我们将看到的是部署的程序没有任何反应了,这个时候我们可以借助 jstack进行分析,下面我们实战操作查找死锁的原因
    2022-10-10
  • MyBatis高级映射ResultMap解决属性问题

    MyBatis高级映射ResultMap解决属性问题

    对于数据库中对表的增删改查操作,我们知道增删改都涉及的是单表,而只有查询操作既可以设计到单表操作又可以涉及到多表操作,所以对于输入映射parameterType而言是没有所谓的高级映射的,也就是说高级映射只针对于输出映射
    2023-02-02
  • Maven Web项目使用Cargo插件实现自动化部署的详细步骤

    Maven Web项目使用Cargo插件实现自动化部署的详细步骤

    cargo ,它是一组帮助用户实现自动化部署,操作Web容器的工具,并且几乎支持所有的Web容器,这篇文章主要介绍了Maven Web项目使用Cargo实现自动化部署,需要的朋友可以参考下
    2023-02-02
  • Spring定时任务轮询本地数据库实现过程解析

    Spring定时任务轮询本地数据库实现过程解析

    这篇文章主要介绍了Spring定时任务轮询本地数据库实现过程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-01-01
  • Hibernate命名策略详解

    Hibernate命名策略详解

    本文主要介绍了Hibernate命名策略。具有很好的参考价值,下面跟着小编一起来看下吧
    2017-01-01

最新评论