体系结构
AuthenticationManager的体系结构
AuthenticationProvide
r
其实,在UserNamePasswrodAuthenticationFilter中,验证是通过AuthenticationManager,但AuthenticationManager就相当于一个AuthenticationProvider容器,会把验证转让给集合中的AuthenticationProvider去验证
请求一个index.jsp页面,由于页面不足,他会自动把请求承递到spring_security_login,也就是由DefaultLoginPageGeneratingFilter自动生成的一个登录页面
登陆界面
页面源码
第一次登录页面时候,服务器会产生一个Session,并把cookieid返回来给浏览器,所以可以看到请求地址行的请求参数中带有jsesessionid,其实就是一个cookie值了
同时,以spring_security_check结尾(不算上jessessionid参数)所以会激发filter UserNamePasswrodAuthenticationFilter了
接下来看一下他是怎么处理的
进入attempAuthentication()中
再看一下getAuthenticationManager()
可见authenticationManager默认实现是ProviderManager
以下对ProviderManager的authenticate源码进行仔细解读了
//ProviderManager.authenticate
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
Class<? extends Authentication> toTest = authentication.getClass();
AuthenticationException lastException = null;
Authentication result = null;
boolean debug = logger.isDebugEnabled();
for (AuthenticationProvider provider : getProviders()) {
if (!provider.supports(toTest)) {
continue;
}
if (debug) {
logger.debug("Authentication attempt using " + provider.getClass().getName());
}
try {
result = provider.authenticate(authentication);
if (result != null) {
copyDetails(authentication, result);
break;
}
} catch (AccountStatusException e) {
prepareException(e, authentication);
// SEC-546: Avoid polling additional providers if auth failure is due to invalid account status
throw e;
} catch (AuthenticationException e) {
lastException = e;
}
}
if (result == null && parent != null) {
// Allow the parent to try.
try {
result = parent.authenticate(authentication);
} catch (ProviderNotFoundException e) {
// ignore as we will throw below if no other exception occurred prior to calling parent and the parent
// may throw ProviderNotFound even though a provider in the child already handled the request
} catch (AuthenticationException e) {
lastException = e;
}
}
if (result != null) {
if (eraseCredentialsAfterAuthentication && (result instanceof CredentialsContainer)) {
// Authentication is complete. Remove credentials and other secret data from authentication
((CredentialsContainer)result).eraseCredentials();
}
eventPublisher.publishAuthenticationSuccess(result);
return result;
}
// Parent was null, or didn't authenticate (or throw an exception).
if (lastException == null) {
lastException = new ProviderNotFoundException(messages.getMessage("ProviderManager.providerNotFound",
new Object[] {toTest.getName()}, "No AuthenticationProvider found for {0}"));
}
prepareException(lastException, authentication);
throw lastException;
}
看上面代码中的第一个for循环
配置文件中没有配置其他的AuthenticationProvider,所以采用默认实现AnonymousAuthenticationManager去验证
这里可见AuthenticationManager其实里面还分装这一个parent,他也是一个ProviderManager(AuthenticationManager),而他的默认的providers集合里面有一个DaoAuthenticationProvider
parent的验证过程也是一样的,只不过这次的provider是DaoAuthenticationProvider了
再看一下provider.authenticate()实现
看一下retriveUser()中的实现
这里是根据我们登录界面提交的用户名去检索一个UserDetails,其中包括用户的密码,权限还有一下相信信息,我们只要在Hibernate中加入一个UserDetailsService就可以实现,用户名和密码等存储在数据库了,当然,我们这里只是简单的将用户名guest和密码guest配置在配置文件中而已了
再看一下additionAuthenticationChecks()中的实现吧
这里简单的检查一下,我们的密码有没有加入艳值salt(salt可以在密码中对密码加密的时候一起加上去加密,增加破解难度),明显,我们这里没有,然后他会把我们之前在登录界面输入用户名密码的一个封装Authentication和数据库或者配置文件中获取的UserDetails的密码进行匹配
于是,验证成功了,回到AbstractAuthenticationFilter中看successfulAuthentication(request, response, chain, authResult);的实现,他会接着调用successfulAuthentication()
验证成功了,filter链不再继续进行下去了,于是一路返回
我们回去看SecurityContextPersistentFilter中借来下的处理吧
可见,验证登录过的用户下次就不用在登录了,因为springsecurity已经把上次登录成功的securityContext加入了SecurityContextRepository了
于是,登录成功了
总结
1、UserNamePasswrodAuthenticationFilter的处理过程就是先从登录界面中得到的Authentication(用户名和密码的封装实体)中的用户名去寻找配置文件或者数据库中的用户信息,并把该信息进行分装成一个UserDetails
2、将Authentication和UserDetails进行密码的匹配,可能还会涉及到盐值salt
3、若请求成功,则进行一些列设定,包括securityContext、rememberme、还有缓存该登陆成功的用户信息,用session进行标识,避免他登录过期,替换新的session(这会根据你在sessionManngerfilter中的配置,是新建还是迁移)