在Acegi的认证filter中,首先是org.acegisecurity.ui.webapp.AuthenticationProcessingFilter
这个类是用来对登录者进行认证。它的doFilter方法中调用了attemptAuthentication(httpRequest)方法,并且如果返回的结果说明有效,则把经过认证的信息通过successfulAuthentication(httpRequest, httpResponse, authResult)保存起来。 其中代码如下:
attemptAuthentication方法是用来把登录者填写的用户名、密码信息转化为UsernamePasswordAuthenticationToken类型,并通过AuthenticationManager的authenticate方法进行作业。代码如下:
其中authenticate(authRequest)代码如下,其目的是通过各个实现类来实现认证。
其中doAuthentication(authRequest)方法在ProviderManager中定义,因为其中提供了多个provider,所以需要遍历各个验证器。
对认证管理器中提供的各个provider进行判断,如果有任何一个通过验证,那么就返回,并发布成功的event,否则抛出异常。
而其中privider中进行的验证根据各个实现而不同,例如对于DaoAuthenticationProvider,其提供的authenticate是通过用户名和UserDetailsService来获取用户资料信息,并且在各个检验条件合格后把认证信息绑定并返回。代码如下:
而对于RememberMeAuthenticationProvider,其提供的验证方式是与在自动登录时保存的key值有关。代码如下:
这样,如果在自动登录时保存的用户名、密码等信息经过了系统认证,则也允许登录。
这个类是用来对登录者进行认证。它的doFilter方法中调用了attemptAuthentication(httpRequest)方法,并且如果返回的结果说明有效,则把经过认证的信息通过successfulAuthentication(httpRequest, httpResponse, authResult)保存起来。 其中代码如下:
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
if (!(request instanceof HttpServletRequest)) {
throw new ServletException("Can only process HttpServletRequest");
}
if (!(response instanceof HttpServletResponse)) {
throw new ServletException("Can only process HttpServletResponse");
}
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
if (requiresAuthentication(httpRequest, httpResponse)) {
if (logger.isDebugEnabled()) {
logger.debug("Request is to process authentication");
}
Authentication authResult;
try {
onPreAuthentication(httpRequest, httpResponse);
authResult = attemptAuthentication(httpRequest);
} catch (AuthenticationException failed) {
// Authentication failed
unsuccessfulAuthentication(httpRequest, httpResponse, failed);
return;
}
// Authentication success
if (continueChainBeforeSuccessfulAuthentication) {
chain.doFilter(request, response);
}
successfulAuthentication(httpRequest, httpResponse, authResult);
return;
}
chain.doFilter(request, response);
}
attemptAuthentication方法是用来把登录者填写的用户名、密码信息转化为UsernamePasswordAuthenticationToken类型,并通过AuthenticationManager的authenticate方法进行作业。代码如下:
public Authentication attemptAuthentication(HttpServletRequest request)
throws AuthenticationException {
String username = obtainUsername(request);
String password = obtainPassword(request);
if (username == null) {
username = "";
}
if (password == null) {
password = "";
}
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
// Place the last username attempted into HttpSession for views
request.getSession().setAttribute(ACEGI_SECURITY_LAST_USERNAME_KEY, username);
// Allow subclasses to set the "details" property
setDetails(request, authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
}
其中authenticate(authRequest)代码如下,其目的是通过各个实现类来实现认证。
public final Authentication authenticate(Authentication authRequest)
throws AuthenticationException {
try {
Authentication authResult = doAuthentication(authRequest);
copyDetails(authRequest, authResult);
return authResult;
} catch (AuthenticationException e) {
e.setAuthentication(authRequest);
throw e;
}
}
其中doAuthentication(authRequest)方法在ProviderManager中定义,因为其中提供了多个provider,所以需要遍历各个验证器。
public Authentication doAuthentication(Authentication authentication)
throws AuthenticationException {
Iterator iter = providers.iterator();
Class toTest = authentication.getClass();
AuthenticationException lastException = null;
while (iter.hasNext()) {
AuthenticationProvider provider = (AuthenticationProvider) iter.next();
if (provider.supports(toTest)) {
logger.debug("Authentication attempt using " + provider.getClass().getName());
Authentication result = null;
try {
result = provider.authenticate(authentication);
sessionController.checkAuthenticationAllowed(result);
} catch (AuthenticationException ae) {
lastException = ae;
result = null;
}
if (result != null) {
sessionController.registerSuccessfulAuthentication(result);
publishEvent(new AuthenticationSuccessEvent(result));
return result;
}
}
}
if (lastException == null) {
lastException = new ProviderNotFoundException(messages.getMessage("ProviderManager.providerNotFound",
new Object[] {toTest.getName()}, "No AuthenticationProvider found for {0}"));
}
// Publish the event
String className = exceptionMappings.getProperty(lastException.getClass().getName());
AbstractAuthenticationEvent event = null;
if (className != null) {
try {
Class clazz = getClass().getClassLoader().loadClass(className);
Constructor constructor = clazz.getConstructor(new Class[] {
Authentication.class, AuthenticationException.class
});
Object obj = constructor.newInstance(new Object[] {authentication, lastException});
Assert.isInstanceOf(AbstractAuthenticationEvent.class, obj, "Must be an AbstractAuthenticationEvent");
event = (AbstractAuthenticationEvent) obj;
} catch (ClassNotFoundException ignored) {}
catch (NoSuchMethodException ignored) {}
catch (IllegalAccessException ignored) {}
catch (InstantiationException ignored) {}
catch (InvocationTargetException ignored) {}
}
if (event != null) {
publishEvent(event);
} else {
if (logger.isDebugEnabled()) {
logger.debug("No event was found for the exception " + lastException.getClass().getName());
}
}
// Throw the exception
throw lastException;
}
对认证管理器中提供的各个provider进行判断,如果有任何一个通过验证,那么就返回,并发布成功的event,否则抛出异常。
而其中privider中进行的验证根据各个实现而不同,例如对于DaoAuthenticationProvider,其提供的authenticate是通过用户名和UserDetailsService来获取用户资料信息,并且在各个检验条件合格后把认证信息绑定并返回。代码如下:
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication,
messages.getMessage("AbstractUserDetailsAuthenticationProvider.onlySupports",
"Only UsernamePasswordAuthenticationToken is supported"));
// Determine username
String username = (authentication.getPrincipal() == null) ? "NONE_PROVIDED" : authentication.getName();
boolean cacheWasUsed = true;
UserDetails user = this.userCache.getUserFromCache(username);
if (user == null) {
cacheWasUsed = false;
try {
user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication);
} catch (UsernameNotFoundException notFound) {
if (hideUserNotFoundExceptions) {
throw new BadCredentialsException(messages.getMessage(
"AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
} else {
throw notFound;
}
}
Assert.notNull(user, "retrieveUser returned null - a violation of the interface contract");
}
if (!user.isAccountNonLocked()) {
throw new LockedException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.locked",
"User account is locked"));
}
if (!user.isEnabled()) {
throw new DisabledException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.disabled",
"User is disabled"));
}
if (!user.isAccountNonExpired()) {
throw new AccountExpiredException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.expired",
"User account has expired"));
}
// This check must come here, as we don't want to tell users
// about account status unless they presented the correct credentials
try {
additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication);
} catch (AuthenticationException exception) {
if(cacheWasUsed) {
// There was a problem, so try again after checking
// we're using latest data (ie not from the cache)
cacheWasUsed = false;
user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication);
additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication);
} else {
throw exception;
}
}
if (!user.isCredentialsNonExpired()) {
throw new CredentialsExpiredException(messages.getMessage(
"AbstractUserDetailsAuthenticationProvider.credentialsExpired", "User credentials have expired"));
}
if (!cacheWasUsed) {
this.userCache.putUserInCache(user);
}
Object principalToReturn = user;
if (forcePrincipalAsString) {
principalToReturn = user.getUsername();
}
return createSuccessAuthentication(principalToReturn, authentication, user);
}
而对于RememberMeAuthenticationProvider,其提供的验证方式是与在自动登录时保存的key值有关。代码如下:
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
if (!supports(authentication.getClass())) {
return null;
}
if (this.key.hashCode() != ((RememberMeAuthenticationToken) authentication).getKeyHash()) {
throw new BadCredentialsException(messages.getMessage("RememberMeAuthenticationProvider.incorrectKey",
"The presented RememberMeAuthenticationToken does not contain the expected key"));
}
return authentication;
}
这样,如果在自动登录时保存的用户名、密码等信息经过了系统认证,则也允许登录。