SpringBoot在使用SpringSecurity时,配置跨域过滤器CorsFilter不生效分析解决
作者:mmseoamin日期:2024-01-18

SpringBoot在使用SpringSecurity时配置跨域过滤器CorsFilter不生效

文章目录

  • SpringBoot在使用SpringSecurity时配置跨域过滤器CorsFilter不生效
    • 1.配置
    • 2.原因分析(过滤器有加载顺序)
      • 一般配置时的问题
      • 3.问题查找过程
        • a.检查CorsFilter是否执行
        • b.检查Filter链
        • c.分析filter链的生成过程
        • d.完
          • 此文中代码只粘贴部分代码,完整版请自行查看
          • 请求一般为重启debug服务再次请求

            1.配置

            一般配置方法(适用于没有SpringSecurity配置时)

            @Configuration
            public class CorsConfig {
                @Bean
                public CorsFilter corsFilter() {
                    UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
                    CorsConfiguration corsConfiguration = new CorsConfiguration();
                    corsConfiguration.setAllowCredentials(true);
                    List list = Arrays.asList("*");
                    corsConfiguration.setAllowedHeaders(list);
                    corsConfiguration.setAllowedMethods(list);
                    corsConfiguration.setAllowedOrigins(list);
                    source.registerCorsConfiguration("/**", corsConfiguration);
                    CorsFilter corsFilter = new CorsFilter(source);
                    return corsFilter;
                }
            }
            

            SpringSecurity配置时配置方法

            @Configuration
            public class CorsConfig {
                @Bean
                public FilterRegistrationBean corsFilterBean() {
                    UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
                    CorsConfiguration corsConfiguration = new CorsConfiguration();
                    corsConfiguration.setAllowCredentials(true);
                    List list = Arrays.asList("*");
                    corsConfiguration.setAllowedHeaders(list);
                    corsConfiguration.setAllowedMethods(list);
                    corsConfiguration.setAllowedOrigins(list);
                    source.registerCorsConfiguration("/**", corsConfiguration);
                    CorsFilter corsFilter = new CorsFilter(source);
                    FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean<>(corsFilter);
                    filterRegistrationBean.setOrder(-101);
                    return filterRegistrationBean;
                }
            }
            

            2.原因分析(过滤器有加载顺序)

            首先说明Spring中过滤器加载使用的是FilterRegistrationBean配置过滤器,FilterRegistrationBean实现了Ordered接口,order排序值默认值为

            Ordered.LOWEST_PRECEDENCE=Integer.MAX_VALUE

            一般配置时的问题

            • 如下代码为SpringSecurity的过滤器注册bean
              @Configuration(proxyBeanMethods = false)
              @ConditionalOnWebApplication(type = Type.SERVLET)
              @EnableConfigurationProperties(SecurityProperties.class)
              @ConditionalOnClass({AbstractSecurityWebApplicationInitializer.class, SessionCreationPolicy.class})
              @AutoConfigureAfter(SecurityAutoConfiguration.class)
              public class SecurityFilterAutoConfiguration {
                  @Bean
                  @ConditionalOnBean(name = DEFAULT_FILTER_NAME)
                  public DelegatingFilterProxyRegistrationBean securityFilterChainRegistration(
                          SecurityProperties securityProperties) {
                      DelegatingFilterProxyRegistrationBean registration = new DelegatingFilterProxyRegistrationBean(
                              DEFAULT_FILTER_NAME);
                      registration.setOrder(securityProperties.getFilter().getOrder());
                      registration.setDispatcherTypes(getDispatcherTypes(securityProperties));
                      return registration;
                  }
              }
              

              代码中DEFAULT_FILTER_NAME=“springSecurityFilterChain”,securityProperties.getFilter().getOrder()=-100

              • CorsFilter的过滤器注册bean由Spring生成,而生成的order=(Ordered.LOWEST_PRECEDENCE=Integer.MAX_VALUE),且优先按照Ordered接口排序,

                @Order注解在没有实现Ordered接口时才生效

                3.问题查找过程

                a.检查CorsFilter是否执行

                查看CorsFilter类可以知道跨域逻辑主要在以下doFilterInternal()方法执行,在此方法添加断点,debug执行,发送请求时未执行此方法,由此判断CorsFilter未执行过滤逻辑

                public class CorsFilter extends OncePerRequestFilter {
                    @Override
                    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
                                                    FilterChain filterChain) throws ServletException, IOException {
                        CorsConfiguration corsConfiguration = this.configSource.getCorsConfiguration(request);
                        boolean isValid = this.processor.processRequest(corsConfiguration, request, response);
                        if (!isValid || CorsUtils.isPreFlightRequest(request)) {
                            return;
                        }
                        filterChain.doFilter(request, response);
                    }
                }
                

                b.检查Filter链

                找到FilterChain接口,其中有如下doFilter()方法,在此方法添加断点

                public interface FilterChain {
                    public void doFilter(ServletRequest request, ServletResponse response)
                            throws IOException, ServletException;
                }
                

                请求时断点停顿处为ApplicationFilterChain,在此类中找到属性ApplicationFilterConfig[] filters,查看filters的值如图

                SpringBoot在使用SpringSecurity时,配置跨域过滤器CorsFilter不生效分析解决,在这里插入图片描述,第1张

                ApplicationFilterChain的方法internalDoFilter()逻辑可以看出filters执行顺序为数组下标升序,而corsFilter位于springSecurityFilterChain的后面,

                当执行到springSecurityFilterChain时,springSecurityFilterChain过滤器内部会进行身份校验获取用户信息,如果是跨域请求,用户验证信息会被浏览器拦截,直接中断过滤器执行,

                而corsFilter的跨域配置还没有执行到,所以需要配置corsFilter的执行顺序在springSecurityFilterChain之前

                public final class ApplicationFilterChain implements FilterChain {
                    private void internalDoFilter(ServletRequest request,
                                                  ServletResponse response)
                            throws IOException, ServletException {
                        // Call the next filter if there is one
                        if (pos < n) {
                            ApplicationFilterConfig filterConfig = filters[pos++];
                            try {
                                Filter filter = filterConfig.getFilter();
                                if (request.isAsyncSupported() && "false".equalsIgnoreCase(
                                        filterConfig.getFilterDef().getAsyncSupported())) {
                                    request.setAttribute(Globals.ASYNC_SUPPORTED_ATTR, Boolean.FALSE);
                                }
                                if (Globals.IS_SECURITY_ENABLED) {
                                    final ServletRequest req = request;
                                    final ServletResponse res = response;
                                    Principal principal =
                                            ((HttpServletRequest) req).getUserPrincipal();
                                    Object[] args = new Object[]{req, res, this};
                                    SecurityUtil.doAsPrivilege("doFilter", filter, classType, args, principal);
                                } else {
                                    filter.doFilter(request, response, this);
                                }
                            } catch (IOException | ServletException | RuntimeException e) {
                                throw e;
                            } catch (Throwable e) {
                                e = ExceptionUtils.unwrapInvocationTargetException(e);
                                ExceptionUtils.handleThrowable(e);
                                throw new ServletException(sm.getString("filterChain.filter"), e);
                            }
                            return;
                        }
                    }
                }
                

                c.分析filter链的生成过程

                ApplicationFilterChain中filters过滤连的赋值操作,在addFilter()方法中进行,在此方法中添加断点

                public final class ApplicationFilterChain implements FilterChain {
                    void addFilter(ApplicationFilterConfig filterConfig) {
                        filters[n++] = filterConfig;
                    }
                }
                

                请求时可得到如下调用栈

                SpringBoot在使用SpringSecurity时,配置跨域过滤器CorsFilter不生效分析解决,在这里插入图片描述,第2张

                查看ApplicationFilterFactory类的方法createFilterChain()可知,ApplicationFilterConfig由FilterMap生成,而FilterMaps[]

                由context(TomcatEmbeddedContext)的findFilterMaps()获取到

                public final class ApplicationFilterFactory {
                    public static ApplicationFilterChain createFilterChain(ServletRequest request,
                            Wrapper wrapper, Servlet servlet) {
                        // Acquire the filter mappings for this Context
                        StandardContext context = (StandardContext) wrapper.getParent();
                        FilterMap filterMaps[] = context.findFilterMaps();
                        // If there are no filter mappings, we are done
                        if ((filterMaps == null) || (filterMaps.length == 0))
                            return filterChain;
                        // Acquire the information we will need to match filter mappings
                        DispatcherType dispatcher =
                                (DispatcherType) request.getAttribute(Globals.DISPATCHER_TYPE_ATTR);
                        String requestPath = null;
                        Object attribute = request.getAttribute(Globals.DISPATCHER_REQUEST_PATH_ATTR);
                        if (attribute != null) {
                            requestPath = attribute.toString();
                        }
                        String servletName = wrapper.getName();
                        for (FilterMap filterMap : filterMaps) {
                            if (!matchDispatcher(filterMap, dispatcher)) {
                                continue;
                            }
                            if (!matchFiltersURL(filterMap, requestPath))
                                continue;
                            ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)
                                    context.findFilterConfig(filterMap.getFilterName());
                            if (filterConfig == null) {
                                // FIXME - log configuration problem
                                continue;
                            }
                            filterChain.addFilter(filterConfig);
                        }
                    }
                }
                

                进入findFilterMaps()(此方法为TomcatEmbeddedContext继承自StandardContext的)处在StandardContext类中,分析可知FilterMap[]

                由filterMaps生成,而filterMaps由addFilterMapBefore()和addFilterMap()方法添加,在两个方法添加断点

                public class StandardContext extends ContainerBase implements Context, NotificationEmitter {
                    @Override
                    public FilterMap[] findFilterMaps() {
                        return filterMaps.asArray();
                    }
                    @Override
                    public void addFilterMapBefore(FilterMap filterMap) {
                        validateFilterMap(filterMap);
                        // Add this filter mapping to our registered set
                        filterMaps.addBefore(filterMap);
                        fireContainerEvent("addFilterMap", filterMap);
                    }
                    @Override
                    public void addFilterMap(FilterMap filterMap) {
                        validateFilterMap(filterMap);
                        // Add this filter mapping to our registered set
                        filterMaps.add(filterMap);
                        fireContainerEvent("addFilterMap", filterMap);
                    }
                }
                

                请求时断点停顿处,其调用栈如下

                SpringBoot在使用SpringSecurity时,配置跨域过滤器CorsFilter不生效分析解决,在这里插入图片描述,第3张

                进入addMappingForUrlPatterns()方法,分析FilterMap由filterDef生成,而filterDef由setInitParameter()方法和ApplicationFilterRegistration()

                构造方法控制,在方法添加断点

                public class ApplicationFilterRegistration
                        implements FilterRegistration.Dynamic {
                    public ApplicationFilterRegistration(FilterDef filterDef,
                                                         Context context) {
                        this.filterDef = filterDef;
                        this.context = context;
                    }
                    @Override
                    public void addMappingForUrlPatterns(
                            EnumSet dispatcherTypes, boolean isMatchAfter,
                            String... urlPatterns) {
                        FilterMap filterMap = new FilterMap();
                        filterMap.setFilterName(filterDef.getFilterName());
                        if (dispatcherTypes != null) {
                            for (DispatcherType dispatcherType : dispatcherTypes) {
                                filterMap.setDispatcher(dispatcherType.name());
                            }
                        }
                        if (urlPatterns != null) {
                            // % decoded (if necessary) using UTF-8
                            for (String urlPattern : urlPatterns) {
                                filterMap.addURLPattern(urlPattern);
                            }
                            if (isMatchAfter) {
                                context.addFilterMap(filterMap);
                            } else {
                                context.addFilterMapBefore(filterMap);
                            }
                        }
                        // else error?
                    }
                    @Override
                    public boolean setInitParameter(String name, String value) {
                        if (name == null || value == null) {
                            throw new IllegalArgumentException(
                                    sm.getString("applicationFilterRegistration.nullInitParam",
                                            name, value));
                        }
                        if (getInitParameter(name) != null) {
                            return false;
                        }
                        filterDef.addInitParameter(name, value);
                        return true;
                    }
                }
                

                请求时断点停顿,调用栈为

                SpringBoot在使用SpringSecurity时,配置跨域过滤器CorsFilter不生效分析解决,在这里插入图片描述,第4张

                进入addFilter()方法,使用evaluate expression计算器分析context.findFilterDef(filterName)为null,filterDef为filterName赋值

                public class ApplicationContext implements ServletContext {
                    private FilterRegistration.Dynamic addFilter(String filterName,
                  String filterClass, Filter filter) throws IllegalStateException {
                        FilterDef filterDef = context.findFilterDef(filterName);
                        // Assume a 'complete' FilterRegistration is one that has a class and
                        // a name
                        if (filterDef == null) {
                            filterDef = new FilterDef();
                            filterDef.setFilterName(filterName);
                            context.addFilterDef(filterDef);
                        } else {
                            if (filterDef.getFilterName() != null &&
                                    filterDef.getFilterClass() != null) {
                                return null;
                            }
                        }
                        return new ApplicationFilterRegistration(filterDef, context);
                    }
                }
                

                再次查看调用栈,分析可知filterName来源于addRegistration()方法

                SpringBoot在使用SpringSecurity时,配置跨域过滤器CorsFilter不生效分析解决,在这里插入图片描述,第5张

                public abstract class AbstractFilterRegistrationBean extends DynamicRegistrationBean {
                    @Override
                    protected Dynamic addRegistration(String description, ServletContext servletContext) {
                        Filter filter = getFilter();
                        return servletContext.addFilter(getOrDeduceName(filter), filter);
                    }
                }
                

                进入addRegistration()方法分析知道filter来自getFilter()

                ,且this类型为FilterRegistrationBean,进入FilterRegistrationBean的getFilter()方法,返回为this.filter,查找filter的赋值操作setFilter()

                方法和FilterRegistrationBean()构造方法,添加断点

                public class FilterRegistrationBean extends AbstractFilterRegistrationBean {
                    @Override
                    public T getFilter() {
                        return this.filter;
                    }
                    public FilterRegistrationBean(T filter, ServletRegistrationBean... servletRegistrationBeans) {
                        super(servletRegistrationBeans);
                        Assert.notNull(filter, "Filter must not be null");
                        this.filter = filter;
                    }
                    public void setFilter(T filter) {
                        Assert.notNull(filter, "Filter must not be null");
                        this.filter = filter;
                    }
                }
                

                请求时断点停顿,分析调用栈找到FilterRegistrationBean的filter赋值操作的源头为addAsRegistrationBean()方法

                SpringBoot在使用SpringSecurity时,配置跨域过滤器CorsFilter不生效分析解决,在这里插入图片描述,第6张

                进入addAsRegistrationBean()方法,可以看到RegistrationBean的创建,并且设置排序属性值,最后将FilterRegistrationBean存入this.initializers

                public class ServletContextInitializerBeans extends AbstractCollection {
                    @SafeVarargs
                    public ServletContextInitializerBeans(ListableBeanFactory beanFactory,
                                                          Class... initializerTypes) {
                        this.initializers = new LinkedMultiValueMap<>();
                        this.initializerTypes = (initializerTypes.length != 0) ? Arrays.asList(initializerTypes)
                                : Collections.singletonList(ServletContextInitializer.class);
                        addServletContextInitializerBeans(beanFactory);
                        addAdaptableBeans(beanFactory);
                        List sortedInitializers = this.initializers.values().stream()
                                .flatMap((value) -> value.stream().sorted(AnnotationAwareOrderComparator.INSTANCE))
                                .collect(Collectors.toList());
                        this.sortedList = Collections.unmodifiableList(sortedInitializers);
                        logMappings(this.initializers);
                    }
                    private  void addAsRegistrationBean(ListableBeanFactory beanFactory, Class type,
                         Class beanType, RegistrationBeanAdapter adapter) {
                        List> entries = getOrderedBeansOfType(beanFactory, beanType, this.seen);
                        for (Entry entry : entries) {
                            String beanName = entry.getKey();
                            B bean = entry.getValue();
                            if (this.seen.add(bean)) {
                                // One that we haven't already seen
                                RegistrationBean registration = adapter.createRegistrationBean(beanName, bean, entries.size());
                                int order = getOrder(bean);
                                registration.setOrder(order);
                                this.initializers.add(type, registration);
                                if (logger.isTraceEnabled()) {
                                    logger.trace("Created " + type.getSimpleName() + " initializer for bean '" + beanName + "'; order="
                                            + order + ", resource=" + getResourceDescription(beanName, beanFactory));
                                }
                            }
                        }
                    }
                }
                

                分析this.initializers的操作,没有直接取值的操作,但在ServletContextInitializerBeans()构造函数中间接赋值给了this.sortedList,而查看调用栈也可以得知

                SpringBoot在使用SpringSecurity时,配置跨域过滤器CorsFilter不生效分析解决,在这里插入图片描述,第7张

                而this.initializers向this.sortedList赋值时最重要操作是如下代码:

                将所有AbstractFilterRegistrationBean按照AnnotationAwareOrderComparator的规则进行排序,因为此类型实现了Ordered接口所以优先按照接口排序值排序,当没有实现Ordered接口时才按照@Order注解排序,

                然后转换为不可修改的集合赋值给this.sortedList

                List sortedInitializers=this.initializers.values().stream()
                        .flatMap((value)->value.stream().sorted(AnnotationAwareOrderComparator.INSTANCE))
                        .collect(Collectors.toList());
                this.sortedList=Collections.unmodifiableList(sortedInitializers);
                

                在ServletContextInitializerBeans类中找到this.sortedList的操作,iterator()方法添加断点

                public class ServletContextInitializerBeans extends AbstractCollection {
                    @Override
                    public Iterator iterator() {
                        return this.sortedList.iterator();
                    }
                }
                

                放开上一个断点,进入iterator(),查看调用栈

                SpringBoot在使用SpringSecurity时,配置跨域过滤器CorsFilter不生效分析解决,在这里插入图片描述,第8张

                在selfInitialize()方法内getServletContextInitializerBeans()获取到this.sortedList,即排序后的FilterRegistrationBean,按照顺序挨个执行

                public class ServletWebServerApplicationContext extends GenericWebApplicationContext
                        implements ConfigurableWebServerApplicationContext {
                    private void selfInitialize(ServletContext servletContext) throws ServletException {
                        prepareWebApplicationContext(servletContext);
                        registerApplicationScope(servletContext);
                        WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext);
                        for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
                            beans.onStartup(servletContext);
                        }
                    }
                }
                

                d.完