1、SpringSecurity 在spring boot中与SSM项目中基于配置文件的区别
通过前边的笔记我们可以知道,在传统的SSM项目中 SpringSecurity的使用是基于配置文件
的,然后spring 容器初始化的时候将 SpringSecurity 中的各种标签解析成对应的Bean对象,
SpringSecurity 配置文件如下所示:
-
- <security:http auto-config="true" use-expressions="true">
-
- <security:intercept-url pattern="/login.jsp" access="permitAll()"/>
-
- <security:intercept-url pattern="/**" access="hasAnyRole('ROLE_USER')" >security:intercept-url>
-
-
- <security:form-login login-page="/login.jsp" login-processing-url="/login" default-target-url="/home.jsp" authentication-failure-url="/error.jsp"/>
-
-
- <security:csrf disabled="true"/>
-
-
- <security:remember-me
- token-validity-seconds="1200"
- data-source-ref="dataSource"
- remember-me-parameter="remember-me"/>
-
-
- <security:access-denied-handler error-page="error.jsp"/>
-
- security:http>
-
-
- <bean class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder" id="bCryptPasswordEncoder"/>
-
-
- <security:authentication-manager>
- <security:authentication-provider user-service-ref="userServiceImpl">
-
-
- <security:password-encoder ref="bCryptPasswordEncoder">security:password-encoder>
- security:authentication-provider>
- security:authentication-manager>
也就是在配置文件中通过 security:http 等标签来定义了认证需要的相关信息,但是在
SpringBoot项目中,我们慢慢脱离了xml配置文件的方式,通过配置类的方式来配置
SpringSecurity。SpringSecurity配置类需要继承类 WebSecurityConfigurerAdapter,并重写
configure(AuthenticationManagerBuilder auth) 和 configure(HttpSecurity http) 方法;
SpringSecurity 配置类如下所示:
- /**
- * SpringSecurity的配置文件
- * WebSecurityCofniguration中 @Bean注解 把 FilterChainProxy 注入到了容器中 而且名称为springSecurityFilterChain
- * 而 FilterChainProxy 对象是通过 WebSecurity 构建的
- *
- * @EnableWebSecurity
- */
- @Configuration
- @EnableWebSecurity
- public class SpringSecurityConfiguration extends WebSecurityConfigurerAdapter {
-
- @Autowired
- private UserService userService;
-
- @Autowired
- private BCryptPasswordEncoder bCryptPasswordEncoder;
-
- @Autowired
- private PersistentTokenRepository persistentTokenRepository;
-
- @Override
- protected void configure(AuthenticationManagerBuilder auth) throws Exception {
- auth.userDetailsService(userService)// 数据库认证,绑定需要执行用户认证操作的service
- .passwordEncoder(bCryptPasswordEncoder); // 设置加密处理的方式
- //auth.inMemoryAuthentication().withUser("root").password("123")
- }
-
- /**
- * 容器中注入 BCryptPasswordEncoder
- * @return
- */
- @Bean
- public BCryptPasswordEncoder bCryptPasswordEncoder(){
- return new BCryptPasswordEncoder();
- }
-
- /**
- * HttpSecurity 相当于 SpringSecurity配置文件中 http 标签
- *
- * @param http
- * @throws Exception
- */
- @Override
- protected void configure(HttpSecurity http) throws Exception {
- http.authorizeRequests()
-
- //匿名访问资源
- .antMatchers("/login.html","/css/**","/img/**")
- // 配置需要放过的资源,表示前边 antMatchers 配置的资源都需要放过
- .permitAll()
- .antMatchers("/**")
- //认证的资源及所具备的权限
- .hasAnyRole("USER")
- .anyRequest()//表示所有请求都需要认证
- .authenticated()//需要认证
- //and() 返回一个HttpSecurity
- .and()
- // 配置登录表单相关的信息
- .formLogin()
- // 指定自定义的登录页面
- //todo 注意:
- // 对于前后端分离的项目,不需要指定跳转的页面,只需要 loginProcessingUrl
- // 设置请求资源url就行了
- .loginPage("/login.html") //认证表单相关信息
- .loginProcessingUrl("/login") // 表单提交的登录地址
- .defaultSuccessUrl("/home.html")
- //表示上边与form表单提交得资源都要放过
- .permitAll()
- .and()
- .rememberMe() // 放开 记住我 的功能
- .tokenRepository(persistentTokenRepository) // 持久化
- .and()
- //csrf设置
- .csrf()
- .disable();
-
- HttpSecurity http1 = http.authorizeRequests()
- // 配置需要放过的资源
- .antMatchers("/login.html", "/css/**", "/img/**")
- .permitAll()//表示放过前边 antMatchers 配置得资源
- .antMatchers("/**")
- .hasAnyRole("USER")
- .anyRequest()
- .authenticated()
- .and();
- }
-
- /**
- * 向Spring容器中注入 PersistentTokenRepository 对象
- * @param dataSource
- * @return
- */
- @Bean
- public PersistentTokenRepository persistentTokenRepository(DataSource dataSource){
- JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl();
- //绑定数据源
- tokenRepository.setDataSource(dataSource);
- return tokenRepository;
- }
-
- public static void main(String[] args) {
- BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
- String password = "admin";
- // 每次都会生成一个随机的salt,同一个明文加密多次编码得到的密文其实都不一样
- System.out.println(encoder.encode(password));
- System.out.println(encoder.encode(password));
- System.out.println(encoder.encode(password));
- }
- }
在SpringSecurity中提供了HttpSecurity等工具类,这里HttpSecurity就等同于我们在配置
文件中定义的
通过代码结果来看和配置文件的效果是一样的。基于配置文件的方式我们之前分析过,是通过
标签对应的handler来解析处理的,那么HttpSecurity这块是如何处理的呢?
2、HttpSecurity 类的处理过程
2.1、HttpSecurity 类图:

由该类图可以清晰得发现,HttpSecurity 继承了父类 AbstractConfiguredSecurityBuilder
并实现了接口 SecurityBuilder 和 HttpSecurityBuilder
HttpSecurity 类的定义如下:
2.2、SecurityBuilder 接口
SecurityBuilder 定义如下:
- public interface SecurityBuilder
{ - //构建 SecurityBuilder 指定泛型类型的对象
- O build() throws Exception;
- }
由接口 SecurityBuilder 的定义可以发现,SecurityBuilder 接口只提供了一个 build() 方法,用
来构建 SecurityBuilder 泛型指定类型的bean对象。
结合 HttpSecurity 类中实现 SecurityBuilder 接口时的泛型是什么,就知道在 HttpSecurity 类
中 SecurityBuilder 是用来创建什么对象,HttpSecurity 定义如下:

由 HttpSecurity 的定义可以发现,在 HttpSecurity 类中 SecurityBuilder 指定的泛型是
DefaultSecurityFilterChain,DefaultSecurityFilterChain 是拦截器链SecurityFilterChain
一个默认实现,所以 DefaultSecurityFilterChain 是一个拦截器链,所以在 HttpSecurity
中,SecurityBuilder 是用来创建拦截器链的。
2.2.1、SecurityBuilder.build() 方法的实现
下面看下 SecurityBuilder.build() 的实现过程,及连接器链的创建过程
SecurityBuilder 的默认实现是类 AbstractSecurityBuilder
SecurityBuilder.build() 方法的实现如下:
- public abstract class AbstractSecurityBuilder
implements SecurityBuilder { - //CAS类型
- private AtomicBoolean building = new AtomicBoolean();
- //返回创建的O的对象
- private O object;
-
- public AbstractSecurityBuilder() {
- }
-
- //创建泛型O的对象
- public final O build() throws Exception {
- //基于CAS,保证在整个环境中O只被创建一次
- if (this.building.compareAndSet(false, true)) {
- //真正创建泛型O的对象
- this.object = this.doBuild();
- return this.object;
- } else {
- throw new AlreadyBuiltException("This object has already been built");
- }
- }
-
- //返回O的对象
- public final O getObject() {
- if (!this.building.get()) {
- throw new IllegalStateException("This object has not been built");
- } else {
- return this.object;
- }
- }
-
- //抽象方法,由子类实现
- protected abstract O doBuild() throws Exception;
- }
由 AbstractSecurityBuilder 的定义可以发现,真正创建泛型O(在 HttpSecurity 中O是
拦截器链 DefaultSecurityFilterChain )的对象是在doBuild 发给发中完成,而doBuild是
一个抽象方法,由子类 AbstractConfiguredSecurityBuilder 实现,
doBuild 方法实现如下:
- protected final O doBuild() throws Exception {
- synchronized(this.configurers) {
- this.buildState = AbstractConfiguredSecurityBuilder.BuildState.INITIALIZING;
- this.beforeInit();
- //执行当前类的init方法进行初始化操作
- this.init();
- this.buildState = AbstractConfiguredSecurityBuilder.BuildState.CONFIGURING;
- this.beforeConfigure();
- this.configure();
- this.buildState = AbstractConfiguredSecurityBuilder.BuildState.BUILDING;
- //获取构建的对象,上面的方法可以先忽略
- O result = this.performBuild();
- this.buildState = AbstractConfiguredSecurityBuilder.BuildState.BUILT;
- return result;
- }
- }
performBuild() 是一个抽象方法,由AuthenticationManagerBuilder、HttpSecurity、
WebSecurity 三个子类实现,在这里我们应该看 HttpSecurity 中的实现
HttpSecurity.performBuild() 方法的实现如下:
- @Override
- protected DefaultSecurityFilterChain performBuild() {
- //filters:保存所有的过滤器
- // 对所有的过滤器做排序
- this.filters.sort(OrderComparator.INSTANCE);
- List
sortedFilters = new ArrayList<>(this.filters.size()); - for (Filter filter : this.filters) {
- sortedFilters.add(((OrderedFilter) filter).filter);
- }
- // 然后生成 DefaultSecurityFilterChain
- return new DefaultSecurityFilterChain(this.requestMatcher, sortedFilters);
- }
DefaultSecurityFilterChain 构造方法如下:
- public DefaultSecurityFilterChain(RequestMatcher requestMatcher, List
filters) { - if (!filters.isEmpty()) {
- logger.info(LogMessage.format("Will not secure %s", requestMatcher));
- } else {
- logger.info(LogMessage.format("Will secure %s with %s", requestMatcher, filters));
- }
-
- //绑定请求匹配规则
- this.requestMatcher = requestMatcher;
- //绑定过滤器集合
- this.filters = new ArrayList(filters);
- }
在HttpSecurity构造方法中绑定了对应的请求匹配器和过滤器集合。

对应的请求匹配器则是 AnyRequestMatcher 匹配所有的请求。当然我们会比较关心
默认的过滤器链中的过滤器是哪来的,这块儿我们继续来分析
2.3、AbstractConfiguredSecurityBuilder
AbstractConfiguredSecurityBuilder 是一个抽象类,也可以看成是 SecurityBuilder 接口的
实现,AbstractConfiguredSecurityBuilder 类图如下所示:

| 类型 | 功能 |
| SecurityBuilder | 声明了build方法 |
| AbstractSecurityBuilder | 提供了获取对象的方法以及控制一个对象只能build一次 |
| AbstractConfiguredSecurityBuilder | 除了提供对对象细粒度的控制外还扩展了对configurer的操作 |
AbstractConfiguredSecurityBuilder 对应的三个实现类,如下所示:

2.3.1、BuildState
AbstractConfiguredSecurityBuilder 中定义了一个枚举类BuildState,将整个构建过程
分为 5 种状态,也可 以理解为构建过程生命周期的五个阶段,通过这些阶段来管理需
要构建的对象的不同阶段 如下:
- 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;
- }
-
- /**
- * Determines if the state is CONFIGURING or later
- * @return
- */
- public boolean isConfigured() {
- return this.order >= CONFIGURING.order;
- }
-
- }
2.3.2、AbstractConfiguredSecurityBuilder 常见方法
2.3.2.1、add() 方法
add 方法,这相当于是在收集所有的配置类。将所有的 xxxConfigure 收集起来存储到
configurers 中,将来再统一初始化并配置,configurers 本身是一个 LinkedHashMap ,
key 是配置类的 class, value 是一个集合,集合里边放着 xxxConfigure 配置类。当
需要对这些配置类进行集中配置的时候, 会通过 getConfigurers 方法获取配置类,
这个获取过程就是把 LinkedHashMap 中的 value 拿出来, 放到一个集合中返回。
add 方法代码如下:
- private
extends SecurityConfigurer> void add(C configurer) { - Assert.notNull(configurer, "configurer cannot be null");
- //configurer必须是 SecurityConfigurer 的子类
- Class extends SecurityConfigurer
> clazz = configurer.getClass(); - synchronized(this.configurers) {
- if (this.buildState.isConfigured()) {
- throw new IllegalStateException("Cannot apply " + configurer + " to already built object");
- } else {
- List
> configs = null; - if (this.allowConfigurersOfSameType) {
- configs = (List)this.configurers.get(clazz);
- }
-
- List
> configs = configs != null ? configs : new ArrayList(1); - ((List)configs).add(configurer);
- this.configurers.put(clazz, configs);
- if (this.buildState.isInitializing()) {
- this.configurersAddedInInitializing.add(configurer);
- }
-
- }
- }
- }
-
- //获取指定的配置类
- @SuppressWarnings("unchecked")
- public
extends SecurityConfigurer> List getConfigurers(Class clazz) { - List
configs = (List) this.configurers.get(clazz); - if (configs == null) {
- return new ArrayList<>();
- }
- return new ArrayList<>(configs);
- }
2.3.2.2、doBuild()方法
- @Override
- protected final O doBuild() throws Exception {
- synchronized (this.configurers) {
- this.buildState = BuildState.INITIALIZING;
- beforeInit(); //是一个预留方法,没有任何实现
- init(); // 就是找到所有的 xxxConfigure,挨个调用其 init 方法进行初始化,完成默认过滤器的初始化
- this.buildState = BuildState.CONFIGURING;
- beforeConfigure(); // 是一个预留方法,没有任何实现
- configure(); // 就是找到所有的 xxxConfigure,挨个调用其 configure 方法进行配置。
- this.buildState = BuildState.BUILDING;
- O result = performBuild();
- // 是真正的过滤器链构建方法,但是在 AbstractConfiguredSecurityBuilder中 performBuild 方法只是一个抽象方法,具体的实现在 HttpSecurity 中
- this.buildState = BuildState.BUILT;
- return result;
- }
- }
init方法:完成所有相关过滤器的初始化
- private void init() throws Exception {
- Collection
> configurers = getConfigurers(); - for (SecurityConfigurer
configurer : configurers) { - configurer.init((B) this); // 初始化对应的过滤器
- }
- for (SecurityConfigurer
configurer : this.configurersAddedInInitializing) { - configurer.init((B) this);
- }
- }
configure方法:完成HttpSecurity和对应的过滤器的绑定。
- private void configure() throws Exception {
- Collection
> configurers = getConfigurers(); - for (SecurityConfigurer
configurer : configurers) { - configurer.configure((B) this);
- }
- }
2.4、HttpSecurity
HttpSecurity 做的事情,就是对各种各样的 xxxConfigurer 进行配置;
HttpSecurity 部分方法列表如下:

HttpSecurity 中有大量类似的方法,过滤器链中的过滤器就是这样一个一个配置的。我们
就不一一介绍 了。每个配置方法的结尾都会调用一次 getOrApply 方法,getOrApply
方法是做什么的?
getOrApply 方法如下:
- private
extends SecurityConfigurerAdapter> C getOrApply(C configurer) - throws Exception {
- C existingConfig = (C) getConfigurer(configurer.getClass());
- if (existingConfig != null) {
- return existingConfig;
- }
- return apply(configurer);
- }
getConfigurer 方法是在它的父类 AbstractConfiguredSecurityBuilder 中定义的,目的就
是去查看当前 这个 xxxConfigurer 是否已经配置过了。
如果当前 xxxConfigurer 已经配置过了,则直接返回,否则调用 apply 方法,这个 apply
方法最终会调 用到 AbstractConfiguredSecurityBuilder#add 方法,将当前配置
configurer 收集起来 HttpSecurity 中还有一个 addFilter 方法.
addFilter 方法如下所示:
- @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;
- }
这个 addFilter 方法的作用,主要是在各个 xxxConfigurer 进行配置的时候,会调用到这
个方法, (xxxConfigurer 就是用来配置过滤器的),把 Filter 都添加到 fitlers 变量中。
3、总结
这就是 HttpSecurity 的一个大致工作流程。把握住了这个工作流程,剩下的就只是一些简
单的重 复的 xxxConfigurer 配置了