记一次由ehcache缓存引起的shiro登录信息变更失败的解决方案

问题描述: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;
		}
	}

综上所述,两种方案均能实现清理用户缓存目的,但方案一在清理用户缓存时机上更合理。

猜你喜欢

转载自blog.csdn.net/wangpf2011/article/details/107261878