3.8 认证(Authentication)与源码解读

2016-06-11 17:52:57 26,974 2

在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的子类。

QQ截图20160611174026.png

4、UserDetailsService创建流程

前面提到InMemoryUserDetailsManagerConfigurer、JdbcUserDetailsManagerConfigurer、LdapAuthenticationProviderConfigurer分别对应一个AuthenticationProvider。

特别的,他们各自还对应一个UserDetailsService的三个子类实例:InMemoryUserDetailsManager、JdbcUserDetailsManager、LdapUserDetailManager。

类图如下

QQ截图20160611150818.png

上一篇:3.7 注销处理 下一篇:3.9 多个HttpSecurity