Spring Security是Spring Framework的安全认证框架,网上有很多相关资料,原先个人研究的时候,网上很多资料只是笼统的介绍了一下整个流程,当自己去实现认证器的时候感觉懵了,本文参考http://www.spring4all.com/article/443 上面很多讲解的都很详细,这里只是补充一些简单的东西
1 Authentication 很多文章都说使用这个接口进行实现认证过程,而且是所有步骤中最为关键的一步,具体Authentication接口如何工作没有写,Authentication接口有一个实现类AuthenticationManagerDelegator,该类实现如下
static final class AuthenticationManagerDelegator implements AuthenticationManager {
private AuthenticationManagerBuilder delegateBuilder;
private AuthenticationManager delegate;
private final Object delegateMonitor = new Object();
AuthenticationManagerDelegator(AuthenticationManagerBuilder delegateBuilder) {
Assert.notNull(delegateBuilder, "delegateBuilder cannot be null");
this.delegateBuilder = delegateBuilder;
}
@Override
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
if (this.delegate != null) {
return this.delegate.authenticate(authentication);
}
synchronized (this.delegateMonitor) {
if (this.delegate == null) {
this.delegate = this.delegateBuilder.getObject();
this.delegateBuilder = null;
}
}
return this.delegate.authenticate(authentication);
}
@Override
public String toString() {
return "AuthenticationManagerDelegator [delegate=" + this.delegate + "]";
}
}
在这个实现类中实际则是AuthenticationManagerBuilder 类进行认证功能实现
2 AuthenticationManagerBuilder 实现认证功能,在简单配置Spring Security认证中可以传递的userDetailsService在本类中有对应的实现方法以及对应的在内存中存储用户名密码对应的方法,本类最重要的一点就是可以添加自定义的AuthenticationProvider,进行自定义验证。源码如下:
public class AuthenticationManagerBuilder
extends
AbstractConfiguredSecurityBuilder<AuthenticationManager, AuthenticationManagerBuilder>
implements ProviderManagerBuilder<AuthenticationManagerBuilder> {
private final Log logger = LogFactory.getLog(getClass());
private AuthenticationManager parentAuthenticationManager;
private List<AuthenticationProvider> authenticationProviders = new ArrayList<AuthenticationProvider>();
private UserDetailsService defaultUserDetailsService;
private Boolean eraseCredentials;
private AuthenticationEventPublisher eventPublisher;
/**
* Creates a new instance
* @param objectPostProcessor the {@link ObjectPostProcessor} instance to use.
*/
public AuthenticationManagerBuilder(ObjectPostProcessor<Object> objectPostProcessor) {
super(objectPostProcessor, true);
}
/**
* Allows providing a parent {@link AuthenticationManager} that will be tried if this
* {@link AuthenticationManager} was unable to attempt to authenticate the provided
* {@link Authentication}.
*
* @param authenticationManager the {@link AuthenticationManager} that should be used
* if the current {@link AuthenticationManager} was unable to attempt to authenticate
* the provided {@link Authentication}.
* @return the {@link AuthenticationManagerBuilder} for further adding types of
* authentication
*/
public AuthenticationManagerBuilder parentAuthenticationManager(
AuthenticationManager authenticationManager) {
if (authenticationManager instanceof ProviderManager) {
eraseCredentials(((ProviderManager) authenticationManager)
.isEraseCredentialsAfterAuthentication());
}
this.parentAuthenticationManager = authenticationManager;
return this;
}
/**
* Sets the {@link AuthenticationEventPublisher}
*
* @param eventPublisher the {@link AuthenticationEventPublisher} to use
* @return the {@link AuthenticationManagerBuilder} for further customizations
*/
public AuthenticationManagerBuilder authenticationEventPublisher(
AuthenticationEventPublisher eventPublisher) {
Assert.notNull(eventPublisher, "AuthenticationEventPublisher cannot be null");
this.eventPublisher = eventPublisher;
return this;
}
/**
*
*
* @param eraseCredentials true if {@link AuthenticationManager} should clear the
* credentials from the {@link Authentication} object after authenticating
* @return the {@link AuthenticationManagerBuilder} for further customizations
*/
public AuthenticationManagerBuilder eraseCredentials(boolean eraseCredentials) {
this.eraseCredentials = eraseCredentials;
return this;
}
/**
* Add in memory authentication to the {@link AuthenticationManagerBuilder} and return
* a {@link InMemoryUserDetailsManagerConfigurer} to allow customization of the in
* memory authentication.
*
* <p>
* This method also ensure that a {@link UserDetailsService} is available for the
* {@link #getDefaultUserDetailsService()} method. Note that additional
* {@link UserDetailsService}'s may override this {@link UserDetailsService} as the
* default.
* </p>
*
* @return a {@link InMemoryUserDetailsManagerConfigurer} to allow customization of
* the in memory authentication
* @throws Exception if an error occurs when adding the in memory authentication
*/
public InMemoryUserDetailsManagerConfigurer<AuthenticationManagerBuilder> inMemoryAuthentication()
throws Exception {
return apply(new InMemoryUserDetailsManagerConfigurer<AuthenticationManagerBuilder>());
}
/**
* Add JDBC authentication to the {@link AuthenticationManagerBuilder} and return a
* {@link JdbcUserDetailsManagerConfigurer} to allow customization of the JDBC
* authentication.
*
* <p>
* When using with a persistent data store, it is best to add users external of
* configuration using something like <a href="http://flywaydb.org/">Flyway</a> or <a
* href="http://www.liquibase.org/">Liquibase</a> to create the schema and adding
* users to ensure these steps are only done once and that the optimal SQL is used.
* </p>
*
* <p>
* This method also ensure that a {@link UserDetailsService} is available for the
* {@link #getDefaultUserDetailsService()} method. Note that additional
* {@link UserDetailsService}'s may override this {@link UserDetailsService} as the
* default. See the <a href=
* "http://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#user-schema"
* >User Schema</a> section of the reference for the default schema.
* </p>
*
* @return a {@link JdbcUserDetailsManagerConfigurer} to allow customization of the
* JDBC authentication
* @throws Exception if an error occurs when adding the JDBC authentication
*/
public JdbcUserDetailsManagerConfigurer<AuthenticationManagerBuilder> jdbcAuthentication()
throws Exception {
return apply(new JdbcUserDetailsManagerConfigurer<AuthenticationManagerBuilder>());
}
/**
* Add authentication based upon the custom {@link UserDetailsService} that is passed
* in. It then returns a {@link DaoAuthenticationConfigurer} to allow customization of
* the authentication.
*
* <p>
* This method also ensure that the {@link UserDetailsService} is available for the
* {@link #getDefaultUserDetailsService()} method. Note that additional
* {@link UserDetailsService}'s may override this {@link UserDetailsService} as the
* default.
* </p>
*
* @return a {@link DaoAuthenticationConfigurer} to allow customization of the DAO
* authentication
* @throws Exception if an error occurs when adding the {@link UserDetailsService}
* based authentication
*/
public <T extends UserDetailsService> DaoAuthenticationConfigurer<AuthenticationManagerBuilder, T> userDetailsService(
T userDetailsService) throws Exception {
this.defaultUserDetailsService = userDetailsService;
return apply(new DaoAuthenticationConfigurer<AuthenticationManagerBuilder, T>(
userDetailsService));
}
/**
* Add LDAP authentication to the {@link AuthenticationManagerBuilder} and return a
* {@link LdapAuthenticationProviderConfigurer} to allow customization of the LDAP
* authentication.
*
* <p>
* This method <b>does NOT</b> ensure that a {@link UserDetailsService} is available
* for the {@link #getDefaultUserDetailsService()} method.
*
* @return a {@link LdapAuthenticationProviderConfigurer} to allow customization of
* the LDAP authentication
* @throws Exception if an error occurs when adding the LDAP authentication
*/
public LdapAuthenticationProviderConfigurer<AuthenticationManagerBuilder> ldapAuthentication()
throws Exception {
return apply(new LdapAuthenticationProviderConfigurer<AuthenticationManagerBuilder>());
}
/**
* Add authentication based upon the custom {@link AuthenticationProvider} that is
* passed in. Since the {@link AuthenticationProvider} implementation is unknown, all
* customizations must be done externally and the {@link AuthenticationManagerBuilder}
* is returned immediately.
*
* <p>
* This method <b>does NOT</b> ensure that the {@link UserDetailsService} is available
* for the {@link #getDefaultUserDetailsService()} method.
*
* Note that an {@link Exception} might be thrown if an error occurs when adding the {@link AuthenticationProvider}.
*
* @return a {@link AuthenticationManagerBuilder} to allow further authentication to
* be provided to the {@link AuthenticationManagerBuilder}
*/
public AuthenticationManagerBuilder authenticationProvider(
AuthenticationProvider authenticationProvider) {
this.authenticationProviders.add(authenticationProvider);
return this;
}
@Override
protected ProviderManager performBuild() throws Exception {
if (!isConfigured()) {
logger.debug("No authenticationProviders and no parentAuthenticationManager defined. Returning null.");
return null;
}
ProviderManager providerManager = new ProviderManager(authenticationProviders,
parentAuthenticationManager);
if (eraseCredentials != null) {
providerManager.setEraseCredentialsAfterAuthentication(eraseCredentials);
}
if (eventPublisher != null) {
providerManager.setAuthenticationEventPublisher(eventPublisher);
}
providerManager = postProcess(providerManager);
return providerManager;
}
/**
* Determines if the {@link AuthenticationManagerBuilder} is configured to build a non
* null {@link AuthenticationManager}. This means that either a non-null parent is
* specified or at least one {@link AuthenticationProvider} has been specified.
*
* <p>
* When using {@link SecurityConfigurer} instances, the
* {@link AuthenticationManagerBuilder} will not be configured until the
* {@link SecurityConfigurer#configure(SecurityBuilder)} methods. This means a
* {@link SecurityConfigurer} that is last could check this method and provide a
* default configuration in the {@link SecurityConfigurer#configure(SecurityBuilder)}
* method.
*
* @return
*/
public boolean isConfigured() {
return !authenticationProviders.isEmpty() || parentAuthenticationManager != null;
}
/**
* Gets the default {@link UserDetailsService} for the
* {@link AuthenticationManagerBuilder}. The result may be null in some circumstances.
*
* @return the default {@link UserDetailsService} for the
* {@link AuthenticationManagerBuilder}
*/
public UserDetailsService getDefaultUserDetailsService() {
return this.defaultUserDetailsService;
}
/**
* Captures the {@link UserDetailsService} from any {@link UserDetailsAwareConfigurer}
* .
*
* @param configurer the {@link UserDetailsAwareConfigurer} to capture the
* {@link UserDetailsService} from.
* @return the {@link UserDetailsAwareConfigurer} for further customizations
* @throws Exception if an error occurs
*/
private <C extends UserDetailsAwareConfigurer<AuthenticationManagerBuilder, ? extends UserDetailsService>> C apply(
C configurer) throws Exception {
this.defaultUserDetailsService = configurer.getUserDetailsService();
return (C) super.apply(configurer);
}
}
3 AuthenticationProvider 该接口实现验证的业务逻辑 只包含两个方法
1 boolean supports(Class<?> authentication); 是否支持当前验证的类型,验证的类型不只是包含UsernamePasswordAuthenticationToken这个类型,还有其他的如果自定义其他的类型可以根据instance 进行判断。
2
Authentication authenticate(Authentication authentication)throws AuthenticationException; 该方法则是实现对应的逻辑判断,用户是否能够认证通过,如果通过则返回一个充满相关信息的Authentication 实例。该实例最终被放置到SecurityContext中。至此整个认证流程完成
总结: 认证任务总是一级级下发,最终要真正去做的只有实现了AuthenticationProvider 这个接口的小兵。整个过程很有序。。。。。