Acegi学习心得《二》

在配置Acegi Filter Chain Proxy是设定了targetClass,并制定了其代表的类,并在其他配置文件中声明了其具体的实现。其中实现是通过指定filterInvocationDefinitionSource的。如下:
	<!--****** Fileter Chain ******-->
<bean id="filterChainProxy" class="org.acegisecurity.util.FilterChainProxy">
<property name="filterInvocationDefinitionSource">
<value>
CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
PATTERN_TYPE_APACHE_ANT
/**=authenticationProcessingFilter,logoutFilter,rememberMeProcessingFilter,exceptionTranslationFilter
</value>
</property>
</bean>

这里声明了过滤链时需要考虑前后,因为在实现是是从第一个开始的。
也就是说authenticationProcessingFilter是最先触发的,其触发的条件是其URI中是以j_acegi_security_check结尾的。而对于LogoutFilter因为具体实现时它会考虑请求的URI,所以并不是所有的请求都会触发这个过滤器。而rememberMeProcessingFilter并没有任何条件,它的实现如下:
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 (SecurityContextHolder.getContext().getAuthentication() == null) {
//这里是通过rememberMeServices的autoLogin来进行登录作业的,具体的实现方式可查看下一段代码
Authentication rememberMeAuth = rememberMeServices.autoLogin(httpRequest, httpResponse);

if (rememberMeAuth != null) {
// Attempt authenticaton via AuthenticationManager
try {
authenticationManager.authenticate(rememberMeAuth);

// Store to SecurityContextHolder
SecurityContextHolder.getContext().setAuthentication(rememberMeAuth);

if (logger.isDebugEnabled()) {
logger.debug("SecurityContextHolder populated with remember-me token: '"
+ SecurityContextHolder.getContext().getAuthentication() + "'");
}

// Fire event
if (this.eventPublisher != null) {
eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(
SecurityContextHolder.getContext().getAuthentication(), this.getClass()));
}
} catch (AuthenticationException authenticationException) {
if (logger.isDebugEnabled()) {
logger.debug(
"SecurityContextHolder not populated with remember-me token, as AuthenticationManager rejected Authentication returned by RememberMeServices: '"
+ rememberMeAuth + "'; invalidating remember-me token", authenticationException);
}

rememberMeServices.loginFail(httpRequest, httpResponse);
}
}

chain.doFilter(request, response);
} else {
if (logger.isDebugEnabled()) {
logger.debug("SecurityContextHolder not populated with remember-me token, as it already contained: '"
+ SecurityContextHolder.getContext().getAuthentication() + "'");
}

chain.doFilter(request, response);
}
}

承上说明:自动登录时通过其提供的services进行登录,在登录后返回一个Authentication对象,并通过authenticationManager的校验(认证),判断是否可以通过,如果允许(也就是没有抛出异常)则保存至ServletContextHolder中,并发布成功的事件。否则继续下一个过滤器。
而对于自动登录中的RememberMeServices的实现代码如下:
public Authentication autoLogin(HttpServletRequest request, HttpServletResponse response) {
Cookie[] cookies = request.getCookies();

if ((cookies == null) || (cookies.length == 0)) {
return null;
}

for (int i = 0; i < cookies.length; i++) {
if (ACEGI_SECURITY_HASHED_REMEMBER_ME_COOKIE_KEY.equals(cookies[i].getName())) {
String cookieValue = cookies[i].getValue();

if (Base64.isArrayByteBase64(cookieValue.getBytes())) {
if (logger.isDebugEnabled()) {
logger.debug("Remember-me cookie detected");
}

// Decode token from Base64
// format of token is:
// username + ":" + expiryTime + ":" + Md5Hex(username + ":" + expiryTime + ":" + password + ":" + key)
String cookieAsPlainText = new String(Base64.decodeBase64(cookieValue.getBytes()));
String[] cookieTokens = StringUtils.delimitedListToStringArray(cookieAsPlainText, ":");

if (cookieTokens.length == 3) {
long tokenExpiryTime;

try {
tokenExpiryTime = new Long(cookieTokens[1]).longValue();
} catch (NumberFormatException nfe) {
cancelCookie(request, response,
"Cookie token[1] did not contain a valid number (contained '" + cookieTokens[1] + "')");

return null;
}

// Check it has not expired
if (tokenExpiryTime < System.currentTimeMillis()) {
cancelCookie(request, response,
"Cookie token[1] has expired (expired on '" + new Date(tokenExpiryTime)
+ "'; current time is '" + new Date() + "')");

return null;
}

// Check the user exists
// Defer lookup until after expiry time checked, to possibly avoid expensive lookup
UserDetails userDetails;

try {
userDetails = this.userDetailsService.loadUserByUsername(cookieTokens[0]);
} catch (UsernameNotFoundException notFound) {
cancelCookie(request, response,
"Cookie token[0] contained username '" + cookieTokens[0] + "' but was not found");

return null;
}

// Immediately reject if the user is not allowed to login
if (!userDetails.isAccountNonExpired() || !userDetails.isCredentialsNonExpired()
|| !userDetails.isEnabled()) {
cancelCookie(request, response,
"Cookie token[0] contained username '" + cookieTokens[0]
+ "' but account has expired, credentials have expired, or user is disabled");

return null;
}

// Check signature of token matches remaining details
// Must do this after user lookup, as we need the DAO-derived password
// If efficiency was a major issue, just add in a UserCache implementation,
// but recall this method is usually only called one per HttpSession
// (as if the token is valid, it will cause SecurityContextHolder population, whilst
// if invalid, will cause the cookie to be cancelled)
String expectedTokenSignature = DigestUtils.md5Hex(userDetails.getUsername() + ":"
+ tokenExpiryTime + ":" + userDetails.getPassword() + ":" + this.key);

if (!expectedTokenSignature.equals(cookieTokens[2])) {
cancelCookie(request, response,
"Cookie token[2] contained signature '" + cookieTokens[2] + "' but expected '"
+ expectedTokenSignature + "'");

return null;
}

// By this stage we have a valid token
if (logger.isDebugEnabled()) {
logger.debug("Remember-me cookie accepted");
}

RememberMeAuthenticationToken auth = new RememberMeAuthenticationToken(this.key, userDetails,
userDetails.getAuthorities());
auth.setDetails(authenticationDetailsSource.buildDetails((HttpServletRequest) request));

return auth;
} else {
cancelCookie(request, response,
"Cookie token did not contain 3 tokens; decoded value was '" + cookieAsPlainText + "'");

return null;
}
} else {
cancelCookie(request, response,
"Cookie token was not Base64 encoded; value was '" + cookieValue + "'");

return null;
}
}
}

return null;
}

承上说明:我们采用的是TokenBasedRememberMeServices的实现方式,其主要过程是读取Cookies文件,判断其中是否有Acegi标识的信息,并检验是否过期;并根据用户名和配置的userDetailsService去获取用户的具体信息(判断是否过期、密码是否有效、是否可用等),同时加密后与Cookies中的信息进行对比,判断是否一致。因为每一个session都需要保证如果用户有效,则装配信息,无效的话则需要取消(就是将Cookies设置为null,并返回响应)。做完校验后,把这个userDetail信息绑定到Key对应的键值中,供前面的AuthenticationManager做认证,并把userDetail保存至Request中。
那么就有一个疑问是Cookies的信息是在什么时候保存起来的呢?
这个其实可以先看一下配置信息
	<!-- 表单认证处理Filter -->
<bean id="authenticationProcessingFilter" class="org.acegisecurity.ui.webapp.AuthenticationProcessingFilter">
<property name="authenticationManager" ref="authenticationManager"></property>
<property name="authenticationFailureUrl" value="/acegi_login.jsp?login_error=1"></property>
<property name="defaultTargetUrl" value="/userinfo.jsp"></property>
<property name="filterProcessesUrl" value="/j_acegi_security_check"></property>
<property name="rememberMeServices" ref="rememberMeServices"></property>
</bean>

以前看别人的示例中并没有加上rememberMeServices,这样是没有办法自动登录的,因为系统提供的默认实现是NullRememberMeServices,并没有保存Cookies信息。
而这边配置的是TokenBasedRememberMeServices实现,其根据提交的参数是否有“_acegi_security_remember_me”选中,若有则保存Cookies。
并且如果注销后则系统将Cookies删除。所以要保证自动登录的话,是不可以点击注销的。
因为在LogoutFilter中的构造器中的Holder中加入了rememberMeServices实现,所以在注销时会调用其logout方法(就是把Cookies设为空并过时的操作),以及其他诸如org.acegisecurity.ui.logout.SecurityContextLogoutHandler的实现,用于清空Session。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值