apache shiro集群实现(一) session共享

来源:互联网 发布:购买ET软件 编辑:程序博客网 时间:2024/06/06 05:04

    Apache Shiro的基本配置和构成这里就不详细说明了,其官网有说明文档,这里仅仅说明集群的解决方案,详细配置:shiro web config

    Apache Shiro集群要解决2个问题,一个是session的共享问题,一个是授权信息的cache共享问题,官网给的例子是Ehcache的实现,在配置说明上不算很详细,我这里用nosql(redis)替代了ehcache做了session和cache的存储。


shiro spring的默认配置(单机,非集群)

<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">    <property name="realm" ref="shiroDbRealm" />    <property name="cacheManager" ref="memoryConstrainedCacheManager" /></bean><!-- 自定义Realm --><bean id="shiroDbRealm" class="com.xxx.security.shiro.custom.ShiroDbRealm">    <property name="credentialsMatcher" ref="customCredentialsMather"></property></bean> <!-- 用户授权信息Cache(本机内存实现) --><bean id="memoryConstrainedCacheManager" class="org.apache.shiro.cache.MemoryConstrainedCacheManager" /><!-- 保证实现了Shiro内部lifecycle函数的bean执行 --><bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/><bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">    <property name="securityManager" ref="securityManager" />    <property name="loginUrl" value="/login" />    <property name="successUrl" value="/project" />    <property name="filterChainDefinitions">        <value>    /login = authc    /logout = logout</value>    </property></bean>


上面的配置是shiro非集群下的配置,DefaultWebSecurityManager类不需要注入sessionManager属性,它会使用默认的sessionManager类,请看源码

public DefaultWebSecurityManager() {        super();        ((DefaultSubjectDAO) this.subjectDAO).setSessionStorageEvaluator(new DefaultWebSessionStorageEvaluator());        this.sessionMode = HTTP_SESSION_MODE;        setSubjectFactory(new DefaultWebSubjectFactory());        setRememberMeManager(new CookieRememberMeManager());        setSessionManager(new ServletContainerSessionManager());}

在最后一行,set了默认的servlet容器实现的sessionManager,sessionManager会管理session的创建、删除等等。如果我们需要让session在集群中共享,就需要替换这个默认的sessionManager。在其官网上原话是这样的:

Native SessionsIf you want your session configuration settings and clustering to be portable across servlet containers(e.g. Jetty in testing, but Tomcat or JBoss in production), or you want to control specific session/clustering features, you can enable Shiro's native session management.The word 'Native' here means that Shiro's own enterprise session management implementation will be used to support all Subject and HttpServletRequest sessions and bypass the servlet container completely. But rest assured - Shiro implements the relevant parts of the Servlet specification directly so any existing web/http related code works as expected and never needs to 'know' that Shiro is transparently managing sessions.DefaultWebSessionManagerTo enable native session management for your web application, you will need to configure a native web-capable session manager to override the default servlet container-based one. You can do that by configuring an instance of DefaultWebSessionManager on Shiro's SecurityManager. 


我们可以看到如果要用集群,就需要用本地会话,这里shiro给我准备了一个默认的native session manager,DefaultWebSessionManager,所以我们要修改spring配置文件,注入DefaultWebSessionManager

<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">    <property name="sessionManager" ref="defaultWebSessionManager" />    <property name="realm" ref="shiroDbRealm" />    <property name="cacheManager" ref="memoryConstrainedCacheManager" /></bean><bean id="defaultWebSessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">    <property name="globalSessionTimeout" value="1200000" /></bean>

我们继续看DefaultWebSessionManager的源码发现其父类DefaultSessionManager中有sessionDAO属性,这个属性是真正实现了session储存的类,这个就是我们自己实现的redis session的储存类。

 protected SessionDAO sessionDAO; private CacheManager cacheManager; private boolean deleteInvalidSessions; public DefaultSessionManager() {     this.deleteInvalidSessions = true;     this.sessionFactory = new SimpleSessionFactory();     this.sessionDAO = new MemorySessionDAO(); }
这里我们看到了,如果不自己注入sessionDAO,defaultWebSessionManager会使用MemorySessionDAO做为默认实现类,这个肯定不是我们想要的,所以这就自己动手实现sessionDAO吧。
public class CustomShiroSessionDAO extends AbstractSessionDAO {private ShiroSessionRepository shiroSessionRepository;public ShiroSessionRepository getShiroSessionRepository() {return shiroSessionRepository;}public void setShiroSessionRepository(ShiroSessionRepository shiroSessionRepository) {this.shiroSessionRepository = shiroSessionRepository;}@Overridepublic void update(Session session) throws UnknownSessionException {getShiroSessionRepository().saveSession(session);}@Overridepublic void delete(Session session) {if (session == null) {LoggerUtil.error(CustomShiroSessionDAO.class,"session can not be null,delete failed");return;}Serializable id = session.getId();if (id != null)getShiroSessionRepository().deleteSession(id);}@Overridepublic Collection<Session> getActiveSessions() {return getShiroSessionRepository().getAllSessions();}@Overrideprotected Serializable doCreate(Session session) {Serializable sessionId = this.generateSessionId(session);this.assignSessionId(session, sessionId);getShiroSessionRepository().saveSession(session);return sessionId;}@Overrideprotected Session doReadSession(Serializable sessionId) {return getShiroSessionRepository().getSession(sessionId);}}
我们自定义CustomShiroSessionDAO继承AbstractSessionDAO,实现对session操作的方法。这里为了便于扩展,我引入了一个接口ShiroSessionRepository,可以用redis、mongoDB等进行实现。


public interface ShiroSessionRepository {void saveSession(Session session);void deleteSession(Serializable sessionId);Session getSession(Serializable sessionId);Collection<Session> getAllSessions();}
这个是我自己redis的ShiroSessionReposotory存储实现类:
public class JedisShiroSessionRepository extends JedisManager implementsShiroSessionRepository {/** * redis session key前缀 */private final String REDIS_SHIRO_SESSION = "shiro-session:";@Autowiredprivate JedisPool jedisPool;@Overrideprotected JedisPool getJedisPool() {return jedisPool;}@Overrideprotected JedisDataType getJedisDataType() {return JedisDataType.SESSION_CACHE;}@Overridepublic void saveSession(Session session) {if (session == null || session.getId() == null) {LoggerUtil.error(JedisShiroSessionRepository.class,"session或者session id为空");return;}byte[] key = SerializeUtil.serialize(getRedisSessionKey(session.getId()));byte[] value = SerializeUtil.serialize(session);Jedis jedis = this.getJedis();try {Long timeOut = session.getTimeout() / 1000;jedis.set(key, value);jedis.expire(key, Integer.parseInt(timeOut.toString()));} catch (JedisException e) {LoggerUtil.error(JedisShiroSessionRepository.class, "保存session失败",e);} finally {this.returnResource(jedis);}}@Overridepublic void deleteSession(Serializable id) {if (id == null) {LoggerUtil.error(JedisShiroSessionRepository.class, "id为空");return;}Jedis jedis = this.getJedis();try {jedis.del(SerializeUtil.serialize(getRedisSessionKey(id)));} catch (JedisException e) {LoggerUtil.error(JedisShiroSessionRepository.class, "删除session失败",e);} finally {this.returnResource(jedis);}}@Overridepublic Session getSession(Serializable id) {if (id == null) {LoggerUtil.error(JedisShiroSessionRepository.class, "id为空");return null;}Session session = null;Jedis jedis = this.getJedis();try {byte[] value = jedis.get(SerializeUtil.serialize(getRedisSessionKey(id)));session = SerializeUtil.deserialize(value, Session.class);} catch (JedisException e) {LoggerUtil.error(JedisShiroSessionRepository.class, "获取id为" + id+ "的session失败", e);} finally {this.returnResource(jedis);}return session;}@Overridepublic Collection<Session> getAllSessions() {Jedis jedis = this.getJedis();Set<Session> sessions = new HashSet<Session>();try {Set<byte[]> byteKeys = jedis.keys(SerializeUtil.serialize(this.REDIS_SHIRO_SESSION + "*"));if (byteKeys != null && byteKeys.size() > 0) {for (byte[] bs : byteKeys) {Session s = SerializeUtil.deserialize(jedis.get(bs),Session.class);sessions.add(s);}}} catch (JedisException e) {LoggerUtil.error(JedisShiroSessionRepository.class,"获取所有session失败", e);} finally {this.returnResource(jedis);}return sessions;}/** * 获取redis中的session key *  * @param sessionId * @return */private String getRedisSessionKey(Serializable sessionId) {return this.REDIS_SHIRO_SESSION + sessionId;}}

这样sessionDAO我们就完成了,下面继续修改我们spring配置文件:

<bean id="defaultWebSessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">    <property name="globalSessionTimeout" value="1200000" />    <property name="sessionDAO" ref="customShiroSessionDAO" /></bean><bean id="customShiroSessionDAO" class="com.xxx.security.shiro.custom.session.CustomShiroSessionDAO">    <property name="shiroSessionRepository" ref="jedisShiroSessionRepository" /></bean><bean id="jedisShiroSessionRepository" class="com.xxx.security.shiro.custom.session.JedisShiroSessionRepository" />



这样第一个问题,session的共享问题我们就解决好了,下一篇介绍另一个问题,cache的共享问题。


源码地址:https://github.com/michaelliuyang/shiro-redis-cluster

原创粉丝点击