以下是简化了的Acegi的配置文件:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<bean id="myFilterChainProxy" class="org.acegisecurity.util.FilterChainProxy">
<property name="filterInvocationDefinitionSource">
<value><![CDATA[
CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
PATTERN_TYPE_APACHE_ANT
/**=mySecAuthenticationProcessingFilter,mySecFilterInvocationInterceptor
]]></value>
</property>
</bean>
<bean id="mySecAuthenticationProcessingFilter" class="com.woorh.ageci.MySecAuthenticationProcessingFilter">
<property name="authenticationManager" ref="mySecAuthenticationManager"/>
</bean>
<bean id="mySecAuthenticationManager" class="org.acegisecurity.providers.ProviderManager">
<property name="providers">
<list>
<ref local="mySecDaoAuthenticationProvider" />
</list>
</property>
</bean>
<bean id="mySecDaoAuthenticationProvider" class="org.acegisecurity.providers.dao.DaoAuthenticationProvider">
<property name="userDetailsService">
<ref bean="myUserDetails"/>
</property>
</bean>
<bean id="myUserDetails" class="com.woorh.ageci.MySecUserDetailsService"/>
<!--上面是验证器的配置,下面是授权器的配置-->
<bean id="mySecFilterInvocationInterceptor" class="org.acegisecurity.intercept.web.FilterSecurityInterceptor">
<property name="authenticationManager" ref="mySecAuthenticationManager"/>
<property name="accessDecisionManager">
<bean class="org.acegisecurity.vote.AffirmativeBased">
<property name="decisionVoters">
<list>
<bean class="org.acegisecurity.vote.AuthenticatedVoter"/>
</list>
</property>
</bean>
</property>
<property name="objectDefinitionSource">
<value><![CDATA[
CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
PATTERN_TYPE_APACHE_ANT
/main/**=SEC_AUTH_LOGON
/template/**=SEC_AUTH_LOGON
]]></value>
</property>
</bean>
</beans>
一、ACEGI验证器——AuthenticationProcessingFilter
配置文件MySecAuthenticationProcessingFilter是继承自抽象类AuthenticationProcessingFilter的,需要实现其中的几个方法。
MySecAuthenticationProcessingFilter是个Filter子类,每次请求过来的时候,先执行其doFilter()方法。doFilter方法在AuthenticationProcessingFilter的父类AbstractProcessingFilter中实现:
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);
}
1.requiresAuthentication(httpRequest, httpResponse)判断请求地址是否为j_acegi_security_check,不是的话就不用过滤了
protected boolean requiresAuthentication(HttpServletRequest request, HttpServletResponse response) {
String uri = request.getRequestURI();
int pathParamIndex = uri.indexOf(';');
if (pathParamIndex > 0) {
// strip everything after the first semi-colon
uri = uri.substring(0, pathParamIndex);
}
if ("".equals(request.getContextPath())) {
return uri.endsWith(filterProcessesUrl);
}
return uri.endsWith(request.getContextPath() + filterProcessesUrl);
}
private String filterProcessesUrl = getDefaultFilterProcessesUrl();
AuthenticationProcessingFilter
public String getDefaultFilterProcessesUrl() {
return "/j_acegi_security_check";
}
2.authResult = attemptAuthentication(httpRequest);开始处理请求
AuthenticationProcessingFilter.attemptAuthentication(HttpServletRequest request):
public Authentication attemptAuthentication(HttpServletRequest request) throws AuthenticationException {
String username = obtainUsername(request);
String password = obtainPassword(request);
if (username == null) {
username = "";
}
if (password == null) {
password = "";
}
username = username.trim();
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);
}
页面输入的username、password保存到UsernamePasswordAuthenticationToken对象中,将此对象作为参数来判断密码是否正确
public UsernamePasswordAuthenticationToken(Object principal, Object credentials) {
super(null);
this.principal = principal;
this.credentials = credentials;
setAuthenticated(false);
}
2.1.this.getAuthenticationManager().authenticate(authRequest);
public void setAuthenticationManager(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}
private AuthenticationManager authenticationManager;
其来源是在配置文件中配置的ProviderManager的对象。
authenticate()方法在其父类AbstractAuthenticationManager中实现,AbstractAuthenticationManager.authenticate(Authentication authentication):
public final Authentication authenticate(Authentication authRequest)
throws AuthenticationException {
try {
return doAuthentication(authRequest);
} catch (AuthenticationException e) {
e.setAuthentication(authRequest);
if (clearExtraInformation) {
e.clearExtraInformation();
}
throw e;
}
}
调用ProviderManager中的doAuthentication(Authentication authentication):
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);
copyDetails(authentication, result);
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;
}
这个providers是来自配置文件中的链表对象,链表中的元素是DaoAuthenticationProvider对象。
DaoAuthenticationProvider的父类AbstractAuthenticationManager.authenticate(Authentication authentication):
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");
}
preAuthenticationChecks.check(user);
// 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;
}
}
postAuthenticationChecks.check(user);
if (!cacheWasUsed) {
this.userCache.putUserInCache(user);
}
Object principalToReturn = user;
if (forcePrincipalAsString) {
principalToReturn = user.getUsername();
}
return createSuccessAuthentication(principalToReturn, authentication, user);
}
2.1.1、user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication);
在DaoAuthenticationProvider中实现:
protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication)
throws AuthenticationException {
UserDetails loadedUser;
try {
loadedUser = this.getUserDetailsService().loadUserByUsername(username);
}
catch (DataAccessException repositoryProblem) {
throw new AuthenticationServiceException(repositoryProblem.getMessage(), repositoryProblem);
}
if (loadedUser == null) {
throw new AuthenticationServiceException(
"UserDetailsService returned null, which is an interface contract violation");
}
return loadedUser;
}
public UserDetailsService getUserDetailsService() {
return userDetailsService;
}
userDetailsService来自配置文件的MySecUserDetailsService对象,这个对象必须是实现接口UserDetailsService,重写其方法loadUserByUsername(String username)。至于怎么去加载用户信息就由业务系统自己负责了,如果要从数据库中读取信息,可以继承JdbcDaoImpl(此类封装了些数据库操作,也实现了UserDetailsService接口),重写其loadUserByUsername方法。
2.1.2、additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication);
方法在DaoAuthenticationProvider中实现:
protected void additionalAuthenticationChecks(UserDetails userDetails,
UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
Object salt = null;
if (this.saltSource != null) {
salt = this.saltSource.getSalt(userDetails);
}
if (authentication.getCredentials() == null) {
throw new BadCredentialsException(messages.getMessage(
"AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"),
includeDetailsObject ? userDetails : null);
}
String presentedPassword = authentication.getCredentials() == null ? "" : authentication.getCredentials()
.toString();
if (!passwordEncoder.isPasswordValid(userDetails.getPassword(), presentedPassword, salt)) {
throw new BadCredentialsException(messages.getMessage(
"AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"),
includeDetailsObject ? userDetails : null);
}
}
passwordEncoder.isPasswordValid(userDetails.getPassword(), presentedPassword, salt)开始判断用户输入的密码与数据库中的是否一致
使用默认的加密方式
private PasswordEncoder passwordEncoder = new PlaintextPasswordEncoder();
要修改的加密方式,也可以再配置文件中重新注入。
PlaintextPasswordEncoder.isPasswordValid(String encPass, String rawPass, Object salt):
public boolean isPasswordValid(String encPass, String rawPass, Object salt) {
String pass1 = encPass + "";
// Strict delimiters is false because pass2 never persisted anywhere
// and we want to avoid unnecessary exceptions as a result (the
// authentication will fail as the encodePassword never allows them)
String pass2 = mergePasswordAndSalt(rawPass, salt, false);
if (!ignorePasswordCase) {
return pass1.equals(pass2);
} else {
return pass1.equalsIgnoreCase(pass2);
}
}
protected String mergePasswordAndSalt(String password, Object salt, boolean strict) {
if (password == null) {
password = "";
}
if (strict && (salt != null)) {
if ((salt.toString().lastIndexOf("{") != -1) || (salt.toString().lastIndexOf("}") != -1)) {
throw new IllegalArgumentException("Cannot use { or } in salt.toString()");
}
}
if ((salt == null) || "".equals(salt)) {
return password;
} else {
return password + "{" + salt.toString() + "}";
}
}
2.1.3、密码判断成功后,接着createSuccessAuthentication(principalToReturn, authentication, user);
protected Authentication createSuccessAuthentication(Object principal, Authentication authentication,
UserDetails user) {
// Ensure we return the original credentials the user supplied,
// so subsequent attempts are successful even with encoded passwords.
// Also ensure we return the original getDetails(), so that future
// authentication events after cache expiry contain the details
UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(principal,
authentication.getCredentials(), user.getAuthorities());
result.setDetails(authentication.getDetails());
return result;
}
返回一个封装了用户等信息的UsernamePasswordAuthenticationToken对象
3、密码判断成功后,接着根据continueChainBeforeSuccessfulAuthentication判断是否马上执行过滤器链。默认为false,表示先执行一些业务系统的操作successfulAuthentication(httpRequest, httpResponse, authResult);在执行过滤器链。这样可以解决这种情况:先从业务系统中读取权限,接着在过滤器链中执行到授权器的时候就可以判断用户的请求是否被授权。
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response,
Authentication authResult) throws IOException {
if (logger.isDebugEnabled()) {
logger.debug("Authentication success: " + authResult.toString());
}
SecurityContextHolder.getContext().setAuthentication(authResult);
if (logger.isDebugEnabled()) {
logger.debug("Updated SecurityContextHolder to contain the following Authentication: '" + authResult + "'");
}
String targetUrl = determineTargetUrl(request);
if (logger.isDebugEnabled()) {
logger.debug("Redirecting to target URL from HTTP Session (or default): " + targetUrl);
}
onSuccessfulAuthentication(request, response, authResult);
rememberMeServices.loginSuccess(request, response, authResult);
// Fire event
if (this.eventPublisher != null) {
eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(authResult, this.getClass()));
}
sendRedirect(request, response, targetUrl);
}
onSuccessfulAuthentication(request, response, authResult);成功判断后,进行些操作,在开头说的MySecAuthenticationProcessingFilter中实现,完成业务系统的功能。
最后是请求的转向。
protected void sendRedirect(HttpServletRequest request, HttpServletResponse response, String url)
throws IOException {
String finalUrl;
if (!url.startsWith("http://") && !url.startsWith("https://")) {
if (useRelativeContext) {
finalUrl = url;
}
else {
finalUrl = request.getContextPath() + url;
}
}
else if (useRelativeContext) {
// Calculate the relative URL from the fully qualifed URL, minus the
// protocol and base context.
int len = request.getContextPath().length();
int index = url.indexOf(request.getContextPath()) + len;
finalUrl = url.substring(index);
if (finalUrl.length() > 1 && finalUrl.charAt(0) == '/') {
finalUrl = finalUrl.substring(1);
}
}
else {
finalUrl = url;
}
Assert.isTrue(!response.isCommitted(),
"Response already committed; the authentication mechanism must be able to modify buffer size");
response.setBufferSize(bufferSize);
response.sendRedirect(response.encodeRedirectURL(finalUrl));
}
整个验证过程是AuthenticationProcessingFilter、ProviderManager、DaoAuthenticationProvider、MySecUserDetailsService这几个类协同完成的