文章目录

  • *过滤器链分析*
  • *4.1初始化流程分析*
  • *4.1.1`ObjectPostProcessor`*
  • *4.1.2`SecurityFilterChain`*
  • *4.1.3`SecurityBuilder`*
  • *`SecurityBuilder`*
  • *`HttpSecurityBuilder`*
  • *`AbstractSecurityBuilder`*
  • *`AbstractConfiguredSecurityBuilder`*
  • *`ProviderManagerBuilder`*
  • *`AuthenticationManagerBuilder`*
  • *`HttpSecurity`*
  • *`WebSecurity`*
  • *4.1.4`FilterChainProxy`*
  • *4.1.5`SecurityConfigurer`*
  • *`SecurityConfigurer`*
  • *`SecurityConfigurerAdapter`*
  • *`UserDetailsAwareConfigurer`*
  • *`AbstractHttpConfigurer`*
  • *`GlobalAuthenticationConfigurerAdapter`*
  • *`WebSecurityConfigurer`*
  • *`WebSecurityConfigurerAdapter`*
  • *4.1.6初始化流程分析*
  • *`WebSecurityConfiguration`*
  • *`AuthenticationConfiguration`*
  • *4.2`ObjectPostProcessor`使用*
  • *4.3多种用户定义方式*
  • *4.4定义多个过滤器链*
  • *4.5静态资源过滤*
  • *4.6使用JSON格式登录*
  • *4.7添加登录验证码*


过滤器链分析

Spring security中,所有功能都是通过过滤器来实现的,这些过滤器组成一个完整的过滤器链。

4.1初始化流程分析

为了更好的理解,需要先大致介绍下spring security中一些常见的关键组件。

4.1.1ObjectPostProcessor

ObjectPostProcessor是spring security中使用频率最高的组件之一,它是一个对象后置处理器,也就是当一个对象创建成功后,如果还有一些额外的事情需要补充,那么可以通过它来进行处理。

/**
 * 允许初始化对象。通常,这用于调用Aware中的方法,InitializingBean.afterPropertiesSet()以及
 * 确保DisposableBean.destroy()被调用。
 * Type parameters: <T> – 此ObjectPostProcessor支持的对象类型的界限
 */
public interface ObjectPostProcessor<T> {
    // 初始化对象,可能返回一个应该使用的修改过的实例。完成对象的二次处理
    <O extends T> O postProcess(O object);
}

Spring Cloud Gateway自定义拦截器开发 springsecurity拦截器链_spring

  • AutowireBeanFactoryObjectPostProcessor:由于spring security中大量采用了java配置,许多过滤器都是直接new出来的,这些直接new出来的对象并不会自动注入到spring容器中。Spring security这样做的本意是为了简化配置,但是却带来了另外一个问题就是,大量new出来的对象需要手动注册到spring容器中去。AutowireBeanFactoryObjectPostProcessor对象所承担的就是这件事,一个对象new出来之后,只要调用AutowireBeanFactoryObjectPostProcessor#postProcess方法,就可以成功注入到spring容器中,它的实现原理就是通过调用spring容器中的AutowireCapableBeanFactory对象将一个new出来的对象注入到spring容器中去。
  • CompositeObjectPostProcessor:一个对象可以有一个后置处理器,也可以自定义多个对象后置处理器。CompositeObjectPostProcessor是一个组合的对象后置处理器,它里边维护了一个List集合,集合中存放了某一个对象的所有后置处理器,当需要执行对象的后置处理器时,会遍历集合中的所有ObjectPostProcessor实例,分别调用实例的postProcess方法进行对象的后置处理。在spring security中,最终使用的对象后置处理器其实就是CompositeObjectPostProcessor,它里边的集合默认只有一个对象,就是AutowireBeanFactoryObjectPostProcessor

在spring security中,开发者可以灵活地配置项目中需要哪些spring security的过滤器,一旦选定过滤器之后,每一个过滤器都会有一个对应的配置器,叫做xxxConfigurer(例如CorsConfigurerCsrfConfigurer等),过滤器都是在相应的配置器中new出来的,然后在postProcess方法中处理一遍,就将这些过滤器注入到spring容器中了。

4.1.2SecurityFilterChain

SecurityFilterChain就是spring security中的过滤器链对象:

/**
 * 定义一个能够与HttpServletRequest匹配的过滤器链,以决定是否适用于该请求。
 * 用于配置FilterChainProxy。
 */
public interface SecurityFilterChain {
    // 判断request请求是否应该被当前过滤器链所处理
    boolean matches(HttpServletRequest request);

    // 返回一个spring security中的过滤器集合。如果matches返回true,那么request
    // 请求就会在getFilters方法中返回的集合中被处理
    List<Filter> getFilters();

}

SecurityFilterChain只有一个默认的实现类就是DefaultSecurityFilterChain

public final class DefaultSecurityFilterChain implements SecurityFilterChain {
    private final RequestMatcher requestMatcher;

    private final List<Filter> filters;

	public DefaultSecurityFilterChain(RequestMatcher requestMatcher, Filter... filters) {
		this(requestMatcher, Arrays.asList(filters));
	}

	// 需要传入两个对象,一个是请求匹配器requestMatcher,另一个则是过滤器集合或者过滤器数组filters
    public DefaultSecurityFilterChain(RequestMatcher requestMatcher, List<Filter> filters) {
        this.requestMatcher = requestMatcher;
        this.filters = new ArrayList<>(filters);
    }

    public RequestMatcher getRequestMatcher() {
        return this.requestMatcher;
    }

    @Override
    public List<Filter> getFilters() {
        return this.filters;
    }

    @Override
    public boolean matches(HttpServletRequest request) {
        return this.requestMatcher.matches(request);
    }
}
4.1.3SecurityBuilder

Spring security中所有需要构建的对象都可以通过SecurityBuilder来实现,默认的过滤器链、代理过滤器、AuthenticationManager等,都可以通过SecurityBuilder来构建。

Spring Cloud Gateway自定义拦截器开发 springsecurity拦截器链_初始化_02

SecurityBuilder
/**
 * 用来创建一个对象的接口。不同的SecurityBuilder将来会构建出不同的对象。
 * Type parameters: <O> – 具体构建的对象
 */
public interface SecurityBuilder<O> {
    O build() throws Exception;
}
HttpSecurityBuilder

HttpSecurityBuilder是用来构建HttpSecurity对象的:

/**
 * 默认情况下HttpSecurityBuilder的实现类只有一个HttpSecurity,所以可以将H都当成
 * HttpSecurity来理解。
 * HttpSecurityBuilder继承自SecurityBuilder接口,同时也指定了SecurityBuilder
 * 中的泛型为DefaultSecurityFilterChain,也就是说,HttpSecurityBuilder最终想要
 * 构建的对象是DefaultSecurityFilterChain
 */
public interface HttpSecurityBuilder<H extends HttpSecurityBuilder<H>>
        extends SecurityBuilder<DefaultSecurityFilterChain> {
    // 用来获取一个xxxConfigurer这样的配置器
    <C extends SecurityConfigurer<DefaultSecurityFilterChain, H>> C getConfigurer(Class<C> clazz);

    // 用来移除一个配置器(相当于从spring security过滤器链中移除)
    <C extends SecurityConfigurer<DefaultSecurityFilterChain, H>> C removeConfigurer(Class<C> clazz);

    // 设置一个可以在多个配置器之间共享的对象
    <C> void setSharedObject(Class<C> sharedType, C object);

	// 获取一个可以在多个配置器之间共享的对象
    <C> C getSharedObject(Class<C> sharedType);

    // 配置一个认证器
    H authenticationProvider(AuthenticationProvider authenticationProvider);

    // 配置一个数据源
    H userDetailsService(UserDetailsService userDetailsService) throws Exception;

	// 在某一个过滤器之后添加一个自定义的过滤器
    H addFilterAfter(Filter filter, Class<? extends Filter> afterFilter);

	// 在某一个过滤器之前添加一个自定义的过滤器
    H addFilterBefore(Filter filter, Class<? extends Filter> beforeFilter);

    // 添加一个过滤器,必须是spring security提供的过滤器的一个实例或者其扩展,
    // 添加完成之后,会自动进行过滤器的排序
    H addFilter(Filter filter);
}
AbstractSecurityBuilder

AbstractSecurityBuilder实现了SecurityBuilder接口,并对build做了完善,确保只build一次:

// Type parameters: <O> – 正在构建的Object的类型
public abstract class AbstractSecurityBuilder<O> implements SecurityBuilder<O> {
    // 确保即使在多线程环境下,配置类也只能构建一次
    private AtomicBoolean building = new AtomicBoolean();
    private O object;

    // 设置为final,子类不能重写
    @Override
    public final O build() throws Exception {
    	// 通过building变量来控制配置类只构建一次
        if (this.building.compareAndSet(false, true)) {
            this.object = doBuild();
            return this.object;
        }
        throw new AlreadyBuiltException("This object has already been built");
    }

	// 返回构建的对象
    public final O getObject() {
        if (!this.building.get()) {
            throw new IllegalStateException("This object has not been built");
        }
        return this.object;
    }

	// 执行具体的构建工作
    protected abstract O doBuild() throws Exception;
}
AbstractConfiguredSecurityBuilder

源码较长,需要分开来看。首先在AbstractConfiguredSecurityBuilder中声明了一个枚举类,用来描述构建过程的不同状态:

private enum BuildState {
    // 配置类构建前
    UNBUILT(0),

    // 初始化中(初始化完成之前是这个状态)
    INITIALIZING(1),

    // 配置中(开始构建之前是这个状态)
    CONFIGURING(2),

    // 构建中
    BUILDING(3),

    // 构建完成
    BUILT(4);

    private final int order;

    BuildState(int order) {
        this.order = order;
    }

    // 判断是否正在初始化中
    public boolean isInitializing() {
        return INITIALIZING.order == this.order;
    }

    // 判断是否已完成配置
    public boolean isConfigured() {
        return this.order >= CONFIGURING.order;
    }
}

AbstractConfiguredSecurityBuilder类的相关方法如下:

/**
 * Type parameters: <O> – 构建器返回的对象
 * Type parameters: <O> – 构建器的类型(由基类返回)
 */
public abstract class AbstractConfiguredSecurityBuilder<O, B extends SecurityBuilder<O>>
        extends AbstractSecurityBuilder<O> {
    // 用来存放所有的配置类
    private final LinkedHashMap<Class<? extends SecurityConfigurer<O, B>>, List<SecurityConfigurer<O, B>>> configurers = new LinkedHashMap<>();

    public <C extends SecurityConfigurerAdapter<O, B>> C apply(C configurer) throws Exception {
        configurer.addObjectPostProcessor(this.objectPostProcessor);
        configurer.setBuilder((B) this);
        add(configurer);
        return configurer;
    }

    public <C extends SecurityConfigurer<O, B>> C apply(C configurer) throws Exception {
        add(configurer);
        return configurer;
    }

	// 用来将所有的配置类保存到configurers中
    private <C extends SecurityConfigurer<O, B>> void add(C configurer) {        
        Class<? extends SecurityConfigurer<O, B>> clazz = (Class<? extends SecurityConfigurer<O, B>>) configurer
                .getClass();
        synchronized (this.configurers) {
            if (this.buildState.isConfigured()) {
                throw new IllegalStateException("Cannot apply " + configurer + " to already built object");
            }
            List<SecurityConfigurer<O, B>> configs = null;

            // 判断是否允许相同类型的配置类存在,默认情况下,普通配置类为false,所以集合中的始终只有一个配置类,
            // 如果在AuthenticationManagerBuilder中设置allowConfigurersOfSameType为true,此时相同
            // 类型的配置类可以有多个
            if (this.allowConfigurersOfSameType) {
                configs = this.configurers.get(clazz);
            }
            configs = (configs != null) ? configs : new ArrayList<>(1);
            configs.add(configurer);
            this.configurers.put(clazz, configs);
            if (this.buildState.isInitializing()) {
                this.configurersAddedInInitializing.add(configurer);
            }
        }
    }

	// 从configurers中返回某一个配置类对应的所有实例
    public <C extends SecurityConfigurer<O, B>> List<C> getConfigurers(Class<C> clazz) {
        List<C> configs = (List<C>) this.configurers.get(clazz);
        if (configs == null) {
            return new ArrayList<>();
        }
        return new ArrayList<>(configs);
    }

	// 从configurers中移除某一个配置类对应的所有实例并返回
    public <C extends SecurityConfigurer<O, B>> List<C> removeConfigurers(Class<C> clazz) {
        List<C> configs = (List<C>) this.configurers.remove(clazz);
        if (configs == null) {
            return new ArrayList<>();
        }
        return new ArrayList<>(configs);
    }

	// 获取配置类实例,但是只获取集合中第一项
    public <C extends SecurityConfigurer<O, B>> C getConfigurer(Class<C> clazz) {
        List<SecurityConfigurer<O, B>> configs = this.configurers.get(clazz);
        if (configs == null) {
            return null;
        }
        
        return (C) configs.get(0);
    }

	// 移除配置类对应的所有实例,并返回实例中的第一项
    public <C extends SecurityConfigurer<O, B>> C removeConfigurer(Class<C> clazz) {
        List<SecurityConfigurer<O, B>> configs = this.configurers.remove(clazz);
        if (configs == null) {
            return null;
        }
        
        return (C) configs.get(0);
    }

	// 将所有的配置类实例放到一个集合中返回,在配置类初始化和配置的时候,会调用到该方法
    private Collection<SecurityConfigurer<O, B>> getConfigurers() {
        List<SecurityConfigurer<O, B>> result = new ArrayList<>();
        for (List<SecurityConfigurer<O, B>> configs : this.configurers.values()) {
            result.addAll(configs);
        }
        return result;
    }
}

其最核心的方法是doBuild

// 一边更新构建状态,一边执行构建方法
@Override
protected final O doBuild() throws Exception {
    synchronized (this.configurers) {
        this.buildState = BuildState.INITIALIZING;
        // 空的初始化方法,如果需要在初始化之前做一些准备工作,可以自己重写该方法
        beforeInit();
        // 遍历所有的配置类,并调用其方法完成初始化操作
        init();
        this.buildState = BuildState.CONFIGURING;
        // 默认空方法
        beforeConfigure();
        // 遍历所有的配置类,分别调用其configure方法完成配置
        configure();
        this.buildState = BuildState.BUILDING;
        // 用来做最终的构建操作,抽象方法,具体的实现则在不同的配置类中
        O result = performBuild();
        this.buildState = BuildState.BUILT;
        return result;
    }
}
ProviderManagerBuilder
// 指定构建的对象是AuthenticationManager
public interface ProviderManagerBuilder<B extends ProviderManagerBuilder<B>>
        extends SecurityBuilder<AuthenticationManager> {
    B authenticationProvider(AuthenticationProvider authenticationProvider);
}
AuthenticationManagerBuilder

用来构建AuthenticationManager(仅列出核心代码):

public class AuthenticationManagerBuilder
        extends AbstractConfiguredSecurityBuilder<AuthenticationManager, AuthenticationManagerBuilder>
        implements ProviderManagerBuilder<AuthenticationManagerBuilder> {
    // 第二个参数为true,表示允许相同类型的配置类同时存在,参考AbstractConfiguredSecurityBuilder
    public AuthenticationManagerBuilder(ObjectPostProcessor<Object> objectPostProcessor) {
        super(objectPostProcessor, true);
    }

    // 用来给一个AuthenticationManager设置parent
    public AuthenticationManagerBuilder parentAuthenticationManager(AuthenticationManager authenticationManager) {
        if (authenticationManager instanceof ProviderManager) {
            eraseCredentials(((ProviderManager) authenticationManager).isEraseCredentialsAfterAuthentication());
        }
        this.parentAuthenticationManager = authenticationManager;
        return this;
    }

    // 用来配置基于内存的数据源,该方法会自动创建InMemoryUserDetailsManagerConfigurer配置类,并最终
    // 将该配置类添加到父类的configurers变量中。由于设置了允许相同类型的配置类同时存在,因此
    // inMemoryAuthentication方法可以反复调用多次
    public InMemoryUserDetailsManagerConfigurer<AuthenticationManagerBuilder> inMemoryAuthentication()
            throws Exception {
        return apply(new InMemoryUserDetailsManagerConfigurer<>());
    }

    // 同上
    public JdbcUserDetailsManagerConfigurer<AuthenticationManagerBuilder> jdbcAuthentication() throws Exception {
        return apply(new JdbcUserDetailsManagerConfigurer<>());
    }

    // 同上
    public <T extends UserDetailsService> DaoAuthenticationConfigurer<AuthenticationManagerBuilder, T> userDetailsService(
            T userDetailsService) throws Exception {
        this.defaultUserDetailsService = userDetailsService;
        return apply(new DaoAuthenticationConfigurer<>(userDetailsService));
    }

    // 用来向authenticationProviders集合中添加AuthenticationProvider对象。
    // 一个AuthenticationManager实例中包含多个AuthenticationProvider实例,
    // 那么,多个AuthenticationProvider实例可以通过authenticationProvider方法进行添加
    @Override
    public AuthenticationManagerBuilder authenticationProvider(AuthenticationProvider authenticationProvider) {
        this.authenticationProviders.add(authenticationProvider);
        return this;
    }

    // 执行具体的构建工作,常用的AuthenticationManager实例就是ProviderManager,所以这里创建
    // ProviderManager对象,并且配置authenticationProviders和parentAuthenticationManager对象,
    // ProviderManager对象创建成功之后,再去对象后置处理器中处理一遍再返回
    @Override
    protected ProviderManager performBuild() throws Exception {
        if (!isConfigured()) {            
            return null;
        }
        ProviderManager providerManager = new ProviderManager(this.authenticationProviders,
                this.parentAuthenticationManager);
        if (this.eraseCredentials != null) {
            providerManager.setEraseCredentialsAfterAuthentication(this.eraseCredentials);
        }
        if (this.eventPublisher != null) {
            providerManager.setAuthenticationEventPublisher(this.eventPublisher);
        }
        providerManager = postProcess(providerManager);
        return providerManager;
    }
}
HttpSecurity

HttpSecurity的主要作用是用来构建一条过滤器链,并反映到代码上,也就是构建一个DefaultSecurityFilterChain对象
一个DefaultSecurityFilterChain对象包含一个路径匹配器和多个spring security过滤器,HttpSecurity中通过收集各种各样的xxxConfigurer,将spring security过滤器对应的配置类收集起来,并保存到父类AbstractConfiguredSecurityBuilderconfigurers变量中,在后续的构建过程中,再将这些xxxConfigurer构建为具体的spring security过滤器,同时添加到HttpSecurityfilters对象中。

/**
 * 其他过滤器的配置都和form表单登录配置类似,因此只讲一个。每一套过滤器链都会有一个AuthenticationManager对象来进行
 * 认证操作(如果认证失败,则会调用AuthenticationManager的parent再次进行认证)
 */
public final class HttpSecurity extends AbstractConfiguredSecurityBuilder<DefaultSecurityFilterChain, HttpSecurity>
		implements SecurityBuilder<DefaultSecurityFilterChain>, HttpSecurityBuilder<HttpSecurity> {
	// 返回一个FormLoginConfigurer,开发者可以在该对象的基础上继续完善对form表单的配置
	public FormLoginConfigurer<HttpSecurity> formLogin() throws Exception {
		return getOrApply(new FormLoginConfigurer<>());
	}

	// 开发者可以提前在外面配置好FormLoginConfigurer对象,然后直接传进来进行配置即可,
	// 返回值HttpSecurity则可以在方法返回后直接进行其他过滤器的配置
	public HttpSecurity formLogin(Customizer<FormLoginConfigurer<HttpSecurity>> formLoginCustomizer) throws Exception {
		formLoginCustomizer.customize(getOrApply(new FormLoginConfigurer<>()));
		return HttpSecurity.this;
	}

	// 配置用来执行认证的AuthenticationProvider对象
	@Override
	public HttpSecurity authenticationProvider(AuthenticationProvider authenticationProvider) {
		getAuthenticationRegistry().authenticationProvider(authenticationProvider);
		return this;
	}

	// 配置UserDetailsService对象
	@Override
	public HttpSecurity userDetailsService(UserDetailsService userDetailsService) throws Exception {
		getAuthenticationRegistry().userDetailsService(userDetailsService);
		return this;
	}

	private AuthenticationManagerBuilder getAuthenticationRegistry() {
		return getSharedObject(AuthenticationManagerBuilder.class);
	}

	// 触发AuthenticationManager对象的构建
	@Override
	protected void beforeConfigure() throws Exception {
		setSharedObject(AuthenticationManager.class, getAuthenticationRegistry().build());
	}

	// 进行DefaultSecurityFilterChain对象的构建,传入请求匹配器和过滤器集合filters,
	// 在构建之前,会先按照既定的顺序对filters进行排序
	@Override
	protected DefaultSecurityFilterChain performBuild() {
		this.filters.sort(OrderComparator.INSTANCE);
		List<Filter> sortedFilters = new ArrayList<>(this.filters.size());
		for (Filter filter : this.filters) {
			sortedFilters.add(((OrderedFilter) filter).filter);
		}
		return new DefaultSecurityFilterChain(this.requestMatcher, sortedFilters);
	}

	// 在某一个过滤器之后添加一个自定义的过滤器
	@Override
	public HttpSecurity addFilterAfter(Filter filter, Class<? extends Filter> afterFilter) {
		return addFilterAtOffsetOf(filter, 1, afterFilter);
	}

	// 在某一个过滤器之前添加一个自定义的过滤器
	@Override
	public HttpSecurity addFilterBefore(Filter filter, Class<? extends Filter> beforeFilter) {
		return addFilterAtOffsetOf(filter, -1, beforeFilter);
	}

	// 向过滤器链中添加一个过滤器,必须是spring security框架提供的过滤器的一个实例或者其扩展。实际上,
	// 在每一个xxxConfigurer的configure方法中,都会调用addFilter方法将构建好的过滤器添加到
	// HttpSecurity中的filters集合中
	@Override
	public HttpSecurity addFilter(Filter filter) {
		Integer order = this.filterOrders.getOrder(filter.getClass());
		if (order == null) {
			throw new IllegalArgumentException("The Filter class " + filter.getClass().getName()
					+ " does not have a registered order and cannot be added without a specified order. Consider using addFilterBefore or addFilterAfter instead.");
		}
		this.filters.add(new OrderedFilter(filter, order));
		return this;
	}

	// 可以在指定位置添加一个过滤器。需要注意的是,在同一个位置添加多个过滤器并不会覆盖现有的过滤器
	public HttpSecurity addFilterAt(Filter filter, Class<? extends Filter> atFilter) {
		return addFilterAtOffsetOf(filter, 0, atFilter);
	}

	// 调用父类的getConfigurer方法去查看是否已经有对应的配置类了,如果有,则直接返回;
	// 如果没有,则调用apply方法添加到父类的configurers变量中
	private <C extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity>> C getOrApply(C configurer)
			throws Exception {
		C existingConfig = (C) getConfigurer(configurer.getClass());
		if (existingConfig != null) {
			return existingConfig;
		}
		return apply(configurer);
	}
}
WebSecurity

相比于HttpSecurityWebSecurity是在一个更大的层面上去构建过滤器。
一个HttpSecurity可以构建一个过滤器链,也就是一个DefaultSecurityFilterChain,而一个项目可以存在多个HttpSecurity对象,也就可以构建多个DefaultSecurityFilterChain过滤器链。
WebSecurity负责将HttpSecurity所构建的DefaultSecurityFilterChain对象(可能有多个),以及其他一些需要忽略的请求,再次重新构建为一个FilterChainProxy对象,同时添加上HTTP防火墙。

public final class WebSecurity extends AbstractConfiguredSecurityBuilder<Filter, WebSecurity>
		implements SecurityBuilder<Filter>, ApplicationContextAware {
	// 保存所有被忽略的请求,因为在实际项目中,例如一些静态资源是不需要经过spring security过滤器链的
	private final List<RequestMatcher> ignoredRequests = new ArrayList<>();

	// 用来保存所有的HttpSecurity对象
	private final List<SecurityBuilder<? extends SecurityFilterChain>> securityFilterChainBuilders = new ArrayList<>();

	// 配置请求防火墙
	public WebSecurity httpFirewall(HttpFirewall httpFirewall) {
		this.httpFirewall = httpFirewall;
		return this;
	}

	// 每一个HttpSecurity对象创建成功之后,通过该方法添加到securityFilterChainBuilders集合中
	public WebSecurity addSecurityFilterChainBuilder(
			SecurityBuilder<? extends SecurityFilterChain> securityFilterChainBuilder) {
		this.securityFilterChainBuilders.add(securityFilterChainBuilder);
		return this;
	}

	// 具体的构建方法
	@Override
	protected Filter performBuild() throws Exception {
		// 统计过滤器链的总个数(被忽略的请求个数+通过HttpSecurity创建出来的过滤器链的个数)
		int chainSize = this.ignoredRequests.size() + this.securityFilterChainBuilders.size();
		List<SecurityFilterChain> securityFilterChains = new ArrayList<>(chainSize);

		for (RequestMatcher ignoredRequest : this.ignoredRequests) {
			// 对于被忽略的请求,在构建对象时,只是传入了请求匹配器,而没有传入对应的过滤器链,
			// 意味着这些被忽略掉的请求,将来不必经过spring security过滤器链
			securityFilterChains.add(new DefaultSecurityFilterChain(ignoredRequest));
		}

		// 遍历集合,调用build方法构建DefaultSecurityFilterChain对象,并存入securityFilterChains
		for (SecurityBuilder<? extends SecurityFilterChain> securityFilterChainBuilder : this.securityFilterChainBuilders) {
			securityFilterChains.add(securityFilterChainBuilder.build());
		}
		// 构建FilterChainProxy对象
		FilterChainProxy filterChainProxy = new FilterChainProxy(securityFilterChains);
		// 设置防火墙
		if (this.httpFirewall != null) {
			filterChainProxy.setFirewall(this.httpFirewall);
		}
		if (this.requestRejectedHandler != null) {
			filterChainProxy.setRequestRejectedHandler(this.requestRejectedHandler);
		}
		filterChainProxy.afterPropertiesSet();

		Filter result = filterChainProxy;
		this.postBuildAction.run();

		// 返回构建好的FilterChainProxy对象,之后通过spring提供的DelegatingFilterProxy
		// 将其嵌入到web filter(原生过滤器链中)
		return result;
	}
}
4.1.4FilterChainProxy

FilterChainProxy通过DelegatingFilterProxy代理过滤器被集成到web filter中,DelegatingFilterProxy作为一个代理对象,它不承载具体的业务。所以,spring security中的过滤器的最终执行,就是在FilterChainProxy中。
FilterChainProxy的源码比较长,拆分成一段一段来看:

// 保存的过滤器链
private List<SecurityFilterChain> filterChains;
// 过滤器链配置完成后的验证器,默认是NullFilterChainValidator,其实没有做任何验证
private FilterChainValidator filterChainValidator = new NullFilterChainValidator();
// 默认的防火墙对象
private HttpFirewall firewall = new StrictHttpFirewall();

public FilterChainProxy() {
}

public FilterChainProxy(SecurityFilterChain chain) {
	this(Arrays.asList(chain));
}

public FilterChainProxy(List<SecurityFilterChain> filterChains) {
	this.filterChains = filterChains;
}

由于FilterChainProxy本质上就是一个过滤器,因此它的核心方法就是doFilter

/**
 * doFilter方法相当于是整个spring security过滤器链的入口,一些具体的过滤器例如SecurityContextPersistenceFilter,
 * 都是在该方法之后执行的。作为整个过滤器链的入口,这里多了一个clearContext变量,如果是第一次执行该doFilter方法,
 * 执行完成后,在finally代码块中需要从SecurityContextHolder里清除用户信息,这个主要是为了防止用户没有正确配置
 * SecurityContextPersistenceFilter,从而导致登录用户信息没有被正确清除,进而发生内存泄露
 */
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
		throws IOException, ServletException {
	// doFilter第一次执行时,clearContext为true
	boolean clearContext = request.getAttribute(FILTER_APPLIED) == null;
	if (!clearContext) {
		doFilterInternal(request, response, chain);
		return;
	}
	try {
		request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
		doFilterInternal(request, response, chain);
	} catch (RequestRejectedException ex) {
		this.requestRejectedHandler.handle((HttpServletRequest) request, (HttpServletResponse) response, ex);
	} finally {
		SecurityContextHolder.clearContext();
		request.removeAttribute(FILTER_APPLIED);
	}
}

doFilter方法中,过滤器的具体执行则交给了doFilterInternal方法:

private void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain)
		throws IOException, ServletException {
	// 将request对象转换为一个FirewalledRequest对象,这个转换过程会进行http防火墙处理
	FirewalledRequest firewallRequest = this.firewall.getFirewalledRequest((HttpServletRequest) request);
	// 同时将response转换为HttpServletResponse
	HttpServletResponse firewallResponse = this.firewall.getFirewalledResponse((HttpServletResponse) response);
	// 获取当前请求对应的过滤器链
	List<Filter> filters = getFilters(firewallRequest);

	// 如果filters为null或者filters中没有元素,说明当前请求并不需要经过spring security过滤器链,
	// 此时执行firewallRequest.reset对http防火墙中的属性进行重置,再执行chain.doFilter,
	// 回到web filter中,spring security过滤器链将被跳过
	if (filters == null || filters.size() == 0) {
		firewallRequest.reset();
		chain.doFilter(firewallRequest, firewallResponse);
		return;
	}

	// 构建虚拟的过滤器链对象VirtualFilterChain(每次调用方法都会生成新的),并执行其doFilter
	VirtualFilterChain virtualFilterChain = new VirtualFilterChain(firewallRequest, chain, filters);
	virtualFilterChain.doFilter(firewallRequest, firewallResponse);
}

// 遍历filterChains集合,进而判断出当前请求和哪一个过滤器链是对应的
private List<Filter> getFilters(HttpServletRequest request) {
	for (SecurityFilterChain chain : this.filterChains) {
		if (chain.matches(request)) {
			return chain.getFilters();
		}
	}
	return null;
}

接下来看看VirtualFilterChain的具体实现:

private static final class VirtualFilterChain implements FilterChain {
	// 表示原生的过滤器链,执行它的doFilter方法会回到web filter中
	private final FilterChain originalChain;
	// 存储的本次请求的Filter
	private final List<Filter> additionalFilters;
	// 当前请求对象
	private final FirewalledRequest firewalledRequest;
	// 过滤器链的大小
	private final int size;
	// 过滤器链执行的下标
	private int currentPosition = 0;

	// 给相应的变量赋值
	private VirtualFilterChain(FirewalledRequest firewalledRequest, FilterChain chain,
			List<Filter> additionalFilters) {
		this.originalChain = chain;
		this.additionalFilters = additionalFilters;
		this.size = additionalFilters.size();
		this.firewalledRequest = firewalledRequest;
	}

	@Override
	public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
		// 判断当前执行的下标是否等于过滤器链的大小,如果相等,则说明整个过滤器链中的所有过滤器已经挨个走一遍了,
		// 此时先对http防火墙中的属性进行重置,然后调用originalChain.doFilter方法跳出spring security filter,
		// 回到web filter;如果不相等,则currentPosition自增,然后从过滤器链集合中取出一个过滤器去执行,注意,
		// 执行的时候第三个参数this表示当前对象(即VirtualFilterChain),这样在每一个过滤器执行完毕之后,
		// 最后的chain.doFilter方法又会回到当前doFilter方法中,继续下一个过滤器的调用
		if (this.currentPosition == this.size) {
			// Deactivate path stripping as we exit the security filter chain
			this.firewalledRequest.reset();
			this.originalChain.doFilter(request, response);
			return;
		}
		this.currentPosition++;
		Filter nextFilter = this.additionalFilters.get(this.currentPosition - 1);
		nextFilter.doFilter(request, response, this);
	}
}
4.1.5SecurityConfigurer

SecurityConfigurer的实现类比较多,主要梳理下常见的实现类:

SecurityConfigurer
// AbstractConfiguredSecurityBuilder里的init方法和configure
// 其实就是在遍历执行不同配置类的init和configure方法
public interface SecurityConfigurer<O, B extends SecurityBuilder<O>> {
	// 用来完成配置类的初始化操作,例如SecurityBuilder的初始化
	void init(B builder) throws Exception;

	// 进行配置类的配置,例如SecurityBuilder的配置
	void configure(B builder) throws Exception;
}

SecurityConfigurer的子类非常多,因为每一个过滤器都有自己对应的xxxConfigurer,着重介绍几个关键的:

Spring Cloud Gateway自定义拦截器开发 springsecurity拦截器链_List_03

SecurityConfigurerAdapter
public abstract class SecurityConfigurerAdapter<O, B extends SecurityBuilder<O>> implements SecurityConfigurer<O, B> {
    // 为每一个配置类都提供一个SecurityBuilder对象,将来通过它构建出具体的配置对象
    private B securityBuilder;

    private CompositeObjectPostProcessor objectPostProcessor = new CompositeObjectPostProcessor();

    @Override
	public void init(B builder) throws Exception {
	}

	@Override
	public void configure(B builder) throws Exception {
	}

    // 通过and方法返回SecurityBuilder对象,这样方便不同的配置类进行链式配置
    public B and() {
		return getBuilder();
	}

    protected final B getBuilder() {
		Assert.state(this.securityBuilder != null, "securityBuilder cannot be null");
		return this.securityBuilder;
	}

    protected <T> T postProcess(T object) {
		return (T) this.objectPostProcessor.postProcess(object);
	}

	// 向复合的对象后置处理器添加新的ObjectPostProcessor实例
    public void addObjectPostProcessor(ObjectPostProcessor<?> objectPostProcessor) {
		this.objectPostProcessor.addObjectPostProcessor(objectPostProcessor);
	}

    public void setBuilder(B builder) {
		this.securityBuilder = builder;
	}

    // 内部类,这是一个复合的对象后置处理器
    private static final class CompositeObjectPostProcessor implements ObjectPostProcessor<Object> {
        private List<ObjectPostProcessor<?>> postProcessors = new ArrayList<>();

        public Object postProcess(Object object) {
			for (ObjectPostProcessor opp : this.postProcessors) {
				Class<?> oppClass = opp.getClass();
				Class<?> oppType = GenericTypeResolver.resolveTypeArgument(oppClass, ObjectPostProcessor.class);
				if (oppType == null || oppType.isAssignableFrom(object.getClass())) {
					object = opp.postProcess(object);
				}
			}
			return object;
		}

        private boolean addObjectPostProcessor(ObjectPostProcessor<?> objectPostProcessor) {
			boolean result = this.postProcessors.add(objectPostProcessor);
			this.postProcessors.sort(AnnotationAwareOrderComparator.INSTANCE);
			return result;
		}
    }
}
UserDetailsAwareConfigurer

UserDetailsAwareConfigurer的子类主要负责配置用户认证相关的组件,如UserDetailsService等,UserDetailsAwareConfigurer中提供了获取UserDetailsService的抽象方法,具体实现则在它的子类中。

Spring Cloud Gateway自定义拦截器开发 springsecurity拦截器链_java_04

  • AbstractDaoAuthenticationConfigurer:完成对DaoAuthenticationProvider的配置。
  • UserDetailsServiceConfigurer:完成对UserDetailsService的配置。
  • UserDetailsManagerConfigurer:使用UserDetailsManager构建用户对象,完成对AuthenticationManagerBuilder的填充。
  • JdbcUserDetailsManagerConfigurer:配置JdbcUserDetailsManager并填充到AuthenticationManagerBuilder中。
  • InMemoryUserDetailsManagerConfigurer:配置InMemoryUserDetailsManager
  • DaoAuthenticationConfigurer:完成对DaoAuthenticationProvider的配置。
AbstractHttpConfigurer

AbstractHttpConfigurer主要是为了给在HttpSecurity中使用的配置类添加一个方便的父类,提取出共同的操作。

public abstract class AbstractHttpConfigurer<T extends AbstractHttpConfigurer<T, B>, B extends HttpSecurityBuilder<B>>
		extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, B> {
    // 表示禁用某一个配置,本质上就是从构建器的configurers集合中移除某一个配置类,
    // 这样在将来构建的时候就不存在该配置类,那么对应的功能也就不存在
    public B disable() {
		getBuilder().removeConfigurer(getClass());
		return getBuilder();
	}

    // 表示给某一个对象添加一个对象后置处理器,由于该方法的返回值是当前对象,所以该方法可以用在链式配置中
    public T withObjectPostProcessor(ObjectPostProcessor<?> objectPostProcessor) {
		addObjectPostProcessor(objectPostProcessor);
		return (T) this;
	}
}

AbstractHttpConfigurer的实现类比较多,基本上都用来配置各种各样的过滤器:

配置类名称

作用

HttpBasicConfigurer

配置基于http basic认证的过滤器BasicAuthenticationFilter

LogoutConfigurer

配置注销登录过滤器LogoutFilter

RequestCacheConfigurer

配置请求缓存过滤器RequestCacheAwareFilter

RememberMeConfigurer

配置remember-me登录过滤器RememberMeAuthenticationFilter

ServletApiConfigurer

配置包装原始请求过滤器SecurityContextHolderAwareRequestFilter

DefaultLoginPageConfigurer

配置提供默认登录页面的过滤器DefaultLoginPageGeneratingFilter和默认注销页面的过滤器DefaultLogoutPageGeneratingFilter

SessionManagementConfigurer

配置session管理过滤器SessionManagementFilterConcurrentSessionFilter

PortMapperConfigurer

配置一个共享的PortMapper实例,以便在HTTP和HTTPS之间重定向时确定端口

ExceptionHandlingConfigurer

配置异常处理过滤器ExceptionTranslationFilter

HeadersConfigurer

配置安全相关的响应头信息

CsrfConfigurer

配置防范CSRF攻击过滤器CsrfFilter

OAuth2ClientConfigurer

配置OAuth2相关的过滤器OAuth2AuthorizationRequestRedirectFilterOAuth2AuthorizationCodeGrantFilter

ImplicitGrantConfigurer

配置OAuth2认证请求重定向的过滤器OAuth2AuthorizationRequestRedirectFilter

AnonymousConfigurer

配置匿名过滤器AnonymousAuthenticationFilter

JeeConfigurer

配置J2EE身份预校验过滤器J2eePreAuthenticatedProcessingFilter

ChannelSecurityConfigurer

配置请求协议处理过滤器ChannelProcessingFilter

CorsConfigurer

配置处理跨域过滤器CorsFilter

SecurityContextConfigurer

配置登录信息存储和恢复的过滤器SecurityContextPersistenceFilter

OAuth2ResourceServerConfigurer

配置OAuth2身份请求认证过滤器BearerTokenAuthenticationFilter

AbstractAuthenticationFilterConfigurer

身份认证配置类的父类

FormLoginConfigurer

配置身份认证过滤器UsernamePasswordAuthenticationFilter和默认登录页面的过滤器DefaultLoginPageGeneratingFilter

OAuth2LoginConfigurer

配置OAuth2认证请求重定向的过滤器OAuth2AuthorizationRequestRedirectFilter和处理第三方回调过滤器OAuth2LoginAuthenticationFilter

OpenIDLoginConfigurer

配置OpenID身份认证过滤器OpenIDAuthenticationFilter

Saml2LoginConfigurer

配置SAML2.0身份认证过滤器Saml2WebSsoAuthenticationFilterSaml2WebSsoAuthenticationRequestFilter

X509Configurer

配置X509身份认证过滤器X509AuthenticationFilter

AbstractInterceptUrlConfigurer

拦截器配置类的父类

UrlAuthorizationConfigurer

配置基于URL的权限认证拦截器FilterSecurityInterceptor

ExpressionUrlAuthorizationConfigurer

配置基于SpEL表达式的URL权限认证拦截器FilterSecurityInterceptor

GlobalAuthenticationConfigurerAdapter

主要用于配置全局AuthenticationManagerBuilder,在AuthenticationConfiguration类中会自动使用GlobalAuthenticationConfigurerAdapter提供的bean来配置全局AuthenticationManagerBuilder
之前提到过,默认情况下,ProviderManager有一个parent,这个parent就是通过这里的全局AuthenticationManagerBuilder来构建的。
GlobalAuthenticationConfigurerAdapter有四个不同的子类:

Spring Cloud Gateway自定义拦截器开发 springsecurity拦截器链_List_05

  • InitializeAuthenticationProviderBeanManagerConfigurer:初始化全局的AuthenticationProvider对象。
  • InitializeAuthenticationProviderManagerConfigurer:配置全局的AuthenticationProvider对象,配置过程就是从spring容器中查找AuthenticationProvider并设置给全局的AuthenticationManagerBuilder对象。
  • InitializeUserDetailsBeanManagerConfigurer:初始化全局的UserDetailsService对象。
  • InitializeUserDetailsManagerConfigurer:配置全局的UserDetailsService对象,配置过程就是从spring容器中查找UserDetailsService,并设置给全局的AuthenticationManagerBuilder对象。
  • EnableGlobalAuthenticationAutowiredConfigurer:从spring容器中加载被@EnableGlobalAuthentication注解标记的bean。
WebSecurityConfigurer

这是一个空接口,可以通过它来自定义WebSecurity。其只有一个实现类就是WebSecurityConfigurerAdapter,在大多数情况下,开发者通过继承WebSecurityConfigurerAdapter来实现对WebSecurity的自定义配置。

WebSecurityConfigurerAdapter

可以方便创建WebSecurityConfigurer实例的基类,开发者可以通过覆盖其中的方法完成对HttpSecurityWebSecurity的定制。
WebSecurityConfigurerAdapter中声明了两个AuthenticationManagerBuilder对象用来构建AuthenticationManager

// 负责构建局部的AuthenticationManager
private AuthenticationManagerBuilder authenticationBuilder;

// 负责构建全局的AuthenticationManager
private AuthenticationManagerBuilder localConfigureAuthenticationBldr;

局部的AuthenticationManager是和每一个HttpSecurity对象绑定的,而全局的AuthenticationManager对象则是所有局部AuthenticationManagerparent
需要注意的是,localConfigureAuthenticationBldr并非总是有用,在开发者没有重写configure(AuthenticationManagerBuilder)方法的情况下,全局的AuthenticationManager对象是由AuthenticationConfiguration类中的getAuthenticationManager方法提供的,如果用户重写了configure(AuthenticationManagerBuilder)方法,则全局的AuthenticationManager就由localConfigureAuthenticationBldr负责构建。
WebSecurityConfigurerAdapter类的初始化方法如下:

public abstract class WebSecurityConfigurerAdapter implements WebSecurityConfigurer<WebSecurity> {
	// 创建并配置一个HttpSecurity的实例,之后添加到WebSecurity中
    @Override
	public void init(WebSecurity web) throws Exception {
        // 获取一个HttpSecurity实例
		HttpSecurity http = getHttp();
        // 添加到WebSecurity中
		web.addSecurityFilterChainBuilder(http).postBuildAction(() -> {
			FilterSecurityInterceptor securityInterceptor = http.getSharedObject(FilterSecurityInterceptor.class);
			web.securityInterceptor(securityInterceptor);
		});
	}

    protected final HttpSecurity getHttp() throws Exception {
        // 如果已经初始化则直接返回
		if (this.http != null) {
			return this.http;
		}
        // 给localConfigureAuthenticationBldr设置事件发布器
		AuthenticationEventPublisher eventPublisher = getAuthenticationEventPublisher();
		this.localConfigureAuthenticationBldr.authenticationEventPublisher(eventPublisher);
        // 获取全局的AuthenticationManager对象
		AuthenticationManager authenticationManager = authenticationManager();
		this.authenticationBuilder.parentAuthenticationManager(authenticationManager);
		Map<Class<?>, Object> sharedObjects = createSharedObjects();
        // 创建HttpSecurity实例
		this.http = new HttpSecurity(this.objectPostProcessor, this.authenticationBuilder, sharedObjects);
		if (!this.disableDefaults) {
            // 应用默认配置,添加默认的过滤器
			applyDefaultConfiguration(this.http);
			ClassLoader classLoader = this.context.getClassLoader();
			List<AbstractHttpConfigurer> defaultHttpConfigurers = SpringFactoriesLoader
					.loadFactories(AbstractHttpConfigurer.class, classLoader);
			for (AbstractHttpConfigurer configurer : defaultHttpConfigurers) {
				this.http.apply(configurer);
			}
		}
        // 进行扩展配置
		configure(this.http);
		return this.http;
	}

    protected void configure(HttpSecurity http) throws Exception {
		http.authorizeRequests((requests) -> requests.anyRequest().authenticated());
		http.formLogin();
		http.httpBasic();
	}

    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
		this.disableLocalConfigureAuthenticationBldr = true;
	}

    protected AuthenticationManager authenticationManager() throws Exception {
		if (!this.authenticationManagerInitialized) {
			// 如果全局的AuthenticationManager对象还没有初始化,则先调用configure方法
			configure(this.localConfigureAuthenticationBldr);
			if (this.disableLocalConfigureAuthenticationBldr) {
				// 通过调用getAuthenticationManager方法获取全局的AuthenticationManager对象并返回
				this.authenticationManager = this.authenticationConfiguration.getAuthenticationManager();
			}
			else {
                // 如果开发者重写了configure(AuthenticationManagerBuilder)方法,
                // 则disableLocalConfigureAuthenticationBldr变量就一直是false
				this.authenticationManager = this.localConfigureAuthenticationBldr.build();
			}
			this.authenticationManagerInitialized = true;
		}
		return this.authenticationManager;
	}
}
4.1.6初始化流程分析

在spring boot中使用spring security,初始化就从spring security的自动化配置类中开始:

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(DefaultAuthenticationEventPublisher.class)
@EnableConfigurationProperties(SecurityProperties.class)
// 导入配置类(前三个为主)
@Import({ SpringBootWebSecurityConfiguration.class, WebSecurityEnablerConfiguration.class,
		SecurityDataConfiguration.class, ErrorPageSecurityFilterConfiguration.class })
public class SecurityAutoConfiguration {
    // 定义默认的事件发布器
	@Bean
	@ConditionalOnMissingBean(AuthenticationEventPublisher.class)
	public DefaultAuthenticationEventPublisher authenticationEventPublisher(ApplicationEventPublisher publisher) {
		return new DefaultAuthenticationEventPublisher(publisher);
	}

}
  • SpringBootWebSecurityConfiguration:主要作用是在开发者没有提供WebSecurityConfigurerAdapter实例的情况下,由其负责提供一个默认的实例,即导入web安全的默认配置。如果开发者指定了自己的WebSecurityConfigurerAdapter或者SecurityFilterChain,这将会失效。
  • SecurityDataConfiguration:主要提供了一个SecurityEvaluationContextExtension实例,以便通过SpEL为经过身份验证的用户提供数据查询(需要引入相关依赖才能使用)。
  • WebSecurityEnablerConfiguration:分析的重点。
  • ErrorPageSecurityFilterConfiguration:配置ErrorPageSecurityFilter
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(name = BeanIds.SPRING_SECURITY_FILTER_CHAIN)
@ConditionalOnClass(EnableWebSecurity.class)
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
@EnableWebSecurity	// 该注解引入了关键的配置类WebSecurityConfiguration
class WebSecurityEnablerConfiguration {

}

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import({ WebSecurityConfiguration.class, SpringWebMvcImportSelector.class, OAuth2ImportSelector.class,
		HttpSecurityConfiguration.class })
@EnableGlobalAuthentication
@Configuration
public @interface EnableWebSecurity {
	boolean debug() default false;
}
  • WebSecurityConfiguration:用来配置WebSecurity(重点分析)。
  • SpringWebMvcImportSelector:判断当前环境是否存在spring MVC,如果存在,则引入相关配置。
  • OAuth2ImportSelector:判断当前环境是否存在OAuth2,如果存在,则引入相关配置。
  • HttpSecurityConfiguration:公开HttpSecuritybean的配置。

另外还有一个@EnableGlobalAuthentication注解,用来开启全局配置:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(AuthenticationConfiguration.class)	// 导入配置类AuthenticationConfiguration
@Configuration
public @interface EnableGlobalAuthentication {

}

可以看到,spring security的自动化配置类主要导入了两个类:WebSecurityConfigurationAuthenticationConfiguration

WebSecurityConfiguration

主要功能就是为了构建spring security过滤器链代理对象FilterChainProxy。在WebSecurityConfiguration中会首先构建WebSecurity对象,再利用它来构建FilterChainProxy
先来看下其中定义的属性:

// 通过实现ImportAware接口可以方便地获取到@EnableWebSecurity中的属性值,即debug
// 通过实现BeanClassLoaderAware接口可以方便地获取到ClassLoader对象
@Configuration(proxyBeanMethods = false)
public class WebSecurityConfiguration implements ImportAware, BeanClassLoaderAware {
    // 需要构建的WebSecurity对象
    private WebSecurity webSecurity;

    private Boolean debugEnabled;

    // 保存了所有的配置类,也就是WebSecurityConfigurerAdapter对象,一个WebSecurityConfigurerAdapter对象可以创建
    // 一个HttpSecurity,进而构建出一条过滤器链,多个WebSecurityConfigurerAdapter对象就可以构建出多条过滤器链
    private List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers;

    private ClassLoader beanClassLoader;

    // 对象后置处理器,注意这个对象是直接从spring容器中注入的
    @Autowired(required = false)
    private ObjectPostProcessor<Object> objectObjectPostProcessor;
}

setFilterChainProxySecurityConfigurer主要用来构建一个WebSecurity对象,并且加载所有的配置类对象:

/**
 * 设置用于创建web配置的<SecurityConfigurer<FilterChainProxy, WebSecurityBuilder>实例,初始化WebSecurity对象,
 * 同时收集到所有的自定义配置类。
 * objectPostProcessor是一个对象后置处理器,由于该方法有一个@Autowired注解,会自动查找需要注入的参数;
 * required属性为false即在方法参数注入的时候,有就注入,没有则忽略,主要是针对第二个参数。
 */
@Autowired(required = false)
public void setFilterChainProxySecurityConfigurer(ObjectPostProcessor<Object> objectPostProcessor,
        @Value("#{@autowiredWebSecurityConfigurersIgnoreParents.getWebSecurityConfigurers()}") List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers)
        throws Exception {
    // 去对象后置处理器中进行处理,这样就将WebSecurity对象注册到spring容器中了
    this.webSecurity = objectPostProcessor.postProcess(new WebSecurity(objectPostProcessor));
    if (this.debugEnabled != null) {
        this.webSecurity.debug(this.debugEnabled);
    }

    // 根据每一个配置类的@Order注解对集合中的所有配置类进行排序,因为一个配置类对应一个过滤器链,当请求到来后,
    // 需要先和哪个过滤器链进行匹配,这里必然存在一个优先级问题。所以如果开发者自定义了多个配置类,则需要通过
    // @Order注解标记多个配置类的优先级
    webSecurityConfigurers.sort(AnnotationAwareOrderComparator.INSTANCE);
    Integer previousOrder = null;
    Object previousConfig = null;
    for (SecurityConfigurer<Filter, WebSecurity> config : webSecurityConfigurers) {
        Integer order = AnnotationAwareOrderComparator.lookupOrder(config);
        // 检查是否存在优先级相等的配置类
        if (previousOrder != null && previousOrder.equals(order)) {
            throw new IllegalStateException("@Order on WebSecurityConfigurers must be unique. Order of " + order
                    + " was already used on " + previousConfig + ", so it cannot be used on " + config + " too.");
        }
        previousOrder = order;
        previousConfig = config;
    }
    for (SecurityConfigurer<Filter, WebSecurity> webSecurityConfigurer : webSecurityConfigurers) {
        // 将配置类添加到WebSecurity父类中的configurers集合中,将来遍历该集合并分别调用配置类的init和configure方法
        // 完成配置类的初始化操作
        this.webSecurity.apply(webSecurityConfigurer);
    }
    this.webSecurityConfigurers = webSecurityConfigurers;
}

@Bean
public static AutowiredWebSecurityConfigurersIgnoreParents autowiredWebSecurityConfigurersIgnoreParents(
        ConfigurableListableBeanFactory beanFactory) {
    return new AutowiredWebSecurityConfigurersIgnoreParents(beanFactory);
}

// 获取spring容器中所有的WebSecurityConfigurer实例,也就是开发者自定义的各种各样
// 继承自WebSecurityConfigurerAdapter的配置类,如果没有自定义,则获取默认的配置类
public List<SecurityConfigurer<Filter, WebSecurity>> getWebSecurityConfigurers() {
    List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers = new ArrayList<>();
    Map<String, WebSecurityConfigurer> beansOfType = this.beanFactory.getBeansOfType(WebSecurityConfigurer.class);
    for (Entry<String, WebSecurityConfigurer> entry : beansOfType.entrySet()) {
        webSecurityConfigurers.add(entry.getValue());
    }
    return webSecurityConfigurers;
}

// 有了WebSecurity对象和配置类,接下来就可以构建过滤器FilterChainProxy了
@Bean(name = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME)
public Filter springSecurityFilterChain() throws Exception {
    boolean hasConfigurers = this.webSecurityConfigurers != null && !this.webSecurityConfigurers.isEmpty();
    boolean hasFilterChain = !this.securityFilterChains.isEmpty();

    // 如果不存在配置类且不存在过滤器链,则创建一个匿名的WebSecurityConfigurerAdapter对象并注册到spring容器中
    if (!hasConfigurers && !hasFilterChain) {
        WebSecurityConfigurerAdapter adapter = this.objectObjectPostProcessor
                .postProcess(new WebSecurityConfigurerAdapter() {
                });
        this.webSecurity.apply(adapter);
    }

    // securityFilterChains通过SecurityAutoConfiguration中导入SpringBootWebSecurityConfiguration进行配置
    for (SecurityFilterChain securityFilterChain : this.securityFilterChains) {
        this.webSecurity.addSecurityFilterChainBuilder(() -> securityFilterChain);
        for (Filter filter : securityFilterChain.getFilters()) {
            if (filter instanceof FilterSecurityInterceptor) {
                this.webSecurity.securityInterceptor((FilterSecurityInterceptor) filter);
                break;
            }
        }
    }

    for (WebSecurityCustomizer customizer : this.webSecurityCustomizers) {
        customizer.customize(this.webSecurity);
    }

    // 调用build方法进行构建
    return this.webSecurity.build();
}

WebSecurity对象的build方法执行后,首先会对所有的配置类即WebSecurityConfigurerAdapter实例进行构建,在WebSecurityConfigurerAdapterinit方法中,又会完成HttpSecurity的构建,而HttpSecurity的构建过程中,则会完成局部AuthenticationManager对象以及每一个具体的过滤器的构建
这就是整个过滤器链的构建流程。

AuthenticationConfiguration

主要是做全局的配置,同时提供一个全局的AuthenticationManager实例(仅列出核心代码):

/**
 * 如果开发者自定义配置类中重写了configure(AuthenticationManagerBuilder)方法,
 * 这里的全局AuthenticationManager对象将不会生效,而大部分情况下都会重写。
 */
@Configuration(proxyBeanMethods = false)
// 导入配置类ObjectPostProcessorConfiguration,提供一个基本的对象后置处理器AutowireBeanFactoryObjectPostProcessor,
// 用来将一个对象注册到spring容器中,在其他配置类中所见到的ObjectPostProcessor实例其实都是这里提供的
@Import(ObjectPostProcessorConfiguration.class)
public class AuthenticationConfiguration {
	/**
	 * 定义一个AuthenticationManagerBuilder实例,用来构建全局的AuthenticationManager对象,这个过程
	 * 会从spring容器中查找AuthenticationEventPublisher实例设置给AuthenticationManagerBuilder对象。
	 */
	@Bean
	public AuthenticationManagerBuilder authenticationManagerBuilder(ObjectPostProcessor<Object> objectPostProcessor,
			ApplicationContext context) {
		LazyPasswordEncoder defaultPasswordEncoder = new LazyPasswordEncoder(context);
		AuthenticationEventPublisher authenticationEventPublisher = getBeanOrNull(context,
				AuthenticationEventPublisher.class);
		DefaultPasswordEncoderAuthenticationManagerBuilder result = new DefaultPasswordEncoderAuthenticationManagerBuilder(
				objectPostProcessor, defaultPasswordEncoder);
		if (authenticationEventPublisher != null) {
			result.authenticationEventPublisher(authenticationEventPublisher);
		}
		return result;
	}

	@Bean
	public static GlobalAuthenticationConfigurerAdapter enableGlobalAuthenticationAutowiredConfigurer(
			ApplicationContext context) {
		// 用来从spring容器中加载被@EnableGlobalAuthentication注解标记的bean
		return new EnableGlobalAuthenticationAutowiredConfigurer(context);
	}

	@Bean
	public static InitializeUserDetailsBeanManagerConfigurer initializeUserDetailsBeanManagerConfigurer(
			ApplicationContext context) {
		// 用来初始化全局的UserDetailsService对象
		return new InitializeUserDetailsBeanManagerConfigurer(context);
	}

	@Bean
	public static InitializeAuthenticationProviderBeanManagerConfigurer initializeAuthenticationProviderBeanManagerConfigurer(
			ApplicationContext context) {
		// 用来初始化全局的AuthenticationProvider对象
		return new InitializeAuthenticationProviderBeanManagerConfigurer(context);
	}

	// 构建具体的AuthenticationManager对象
	public AuthenticationManager getAuthenticationManager() throws Exception {
		// 判断AuthenticationManager对象是否已经初始化
		if (this.authenticationManagerInitialized) {
			return this.authenticationManager;
		}

		// 从容器中获取到AuthenticationManagerBuilder对象
		AuthenticationManagerBuilder authBuilder = this.applicationContext.getBean(AuthenticationManagerBuilder.class);

		// AuthenticationManagerDelegator用来防止在初始化AuthenticationManager时进行无限递归
		if (this.buildingAuthenticationManager.getAndSet(true)) {
			return new AuthenticationManagerDelegator(authBuilder);
		}
		
		// 遍历globalAuthConfigurers配置类集合,也就是上面带有@Bean注解的三个配置类
		for (GlobalAuthenticationConfigurerAdapter config : this.globalAuthConfigurers) {
			authBuilder.apply(config);
		}
		
		this.authenticationManager = authBuilder.build();
		
		if (this.authenticationManager == null) {
			this.authenticationManager = getAuthenticationManagerBean();
		}
		
		this.authenticationManagerInitialized = true;
		return this.authenticationManager;
	}
}

这就是全局的AuthenticationManager的构建过程。
整体来说,AuthenticationConfiguration的作用主要体现在两方面:第一就是导入了ObjectPostProcessorConfiguration配置类;第二则是提供了一个全局的AuthenticationManager对象。
如果开发者在自定义配置类中重写了configure(AuthenticationManagerBuilder)方法,这里的全局AuthenticationManager对象将不会生效,而大部分情况下,开发者都会重写该方法。

4.2ObjectPostProcessor使用

所有的过滤器都由对应的配置类来负责创建,配置类在将过滤器创建成功之后,会调用父类的postProcess方法,在该方法中,会遍历CompositeObjectPostProcessor对象所维护的集合中存储的所有ObjectPostProcessor对象,并调用其postProcess方法对对象进行后置处理。默认情况下,CompositeObjectPostProcessor对象中所维护的集合中只有一个对象那就是AutowireBeanFactoryObjectPostProcessor,调用其postProcess方法可以将对象注册到spring容器中去。
开发者可以自定义ObjectPostProcessor对象,并添加到CompositeObjectPostProcessor所维护的集合中,此时,当一个过滤器在创建成功之后,就会被两个对象后置处理器处理,第一个是默认的对象后置处理器,负责将对象注册到spring容器中;第二个是自定义的对象后置处理器,可以完成一些个性化配置。
自定义ObjectPostProcessor对象比较典型的用法是动态权限配置。

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .anyRequest().authenticated()
                .and()
				// 调用formLogin方法之后,开启了FormLoginConfigurer的配置,其作用是为了配置
				// UsernamePasswordAuthenticationFilter过滤器,在formLogin方法执行完毕后,
                // 调用withObjectPostProcessor方法对UsernamePasswordAuthenticationFilter进行二次处理
                .formLogin()
                .withObjectPostProcessor(new ObjectPostProcessor<UsernamePasswordAuthenticationFilter>() {
                    @Override
                    public <O extends UsernamePasswordAuthenticationFilter> O postProcess(O object) {
                    	// 修改登录参数的key
                        object.setUsernameParameter("name");
                        object.setPasswordParameter("passwd");
                        // 同时配置一个登录成功的处理器
                        object.setAuthenticationSuccessHandler((request, response, authentication) -> {
                            response.getWriter().write("login success");
                        });

                        return object;
                    }
                })
                .and()
                .csrf().disable();
    }
}
4.3多种用户定义方式

目前来说,定义用户主要是两种方式:

  1. 重写configure(AuthenticationManagerBuilder)方法的方式。
  2. 直接向spring容器中注入UserDetailsService对象。

那么,这两种用户定义方式有什么区别?
在spring security中存在两种类型的AuthenticationManager,一种是全局的,另一种则是局部的。局部的AuthenticationManagerHttpSecurity进行配置,而全局的则可以不用配置,系统会默认提供一个全局的AuthenticationManager对象,也可以通过重写configure(AuthenticationManagerBuilder)方法进行全局配置。
当进行用户身份验证时,首先会通过局部的AuthenticationManager对象进行验证,如果验证失败,则会调用其parent也就是全局的对象再次进行验证。
所以开发者在定义用户时,也分为两种情况,一种是针对局部AuthenticationManager定义的用户,另一种则是针对全局AuthenticationManager定义的用户。
先来看看针对局部AuthenticationManager定义的用户(此时控制台仍会打印默认用户的密码):

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
	/**
	 * 针对局部的AuthenticationManager定义的用户。需要注意的是,此时系统仍会自动生成全局用户。系统自动
	 * 提供的用户对象实际上就是往spring容器中注册了一个InMemoryUserDetailsManager对象。而由于没有重写
	 * configure(AuthenticationManagerBuilder)方法,这意味着,全局的AuthenticationManager是通过
	 * AuthenticationConfiguration#getAuthenticationManager方法自动生成的,在生成的过程中,会从
	 * spring容器中查找对应的UserDetailsService实例进行配置(具体配置在InitializeUserDetailsManagerConfigurer中)。
	 * 当开始执行登录后,会先调用局部AuthenticationManager去进行登录校验,如果登录的用户名/密码是javaboy/123,
	 * 那就直接登录成功,否则登录失败。当登录失败后,会继续调用局部AuthenticationManager的parent继续进行校验。
	 */
	@Override
    protected void configure(HttpSecurity http) throws Exception {
    	// 基于内存来管理用户
        InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
        manager.createUser(User.withUsername("javaboy").password("{noop}123").roles("admin").build());

        http.authorizeRequests()
                .anyRequest().authenticated()
                .and()
                .formLogin()
                .permitAll()
                .and()
                // 配置到局部的AuthenticationManager中
                .userDetailsService(manager)
                .csrf().disable();
    }
}

也可以将定义的用户配置给全局的AuthenticationManager,由于默认的全局AuthenticationManager在配置时会从spring容器中查找UserDetailsService实例,所以如果针对全局AuthenticationManager配置用户,只需要往spring容器中注入一个UserDetailsService实例即可。

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
	// 全局的
	@Bean
    UserDetailsService us() {
        InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
        manager.createUser(User.withUsername("江南一点雨").password("{noop}123").roles("admin").build());
        return manager;
    }

	@Override
    protected void configure(HttpSecurity http) throws Exception {
    	// 局部的
        InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
        manager.createUser(User.withUsername("javaboy").password("{noop}123").roles("admin").build());

        http.authorizeRequests()
                .anyRequest().authenticated()
                .and()
                .formLogin()
                .permitAll()
                .and()
                .userDetailsService(manager)
                .csrf().disable();
    }
}

当然,开发者也可以不使用spring security提供的默认的全局AuthenticationManager对象,而是通过重写configure(AuthenticationManagerBuilder)方法来自定义全局AuthenticationManager对象:

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
	@Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication().withUser("javagirl").password("{noop}123").roles("admin");
    }

	@Override
    protected void configure(HttpSecurity http) throws Exception {
        InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
        manager.createUser(User.withUsername("javaboy").password("{noop}123").roles("admin").build());

        http.authorizeRequests()
                .anyRequest().authenticated()
                .and()
                .formLogin()
                .permitAll()
                .and()
                .userDetailsService(manager)
                .csrf().disable();
    }
}

一旦重写了configure(AuthenticationManagerBuilder)方法,则全局的AuthenticationManager对象将不再通过AuthenticationConfiguration#getAuthenticationManager方法来构建,而是通过WebSecurityConfigurerAdapter中的localConfigureAuthenticationBldr变量来构建,该变量也是重写的configure(AuthenticationManagerBuilder)方法的参数。
需要注意的是,一旦重写了configure(AuthenticationManagerBuilder)方法,那么全局的AuthenticationManager对象中使用的用户,将以configure(AuthenticationManagerBuilder)方法中定义的用户为准。此时,如果还向spring容器中注入了另外一个UserDetailsService实例,那么该实例中定义的用户将不会生效(因为AuthenticationConfiguration#getAuthenticationManager方法没有被调用)。

4.4定义多个过滤器链

在spring security中可以同时存在多个过滤器链,一个WebSecurityConfigurerAdapter的实例就可以配置一条过滤器链。

// 可以使用postman进行测试
@Configuration
public class SecurityConfig {
    @Bean
    UserDetailsService us() {
        InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
        manager.createUser(User.withUsername("javaboy").password("{noop}123").roles("admin").build());
        return manager;
    }

    @Configuration
    @Order(1)
    static class SecurityConfig01 extends WebSecurityConfigurerAdapter {
        /**
         * 由于SecurityConfig01中只是重写了configure(HttpSecurity http),所以注册到spring容器中的
         * UserDetailsService(us)将作为局部Authentication的parent对应的用户。所以,如果登录的路径是/bar/login,
         * 那么可以使用bar/123和javaboy/123两个用户进行登录。
         */
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
            manager.createUser(User.withUsername("bar").password("{noop}123").roles("admin").build());

            http.antMatcher("/bar/**")
                    .authorizeRequests()
                    .anyRequest().authenticated()
                    .and()
                    .formLogin()
                    .loginProcessingUrl("/bar/login")
                    .successHandler((request, response, authentication) -> {
                        response.setContentType("application/json;charset=utf-8");
                        String s = new ObjectMapper().writeValueAsString(authentication);
                        response.getWriter().write(s);
                    })
                    .permitAll()
                    .and()
                    .csrf().disable()
                    .userDetailsService(manager);
        }
    }

    @Configuration
    @Order(2)
    static class SecurityConfig02 extends WebSecurityConfigurerAdapter {
        /**
         * 由于重写了configure(AuthenticationManagerBuilder auth),在该方法中定义了局部AuthenticationManager
         * 的parent对应的用户,此时注册到spring容器中的UserDetailsService(us)实例对于/foo/**过滤器链不再生效。
         * 换句话说,如果登录路径是/foo/login,可以使用foo/123和javagirl/123两个用户进行登录,而不可以使用javaboy/123。
         */
        @Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            auth.inMemoryAuthentication().withUser("javagirl").password("{noop}123").roles("admin");
        }

        @Override
        protected void configure(HttpSecurity http) throws Exception {
            InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
            manager.createUser(User.withUsername("foo").password("{noop}123").roles("admin").build());

            http.antMatcher("/foo/**")
                    .authorizeRequests()
                    .anyRequest().authenticated()
                    .and()
                    .formLogin()
                    .loginProcessingUrl("/foo/login")
                    .successHandler((request, response, authentication) -> {
                        response.setContentType("application/json;charset=utf-8");
                        String s = new ObjectMapper().writeValueAsString(authentication);
                        response.getWriter().write(s);
                    })
                    .permitAll()
                    .and()
                    .csrf().disable()
                    .userDetailsService(manager);
        }
    }
}

需要注意的是,如果配置了多个过滤器链,需要使用@Order注解来标记不同配置的优先级(即不同过滤器链的优先级),数字越大优先级越低。当请求到来时,会按照过滤器链的优先级从高往低,依次进行匹配。

4.5静态资源过滤

WebSecurity中维护了一个ignoredRequests变量,该变量记录的就是所有需要被忽略的请求,这些被忽略的请求将不再经过spring security过滤器。

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    /**
     * 忽略的地址,最终都会被添加到ignoredRequests集合中,并最终以过滤器链的形式呈现出来(详见WebSecurity#performBuild
     * 方法)。换句话说,重写的方法中一共包含了五个过滤器链:
     * configure(WebSecurity web)中配置的四个以及configure(HttpSecurity http)中配置的一个(即/**,所有请求都需要进行
     * 身份验证过滤)。
     */
    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/login.html", "/css/**", "/js/**", "/images/**");
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .anyRequest().authenticated()
                .and()
                .formLogin()
                .and()
                .csrf().disable();
    }
}
4.6使用JSON格式登录

登录参数的提取是在UsernamePasswordAuthenticationFilter过滤器中完成的。如果要使用JSON格式登录,只需要模仿它定义一个自己的过滤器,再将自定义的过滤器放到UsernamePasswordAuthenticationFilter过滤器所在的位置(需要在之前进行验证)即可。
首先自定义一个LoginFilter继承自UsernamePasswordAuthenticationFilter

public class LoginFilter extends UsernamePasswordAuthenticationFilter {
	// 父类通过调用子类的attemptAuthentication方法进行身份验证,因此需要重写该方法
    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
    	// 首先确保进入该过滤器中的请求是POST请求
        if (!"POST".equals(request.getMethod())) {
            throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
        }

        // 如果是JSON格式登录
        if (MediaType.APPLICATION_JSON_VALUE.equalsIgnoreCase(request.getContentType()) || MediaType.APPLICATION_JSON_UTF8_VALUE.equalsIgnoreCase(request.getContentType())) {
            Map userInfo = new HashMap<>();
            try {
                userInfo = new ObjectMapper().readValue(request.getInputStream(), Map.class);
                String username = userInfo.get(getUsernameParameter()).toString();
                String password = userInfo.get(getPasswordParameter()).toString();
                // 使用提取出来的用户名/密码信息构造出UsernamePasswordAuthenticationToken对象
                UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
                setDetails(request, authRequest);
                // 执行认证
                return this.getAuthenticationManager().authenticate(authRequest);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        // 如果不是JSON格式登录,则调用父类方法继续进行身份验证
        return super.attemptAuthentication(request, response);
    }
}

定义完成之后,将其添加到spring security过滤器链中:

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
	@Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication().withUser("javaboy").password("{noop}123").roles("admin");
    }

	// 重写父类的authenticationManagerBean方法来提供一个AuthenticationManager实例,并配置给LoginFilter
	@Bean
	@Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

	// 配置loginFilter实例,同时将AuthenticationManager实例设置给loginFilter,然后再设置登录成功回调
	@Bean
    LoginFilter loginFilter() throws Exception {
        LoginFilter loginFilter = new LoginFilter();
        loginFilter.setAuthenticationManager(authenticationManagerBean());
        loginFilter.setAuthenticationSuccessHandler((request, response, authentication) -> {
            response.setContentType("application/json;charset=utf-8");
            response.getWriter().write(new ObjectMapper().writeValueAsString(authentication));
        });

        return loginFilter;
    }

	@Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .anyRequest().authenticated()
                .and()
                .formLogin()
                .and()
                .csrf().disable();

        // 在UsernamePasswordAuthenticationFilter所在的位置添加自定义的过滤器进行处理,如果不能处理,
        // 则通过super调用UsernamePasswordAuthenticationFilter进行处理
        http.addFilterAt(loginFilter(), UsernamePasswordAuthenticationFilter.class);
    }
}

当想要获取一个AuthenticationManager实例时,有两种方式:

  • 第一种是通过重写父类的authenticationManager方法获取。
  • 第二种则是通过重写父类的authenticationManagerBean方法获取。

两者的区别在于第一种获取到的是全局的AuthenticationManager实例,而第二种获取到的则是局部的实例。而LoginFilter作为过滤器链中的一环,显然应该配置局部的实例,因为如果将全局的实例配置给LoginFilter,则局部AuthenticationManager实例所对应的用户就会失效。

/**
 * 无法使用javagirl/123进行登录,因为LoginFilter中指定了全局的AuthenticationManager来做验证,
 * 所以局部的AuthenticationManager失效了
 */
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
	@Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication().withUser("javaboy").password("{noop}123").roles("admin");
    }

	@Override
	@Bean
    protected AuthenticationManager authenticationManager() throws Exception {
        return super.authenticationManager();
    }

	@Bean
    LoginFilter loginFilter() throws Exception {
        LoginFilter loginFilter = new LoginFilter();
        loginFilter.setAuthenticationManager(authenticationManager());
        loginFilter.setAuthenticationSuccessHandler((request, response, authentication) -> {
            response.setContentType("application/json;charset=utf-8");
            response.getWriter().write(new ObjectMapper().writeValueAsString(authentication));
        });

        return loginFilter;
    }

	@Override
    protected void configure(HttpSecurity http) throws Exception {
        InMemoryUserDetailsManager users = new InMemoryUserDetailsManager();
        users.createUser(User.withUsername("javagirl").password("{noop}123").roles("admin").build());

        http.authorizeRequests()
                .anyRequest().authenticated()
                .and()
                .formLogin()
                .and()
                .csrf().disable()
                .userDetailsService(users);

        http.addFilterAt(loginFilter(), UsernamePasswordAuthenticationFilter.class);
    }
}

在实际应用中,如果需要自己配置一个AuthenticationManager实例,大部分情况下,都是通过重写authenticationManagerBean方法来获取。

4.7添加登录验证码

前面介绍了一种登录验证码的实现方案,其实通过过滤器链来实现会更加容易。验证码的生成方案和之前的一致,主要介绍一下LoginFilter的定义以及配置:

public class LoginFilter extends UsernamePasswordAuthenticationFilter {
    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        if (!"POST".equalsIgnoreCase(request.getMethod())) {
            throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
        }

        String kaptcha = request.getParameter("kaptcha");
        String sessionKaptcha = (String) request.getSession().getAttribute("kaptcha");

        if (StringUtils.hasLength(kaptcha) && StringUtils.hasLength(sessionKaptcha) && kaptcha.equalsIgnoreCase(sessionKaptcha)) {
            return super.attemptAuthentication(request, response);
        }

        throw new AuthenticationServiceException("验证码输入错误");
    }
}

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication().withUser("javaboy").password("{noop}123").roles("admin");
    }

    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Bean
    LoginFilter loginFilter() throws Exception {
        LoginFilter loginFilter = new LoginFilter();
        // 等价于loginProcessingUrl
        loginFilter.setFilterProcessesUrl("/doLogin");
        loginFilter.setAuthenticationManager(authenticationManagerBean());
        loginFilter.setAuthenticationSuccessHandler(new SimpleUrlAuthenticationSuccessHandler("/hello"));
        loginFilter.setAuthenticationFailureHandler(new SimpleUrlAuthenticationFailureHandler("/login.html"));
        // 如果继承了UsernamePasswordAuthenticationFilter,则相关的配置项需要LoginFilter实例上配置
        // loginFilter.setUsernameParameter("uname");
        // loginFilter.setPasswordParameter("passwd");
        return loginFilter;
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/vc.jpg").permitAll()
                .anyRequest().authenticated()
                .and()
                .formLogin()
                .loginPage("/login.html")
                // 继承UsernamePasswordAuthenticationFilter之后,这个地方的配置无效
                // .usernameParameter("uname")
                // .passwordParameter("passwd")
                .permitAll()
                .and()
                .csrf().disable();

        http.addFilterAt(loginFilter(), UsernamePasswordAuthenticationFilter.class);
    }
}

可以看到,相比于第一种方式,这种更加简单也更易于理解。