问题描述:apache shiro是一个被广泛使用的安全层框架,用户的登陆/退出/权限控制/Cookie等功能都可以交给shiro来管理。项目开发中登录认证也是正是采用shiro进行管理的,由于shiro默认对ehcache的支持,所以shiro缓存管理我们采用了ehcache。最近测试中偶尔发现,在对登录用户所属机构信息进行变更后,再次登录系统用户所属机构信息竟然没有发生变化,经分析发现是ehcache缓存引起的,用户所属机构变更后并未同步/清空缓存的用户信息。解决问题关键是在合适时机清空登录用户的ehcache缓存。
解决方案一,用户退出系统时,清空ehcache中登录用户缓存。自定义过滤器SystemLogoutFilter,继承shiro默认退出LogoutFilter过滤器,重写其中preHandle方法,实现清除缓存功能。
public class SystemLogoutFilter extends LogoutFilter {
@Override
protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
Subject subject = getSubject(request, response);
// 在此处清除用户缓存
Principal principal = (Principal)subject.getPrincipal();
if(principal!=null) {
CacheUtils.remove(UserUtils.USER_CACHE, UserUtils.USER_CACHE_ID_ + principal.getId());
CacheUtils.remove(UserUtils.USER_CACHE, UserUtils.USER_CACHE_LOGIN_NAME_ + principal.getLoginName());
}
String redirectUrl = getRedirectUrl(request, response, subject);
try {
subject.logout();
} catch (SessionException ise) {
ise.printStackTrace();
}
issueRedirect(request, response, redirectUrl);
return false;
}
}
解决方案二,在shiro登录认证前,把前一次用户的缓存信息清除,具体方法如下:
Shiro的认证依赖AuthenticatingRealm里的getAuthenticationInfo方法,该方法会调用自定义的认证方法doGetAuthenticationInfo获取本次认证的结果。
public final AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
AuthenticationInfo info = getCachedAuthenticationInfo(token);
if (info == null) {
//otherwise not cached, perform the lookup:
info = doGetAuthenticationInfo(token);
log.debug("Looked up AuthenticationInfo [{}] from doGetAuthenticationInfo", info);
if (token != null && info != null) {
cacheAuthenticationInfoIfPossible(token, info);
}
} else {
log.debug("Using cached authentication info [{}] to perform credentials matching.", info);
}
if (info != null) {
assertCredentialsMatch(token, info);
} else {
log.debug("No AuthenticationInfo found for submitted AuthenticationToken [{}]. Returning null.", token);
}
return info;
}
Shiro登录认证前,最终会回调doGetAuthenticationInfo函数,在该函数中根据用户填报登录名获取用户对象User,紧接着清除该对象上次登录的缓存信息UserUtils.clearCache(user)。
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken) {
UsernamePasswordToken token = (UsernamePasswordToken) authcToken;
int activeSessionSize = getSystemService().getSessionDao().getActiveSessions(false).size();
if (logger.isDebugEnabled()){
logger.debug("login submit, active session size: {}, username: {}", activeSessionSize, token.getUsername());
}
User user = getSystemService().getUserByLoginName(token.getUsername());
if (user != null) {
// 在此处清除用户缓存
UserUtils.clearCache(user);
byte[] salt = Encodes.decodeHex(user.getPassword().substring(0,16));
return new SimpleAuthenticationInfo(new Principal(user, token.isMobileLogin()),
user.getPassword().substring(16), ByteSource.Util.bytes(salt), getName());
} else {
return null;
}
}
综上所述,两种方案均能实现清理用户缓存目的,但方案一在清理用户缓存时机上更合理。