3.8 认证(Authentication)与源码解读
在Spring Security中,认证过程称之为Authentication
(验证),指的是建立系统使用者信息( principal
)的过程。使用者可以是一个用户、设备、或者其他可以在我们的应用中执行某种操作的其他系统。
到目前为止,我们只是在SecurityConfig类中进行了最基础的认证配置。现在我们来看一些更加高级的认证配置。
基于内存的验证
我们已经看过一个在内存中配置单个用户验证的配置,以下的代码可以配置多个用户:
@Autowired public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { auth .inMemoryAuthentication() .withUser("user").password("password").roles("USER").and() .withUser("admin").password("password").roles("USER", "ADMIN"); }
JDBC验证
接下来你将看到新版对于基于JDBC验证的支持。以下案例假设你已经在你的应用中定义一个DataSource。 jdbc-jc sample 案例提供了基于JDBC验证的完整案例。
@Autowiredprivate DataSource dataSource; @Autowiredpublic void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { auth .jdbcAuthentication() .dataSource(dataSource) .withDefaultSchema() .withUser("user").password("password").roles("USER").and() .withUser("admin").password("password").roles("USER", "ADMIN"); }
LDAP认证
以下的配置可以支持基于LDAP的认证。在ldap-javaconfig案例中提供了一个完整的基于LDAP认证代码。
@Autowired private DataSource dataSource; @Autowired public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { auth .ldapAuthentication() .userDnPatterns("uid={0},ou=people") .groupSearchBase("ou=groups"); }
上述代码使用了下面的LDIF和一个内嵌的 Apache DS LDAP实例。
users.ldif.
dn: ou=groups,dc=springframework,dc=org objectclass: top objectclass: organizationalUnit ou: groups dn: ou=people,dc=springframework,dc=org objectclass: top objectclass: organizationalUnit ou: people dn: uid=admin,ou=people,dc=springframework,dc=org objectclass: top objectclass: person objectclass: organizationalPerson objectclass: inetOrgPerson cn: Rod Johnson sn: Johnson uid: admin userPassword: password dn: uid=user,ou=people,dc=springframework,dc=org objectclass: top objectclass: person objectclass: organizationalPerson objectclass: inetOrgPerson cn: Dianne Emu sn: Emu uid: user userPassword: password dn: cn=user,ou=groups,dc=springframework,dc=org objectclass: top objectclass: groupOfNames cn: user uniqueMember: uid=admin,ou=people,dc=springframework,dc=org uniqueMember: uid=user,ou=people,dc=springframework,dc=org dn: cn=admin,ou=groups,dc=springframework,dc=org objectclass: top objectclass: groupOfNames cn: admin uniqueMember: uid=admin,ou=people,dc=springframework,dc=org
AuthenticationProvider
我们可以自定义一个AuthenticationProvider,并使其作为Spring的Bean。例如以下代码是假设有一个自定义的类SpringAuthenticationProvider
,其实现了AuthenticationProvider的配置。
@Bean public SpringAuthenticationProvider springAuthenticationProvider() { return new SpringAuthenticationProvider(); }
UserDetailsService
我们还可以自定义一个UserDetailsService
,来实现验证。以下代码假设SpringDataUserDetailsService
实现了UserDetailsService
。
@Bean public SpringDataUserDetailsService springDataUserDetailsService() { return new SpringDataUserDetailsService(); }
我们同样还可以配置PasswordEncoder
,来指定密码的编码方式
@Beanpublic BCryptPasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); }
源码解读:
首先不管是哪一种验证方式(内存、JDBC还是LDAP),我们都是通过一个自动注入的AuthenticationManagerBuilder
对象来完成的。
从名字中就可以看出,这个类是用于构建AuthenticationManager
。那么AuthenticationManager是什么呢?其作用是对用户提交的用户名和密码进行验证(在后面会详细的讲解)。
1、AuthenticationManagerBuilder的创建流程源码分析
再次回顾我们的SecurityConfig类,我们在这个类上添加了@EnableWebSecurity
注解,其可以帮助我们创建Spring Security工作过程中要使用到的Filter。而在这个注解的源码中(读者自行查看),我们可以看到,其还引入了一个@EnableGlobalAuthentication
注解,其源码如下:
@Retention(value = java.lang.annotation.RetentionPolicy.RUNTIME) @Target(value = { java.lang.annotation.ElementType.TYPE }) @Documented @Import(AuthenticationConfiguration.class)//导入配置类AuthenticationConfiguration @Configuration public @interface EnableGlobalAuthentication { }
可以看到在这个注解中,又我们导入了另外一个配置类AuthenticationConfiguration
。而AuthenticationManagerBuilder对象就是由这个类提供的,核心源码如下:
@Configuration public class AuthenticationConfiguration { ... @Bean public AuthenticationManagerBuilder authenticationManagerBuilder( ObjectPostProcessor<Object> objectPostProcessor) { return new AuthenticationManagerBuilder(objectPostProcessor); } ... }
可以看到这个配置类中,配置了一个类型为AuthenticationManagerBuilder的Bean。
2、AuthenticationManager的创建流程源码分析
与HttpSecurity类似,AuthenticationManagerBuilder也是SecurityBuilder的一个子类。不同的是,HttpSecurity使用到的SecurityConfiguer基本上最终产生的都是一个过滤器,而AuthenticationManagerBuilder使用到SecurityConfiguer最终产生的都是AuthenticationManager的一个子类实例ProviderManager。ProviderManager类的创建是通过performBuild方法创建的。
protected ProviderManager performBuild() throws Exception { if (!isConfigured()) { logger.debug("No authenticationProviders and no parentAuthenticationManager defined. Returning null."); return null; } //创建ProviderManager实例 ProviderManager providerManager = new ProviderManager(authenticationProviders, parentAuthenticationManager); if (eraseCredentials != null) { providerManager.setEraseCredentialsAfterAuthentication(eraseCredentials); } if (eventPublisher != null) { providerManager.setAuthenticationEventPublisher(eventPublisher); } providerManager = postProcess(providerManager); return providerManager; }
代码可能有点复杂,不过读者只需要关心加注释的部分就可以了。
那么AuthenticationManager或者说ProviderManager到底是干什么的呢?其是用于管理验证提供者。前面我们已经看到了SpringSecurity支持基于JDBC、LADP等多种验证方式,实际上每一种方式都对应一个provider。如果我们需要联合使用多种验证方式,ProviderManager就可以帮助我们来管理这些provider,例如先用谁验证,后用谁验证,以及是否只要有一个provider验证成功就算用户已经成功验证等。
3、AuthenticationProvider创建流程
我们已经知道AuthenticationManagerBuilder会帮助我们创建一个AuthenticationManager的子类实例ProviderManager,其是用于管理AuthenticationProvider的。
AuthenticationManagerBuilder的inMemoryAuthentication()、jdbcAuthentication()、ldapAuthentication()这三个方法分别返回的
InMemoryUserDetailsManagerConfigurer、JdbcUserDetailsManagerConfigurer、LdapAuthenticationProviderConfigurer三个SecurityConfiguer子类实例,最终会帮助我创建各自对应的provider。
我们列出inMemoryAuthentication()、jdbcAuthentication()、ldapAuthentication()的源码:
public InMemoryUserDetailsManagerConfigurer<AuthenticationManagerBuilder> inMemoryAuthentication() throws Exception { return apply(new InMemoryUserDetailsManagerConfigurer<AuthenticationManagerBuilder>());//调用apply方法 } ------------------------------- public JdbcUserDetailsManagerConfigurer<AuthenticationManagerBuilder> jdbcAuthentication() throws Exception { return apply(new JdbcUserDetailsManagerConfigurer<AuthenticationManagerBuilder>());//调用apply方法 } ------------------------------- public LdapAuthenticationProviderConfigurer<AuthenticationManagerBuilder> ldapAuthentication() throws Exception { return apply(new LdapAuthenticationProviderConfigurer<AuthenticationManagerBuilder>());//调用apply方法 }
其中InMemoryUserDetailsManagerConfigurer和JdbcUserDetailsManagerConfigurer对应的provider都是DaoAuthenticationProvider。
LdapAuthenticationProviderConfigurer对应的是LdapAuthenticationProvider。
很明显的,这两个provider都是AuthenticationProvider的子类。
4、UserDetailsService创建流程
前面提到InMemoryUserDetailsManagerConfigurer、JdbcUserDetailsManagerConfigurer、LdapAuthenticationProviderConfigurer分别对应一个AuthenticationProvider。
特别的,他们各自还对应一个UserDetailsService的三个子类实例:InMemoryUserDetailsManager、JdbcUserDetailsManager、LdapUserDetailManager。
类图如下