解读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信息,所以拦截时需过滤预检请求

总结

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

相关文章

  • MyBatis中的@SelectProvider注解源码分析

    MyBatis中的@SelectProvider注解源码分析

    这篇文章主要介绍了MyBatis中的@SelectProvider注解源码分析,@SelectProvider功能就是用来单独写一个class类与方法,用来提供一些xml或者注解中不好写的sql,今天就来说下这个注解的具体用法与源码,需要的朋友可以参考下
    2024-01-01
  • Docker和 Containerd 的区别解析

    Docker和 Containerd 的区别解析

    containerd 是一个来自 Docker 的高级容器运行时,并实现了 CRI 规范,它是从 Docker 项目中分离出来,之后 containerd 被捐赠给云原生计算基金会(CNCF)为容器社区提供创建新容器解决方案的基础,这篇文章主要介绍了Docker和 Containerd 的区别,需要的朋友可以参考下
    2024-03-03
  • Java构造方法和方法重载详解

    Java构造方法和方法重载详解

    大家好,本篇文章主要讲的是Java构造方法和方法重载详解,感兴趣的同学赶快来看一看吧,对你有帮助的话记得收藏一下
    2022-01-01
  • Java超详细讲解多线程中的Process与Thread

    Java超详细讲解多线程中的Process与Thread

    进程process:在一定的环境下,把静态的程序代码运行起来,通过使用不同的资源,来完成一定的任务;线程thread:是程序中一个单一的顺序控制流程。在单个进程中同时运行多个线程完成不同的工作,称为多线程
    2022-05-05
  • Java 递归查询部门树形结构数据的实践

    Java 递归查询部门树形结构数据的实践

    本文主要介绍了Java 递归查询部门树形结构数据的实践,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-09-09
  • java中的动态代理与责任链模式详解

    java中的动态代理与责任链模式详解

    这篇文章主要介绍了java中的动态代理与责任链模式详解,动态代理提供了一种灵活且非侵入式的方式,可以对对象的行为进行定制和扩展,它在代码重用、解耦和业务逻辑分离、性能优化以及系统架构中起到了重要的作用,需要的朋友可以参考下
    2023-08-08
  • Java switch多值匹配操作详解

    Java switch多值匹配操作详解

    这篇文章主要介绍了Java switch多值匹配操作详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-01-01
  • 如何使用maven-helper插件解决jar包冲突问题

    如何使用maven-helper插件解决jar包冲突问题

    安装了Maven Helper插件,只要打开pom文件,就可以打开该pom文件的Dependency Analyzer视图,这篇文章主要介绍了使用maven-helper插件解决jar包冲突,需要的朋友可以参考下
    2024-05-05
  • Java通过JsApi方式实现微信支付

    Java通过JsApi方式实现微信支付

    本文讲解了Java如何实现JsApi方式的微信支付,代码内容详细,文章思路清晰,需要的朋友可以参考下
    2015-07-07
  • java获取登录者IP和登录时间的两种实现代码详解

    java获取登录者IP和登录时间的两种实现代码详解

    这篇文章主要介绍了java获取登录者IP和登录时间的实现代码,本文通过两种结合实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-07-07

最新评论