我们先来看一个简单的配置:

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.authorizeRequests()
            .anyRequest().authenticated()
            .and()
            .formLogin()
            .loginProcessingUrl("/doLogin")
            .permitAll()
            .and()
            .logout()
            .logoutUrl("/logout")
            .permitAll()
            .and()
            .csrf().disable();
}

这样的配置在 Spring Security 中很常见,通过 and 方法,可以将所有的配置连接在一起,一条线下来,所有的东西都配置好了。

但是有小伙伴对这里的 and 表示很迷,不知道什么时候 and 方法该出场,什么时候 and 不该出场!

所以今天松哥就花点时间来和大家聊一下这里的 and 方法,希望大家看完完整后,能够明白 and 到底怎么玩!

本文是 Spring Security 系列第 33 篇,阅读前面文章有助于更好的理解本文:

  1. 挖一个大坑,Spring Security 开搞!
  2. 松哥手把手带你入门 Spring Security,别再问密码怎么解密了
  3. 手把手教你定制 Spring Security 中的表单登录
  4. Spring Security 做前后端分离,咱就别做页面跳转了!统统 JSON 交互
  5. Spring Security 中的授权操作原来这么简单
  6. Spring Security 如何将用户数据存入数据库?
  7. Spring Security+Spring Data Jpa 强强联手,安全管理只有更简单!
  8. Spring Boot + Spring Security 实现自动登录功能
  9. Spring Boot 自动登录,安全风险要怎么控制?
  10. 在微服务项目中,Spring Security 比 Shiro 强在哪?
  11. SpringSecurity 自定义认证逻辑的两种方式(高级玩法)
  12. Spring Security 中如何快速查看登录用户 IP 地址等信息?
  13. Spring Security 自动踢掉前一个登录用户,一个配置搞定!
  14. Spring Boot + Vue 前后端分离项目,如何踢掉已登录用户?
  15. Spring Security 自带防火墙!你都不知道自己的系统有多安全!
  16. 什么是会话固定攻击?Spring Boot 中要如何防御会话固定攻击?
  17. 集群化部署,Spring Security 要如何处理 session 共享?
  18. 松哥手把手教你在 SpringBoot 中防御 CSRF 攻击!so easy!
  19. 要学就学透彻!Spring Security 中 CSRF 防御源码解析
  20. Spring Boot 中密码加密的两种姿势!
  21. Spring Security 要怎么学?为什么一定要成体系的学习?
  22. Spring Security 两种资源放行策略,千万别用错了!
  23. 松哥手把手教你入门 Spring Boot + CAS 单点登录
  24. Spring Boot 实现单点登录的第三种方案!
  25. Spring Boot+CAS 单点登录,如何对接数据库?
  26. Spring Boot+CAS 默认登录页面太丑了,怎么办?
  27. 用 Swagger 测试接口,怎么在请求头中携带 Token?
  28. Spring Boot 中三种跨域场景总结
  29. Spring Boot 中如何实现 HTTP 认证?
  30. Spring Security 中的四种权限控制方式
  31. Spring Security 多种加密方案共存,老破旧系统整合利器!
  32. 神奇!自己 new 出来的对象一样也可以被 Spring 容器管理!

# 1.原始配置

在 Spring Boot 出现之前,我们使用 Spring Security ,都是通过 XML 文件来配置 Spring Security 的,即使现在大家在网上搜索 Spring Security 的文章,还是能够找到很多 XML 配置的。

但是小伙伴们明白,无论是 XML 配置还是 Java 配置,只是在用不同的方式描述同一件事情,从这里角度来看,我们现在所使用的 Java 配置,和以前使用的 XML 配置,应该有某种异曲同工之妙。

可能有小伙伴没见过 XML 配置的 Spring Security,我在这里给大家简单举几个例子:

<http>
    <intercept-url pattern="/login" access="permitAll" />
    <form-login login-page="/login" />
    <http-basic />
</http>

这段 XML 大家稍微瞅一眼大概就能明白其中的含义:

  1. intercept-url 相当于配置拦截规则
  2. form-login 是配置表单登录
  3. http-basic 是配置 HttpBasic 认证

如果我们使用了 Java 配置,这些 XML 配置都有对应的写法,例如 .anyRequest().authenticated() 就是配置拦截规则的,.formLogin() 是配置表单登录细节的。

仅仅从语义层面来理解,and 有点类似于 XML 中的结束标签,每当 and 出现,当前的配置项就结束了,可以开启下一个配置了。

那么从代码层面上,这个要如何理解呢?

# 2.代码层面的理解

小伙伴们知道,Spring Security 中的功能是由一系列的过滤器来实现的,默认的过滤器一共有 15 个,这 15 个过滤器松哥以后会和大家挨个介绍。

每一个过滤器都有一个对应的 configurer 来对其进行配置,例如我们常见的 UsernamePasswordAuthenticationFilter 过滤器就是通过 AbstractAuthenticationFilterConfigurer 来进行配置的。

这些 configure 都有一个共同的父类,那就是 SecurityConfigurer,给大家大致看一下 SecurityConfigurer 的继承关系图:

可以看到,它的实现类还是蛮多的。

SecurityConfigurer 的源码很简单:

public interface SecurityConfigurer<O, B extends SecurityBuilder<O>> {
	void init(B builder) throws Exception;
	void configure(B builder) throws Exception;
}

就两个方法,第一个 init 用来做初始化操作,第二个 configure 用来做具体的配置。

在 Spring Security 框架初始化的时候,会把所有的这些 xxxConfigurer 收集起来,然后再统一调用每一个 xxxConfigurer 里边的 init 和 configure 方法(松哥在以后的文章中会和大家详细讨论这个过程),调用完成后,Spring Security 默认的过滤器链就形成了。

这就是我们所说的 xxxConfigurer 的作用!

在文章一开始,松哥列出来的示例代码中,HttpSecurity 中其实就是在配置各种各样的 xxxConfigurer。

SecurityConfigurer 有一个重要的实现类就是 SecurityConfigurerAdapter,默认的 15 个过滤器的 Configurer 类都是继承自它!而在 SecurityConfigurerAdapter 中就多出来一个方法:

public abstract class SecurityConfigurerAdapter<O, B extends SecurityBuilder<O>>
		implements SecurityConfigurer<O, B> {

	public void init(B builder) throws Exception {
	}

	public void configure(B builder) throws Exception {
	}
	public B and() {
		return getBuilder();
	}

}

没错,就是大家所熟知的 and 方法。and 方法的返回值是一个 SecurityBuilder 的子类,其实就是 HttpSecurity,也就是 and 方法总是让我们回到 HttpSecurity,从而开启新一轮的 xxxConfigurer 配置。

我们再来瞅一眼 HttpSecurity 中到底都有啥方法(方法比较多,我这里仅列举一部分):

可以看到,每一个类型的配置,都有一个对应的返回 Configure 的方法,例如 OpenIDLoginConfigurer、HeadersConfigurer、CorsConfigurer 等等,大家注意,每一个 configure 方法都有一个 HttpSecurity 作为泛型,这实际上就指定了 and 方法的返回类型。

我再举个例子,大家可能更清楚一些,以 HttpSecurity 中 RememberME 的配置为例,有两个方法:

  • RememberMeConfigurer rememberMe()
  • HttpSecurity rememberMe(Customizer<RememberMeConfigurer> rememberMeCustomizer)
  1. 第一个 rememberMe 方法没有参数,但是返回值是一个 RememberMeConfigurer,我们可以在这个 RememberMeConfigurer 上继续配置 RememberME 相关的其他属性,配置完成后,通过 and 方法重新回到 HttpSecurity 对象,松哥前面文章基本上都是采用这种方式配置的,这里我就不重复举例子了。
  2. 第二个 rememberMe 方法有参数,参数是一个 Customizer ,但是带着一个 RememberMeConfigurer 泛型。其实 Customizer 就是一个接口,我们可以通过匿名内部类的方式来实现该接口,这个接口中就一个实例方法,而且该方法的参数还是你传入的泛型,即 RememberMeConfigurer,其实也就是我们换了个地方去配置 RememberMeConfigurer 了,配置完成后,这个方法会直接返回 HttpSecurity,此时就不再需要 and 方法了。配置示例如下(注意配置完成后不需要 and 方法就能继续后面的配置了):
@Override
protected void configure(HttpSecurity http) throws Exception {
    http.authorizeRequests()
            .antMatchers("/admin/**").hasRole("admin")
            .antMatchers("/user/**").hasRole("user")
            .anyRequest().authenticated()
            .and()
            .formLogin()
            .permitAll()
            .and()
            .rememberMe(new Customizer<RememberMeConfigurer<HttpSecurity>>() {
                @Override
                public void customize(RememberMeConfigurer<HttpSecurity> httpSecurityRememberMeConfigurer) {
                    httpSecurityRememberMeConfigurer.key("123");
                }
            })
            .csrf().disable();
}

这就是我们在 configure(HttpSecurity http) 方法中的配置过程。

# 3.小结

通过前面的讲解,不知道小伙伴们有没有看懂呢?我再给大家总结下。

Spring Security 的功能主要是通过各种各样的过滤器来实现的,各种各样的过滤器都由对应的 xxxConfigurer 来进行配置,我们在 configure(HttpSecurity http) 中所做的配置其实就是在配置 xxxConfigurer,也是在间接的配置过滤器,每一个 and 方法会将我们带回到 HttpSecurity 实例中,从而开启新一轮的配置。

大致就是这样!

小伙伴们如果觉得有收获,记得点个在看鼓励下松哥哦~