SpringBoot在使用SpringSecurity时,配置跨域过滤器CorsFilter不生效分析解决

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

  • 此文中代码只粘贴部分代码,完整版请自行查看
  • 请求一般为重启debug服务再次请求

1.配置

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


@Configuration
public class CorsConfig {
    @Bean
    public CorsFilter corsFilter() {
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        CorsConfiguration corsConfiguration = new CorsConfiguration();
        corsConfiguration.setAllowCredentials(true);
        List<String> 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<String> list = Arrays.asList("*");
        corsConfiguration.setAllowedHeaders(list);
        corsConfiguration.setAllowedMethods(list);
        corsConfiguration.setAllowedOrigins(list);
        source.registerCorsConfiguration("/**", corsConfiguration);
        CorsFilter corsFilter = new CorsFilter(source);
        FilterRegistrationBean<CorsFilter> 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的值如图
在这里插入图片描述

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;
    }
}

请求时可得到如下调用栈
在这里插入图片描述

查看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);
    }
}

请求时断点停顿处,其调用栈如下
在这里插入图片描述

进入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<DispatcherType> 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;
    }
}

请求时断点停顿,调用栈为
在这里插入图片描述

进入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()方法
在这里插入图片描述

public abstract class AbstractFilterRegistrationBean<T extends Filter> extends DynamicRegistrationBean<Dynamic> {

    @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<T extends Filter> extends AbstractFilterRegistrationBean<T> {

    @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()方法
在这里插入图片描述

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

public class ServletContextInitializerBeans extends AbstractCollection<ServletContextInitializer> {

    @SafeVarargs
    public ServletContextInitializerBeans(ListableBeanFactory beanFactory,
                                          Class<? extends ServletContextInitializer>... initializerTypes) {
        this.initializers = new LinkedMultiValueMap<>();
        this.initializerTypes = (initializerTypes.length != 0) ? Arrays.asList(initializerTypes)
                : Collections.singletonList(ServletContextInitializer.class);
        addServletContextInitializerBeans(beanFactory);
        addAdaptableBeans(beanFactory);
        List<ServletContextInitializer> sortedInitializers = this.initializers.values().stream()
                .flatMap((value) -> value.stream().sorted(AnnotationAwareOrderComparator.INSTANCE))
                .collect(Collectors.toList());
        this.sortedList = Collections.unmodifiableList(sortedInitializers);
        logMappings(this.initializers);
    }

    private <T, B extends T> void addAsRegistrationBean(ListableBeanFactory beanFactory, Class<T> type,
                                                        Class<B> beanType, RegistrationBeanAdapter<T> adapter) {
        List<Map.Entry<String, B>> entries = getOrderedBeansOfType(beanFactory, beanType, this.seen);
        for (Entry<String, B> 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,而查看调用栈也可以得知
在这里插入图片描述

而this.initializers向this.sortedList赋值时最重要操作是如下代码:
将所有AbstractFilterRegistrationBean按照AnnotationAwareOrderComparator的规则进行排序,因为此类型实现了Ordered接口所以优先按照接口排序值排序,当没有实现Ordered接口时才按照@Order注解排序,
然后转换为不可修改的集合赋值给this.sortedList

List<ServletContextInitializer> 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<ServletContextInitializer> {

    @Override
    public Iterator<ServletContextInitializer> iterator() {
        return this.sortedList.iterator();
    }
}

放开上一个断点,进入iterator(),查看调用栈
在这里插入图片描述

在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.完

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值