项目中用到了shiro session会话管理机制,今天来总结一下,以下都是在spring boot框架实现。
shiro的session管理机制很完善,也是独立于j2ee容器且不依赖的,所以我们完全可以使用shiro提供给我们的session会话管理来实现我们的业务逻辑,默认提供的sessionDAO是memorySessionDAO,这里也主要讲它的配置和原理。
首先来看下如何在spring boot下配置默认的session会话管理:
ShiroConfig类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 | @Configuration public class ShiroConfig { @Value ( "${spring.redis.host}" ) private String host; @Value ( "${spring.redis.password}" ) private String password; @Value ( "${spring.redis.port}" ) private int port; @Value ( "${spring.redis.timeout}" ) private int timeout; @Value ( "${spring.cache.type}" ) private String cacheType ; @Value ( "${server.session-timeout}" ) private int tomcatTimeout; @Bean public static LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() { return new LifecycleBeanPostProcessor(); } @Bean public SecurityManager securityManager() { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); //设置realm. securityManager.setRealm(userRealm()); // 自定义缓存实现 使用redis if (Constant.CACHE_TYPE_REDIS.equals(cacheType)) { securityManager.setCacheManager(rediscacheManager()); } else { securityManager.setCacheManager(ehCacheManager()); } securityManager.setSessionManager(sessionManager()); return securityManager; } @Bean UserRealm userRealm() { UserRealm userRealm = new UserRealm(); return userRealm; } @Bean public SessionDAO sessionDAO() { return new MemorySessionDAO(); //使用默认的MemorySessionDAO } /** * shiro session的管理 */ @Bean public DefaultWebSessionManager sessionManager() { //配置默认的sesssion管理器 DefaultWebSessionManager sessionManager = new DefaultWebSessionManager(); sessionManager.setGlobalSessionTimeout(tomcatTimeout * 1000 ); sessionManager.setSessionDAO(sessionDAO()); Collection<SessionListener> listeners = new ArrayList<SessionListener>(); listeners.add( new BDSessionListener()); sessionManager.setSessionListeners(listeners); return sessionManager; } @Bean public EhCacheManager ehCacheManager() { EhCacheManager em = new EhCacheManager(); em.setCacheManager(cacheManager()); return em; } @Bean ( "cacheManager2" ) CacheManager cacheManager(){ return CacheManager.create(); } } |
DefaultWebSessionManager是我们配置管理器的核心类,里面配置了session的超时时间,sessionDAO和监听器。
在上面我们向spring boot注入了
1 2 3 4 | @Bean public SessionDAO sessionDAO() { return new MemorySessionDAO(); //使用默认的MemorySessionDAO } |
MemorySessionDAO是shiro已经实现的Session的CRUD实现类,包括对session的操作,增删改读。
下面来看下MemorySessionDAO的源码实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | public class MemorySessionDAO extends AbstractSessionDAO { private static final Logger log = LoggerFactory.getLogger(MemorySessionDAO. class ); private ConcurrentMap<Serializable, Session> sessions = new ConcurrentHashMap(); public MemorySessionDAO() { } protected Serializable doCreate(Session session) { Serializable sessionId = this .generateSessionId(session); this .assignSessionId(session, sessionId); this .storeSession(sessionId, session); return sessionId; } protected Session storeSession(Serializable id, Session session) { if (id == null ) { throw new NullPointerException( "id argument cannot be null." ); } else { return (Session) this .sessions.putIfAbsent(id, session); } } protected Session doReadSession(Serializable sessionId) { return (Session) this .sessions.get(sessionId); } public void update(Session session) throws UnknownSessionException { this .storeSession(session.getId(), session); } public void delete(Session session) { if (session == null ) { throw new NullPointerException( "session argument cannot be null." ); } else { Serializable id = session.getId(); if (id != null ) { this .sessions.remove(id); } } } public Collection<Session> getActiveSessions() { Collection<Session> values = this .sessions.values(); return (Collection)(CollectionUtils.isEmpty(values) ? Collections.emptySet() : Collections.unmodifiableCollection(values)); } } |
在源码中我们能看到MemorySessionDAO继承了AbstractSessionDAO类,这是一个抽象类,里面实现了一部分方法,而AbstractSessionDAO又实现了SessionDAO接口,这才是顶级接口,里面提供了5个方法:
1 2 3 4 5 6 7 8 9 10 11 12 | public interface SessionDAO { Serializable create(Session var1); Session readSession(Serializable var1) throws UnknownSessionException; void update(Session var1) throws UnknownSessionException; void delete(Session var1); Collection<Session> getActiveSessions(); } |
这5个方法也是memorySessionDAO实现的5个方法。
可以看出配置非常简单,我们几乎不需要做任何事情,下面来看看怎么来使用吧,
以一个SessionService为例:
1 2 3 4 5 6 | @Service public interface SessionService { List<UserOnline> list(); //得到系统在线用户 boolean forceLogout(String sessionId); //强制登出 } |
里面有两个方法,分别是查询系统当前在线的用户,以及强制登出某个用户。
实现如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | @Service public class SessionServiceImpl implements SessionService { private final SessionDAO sessionDAO; @Autowired public SessionServiceImpl(SessionDAO sessionDAO) { this .sessionDAO = sessionDAO; } @Override public List<UserOnline> list() { List<UserOnline> list = new ArrayList<>(); Collection<Session> sessions = sessionDAO.getActiveSessions(); for (Session session : sessions) { UserOnline userOnline = new UserOnline(); if (session.getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY) == null ) { continue ; } else { SimplePrincipalCollection principalCollection = (SimplePrincipalCollection) session .getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY); UserDO userDO = (UserDO) principalCollection.getPrimaryPrincipal(); userOnline.setUsername(userDO.getUsername()); } userOnline.setId((String) session.getId()); userOnline.setHost(session.getHost()); userOnline.setStartTimestamp(session.getStartTimestamp()); userOnline.setLastAccessTime(session.getLastAccessTime()); userOnline.setTimeout(session.getTimeout()); list.add(userOnline); } return list; } @Override public boolean forceLogout(String sessionId) { Session session = sessionDAO.readSession(sessionId); session.setTimeout( 0 ); return true ; } } |
在代码中,我们注入了shiro的SessionDAO,
Collection<Session> sessions = sessionDAO.getActiveSessions(); 可以得到当前在线的session集合。
每一个session都封装了用户的各种信息,
userOnline.setId((String) session.getId()); //得到session的ID
userOnline.setHost(session.getHost()); //得到用户的主机
userOnline.setStartTimestamp(session.getStartTimestamp()); //得到用户的登录时间
userOnline.setLastAccessTime(session.getLastAccessTime());//得到用户最后登录时间
userOnline.setTimeout(session.getTimeout());/得到过期时间
如果我们想在强制让某用户登出系统,只需要session.setTimeout(0)即可。
是不是觉得非常简便,如果系统需求不高,我们完全可以使用shiro为我们实现的这个sessionDAO,
而如果系统有定制需求,我们也可以实现自定义的sessionDAO,比如我们可以将session的CRUD通过redis来实现,同样的继承AbstractSessionDAO类,重写它的方法即可,将session的各种存取修改以redis为载体,基本的逻辑就是这样,如果有问题欢迎在评论中留言。