Apache Shiro的基本配置和构成这里就不详细说明了,其官网有说明文档,这里仅仅说明集群的解决方案,详细配置:
shiro web config
在最后一行,set了默认的servlet容器实现的sessionManager,sessionManager会管理session的创建、删除等等。如果我们需要让session在集群中共享,就需要替换这个默认的sessionManager。在其官网上原话是这样的:
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>
<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());
- }
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 Sessions
- If 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.
- DefaultWebSessionManager
- To 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.
Native Sessions
If 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.
DefaultWebSessionManager
To 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>
<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();
- }
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;
- }
- @Override
- public void update(Session session) throws UnknownSessionException {
- getShiroSessionRepository().saveSession(session);
- }
- @Override
- public 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);
- }
- @Override
- public Collection<Session> getActiveSessions() {
- return getShiroSessionRepository().getAllSessions();
- }
- @Override
- protected Serializable doCreate(Session session) {
- Serializable sessionId = this.generateSessionId(session);
- this.assignSessionId(session, sessionId);
- getShiroSessionRepository().saveSession(session);
- return sessionId;
- }
- @Override
- protected Session doReadSession(Serializable sessionId) {
- return getShiroSessionRepository().getSession(sessionId);
- }
- }
public class CustomShiroSessionDAO extends AbstractSessionDAO {
private ShiroSessionRepository shiroSessionRepository;
public ShiroSessionRepository getShiroSessionRepository() {
return shiroSessionRepository;
}
public void setShiroSessionRepository(
ShiroSessionRepository shiroSessionRepository) {
this.shiroSessionRepository = shiroSessionRepository;
}
@Override
public void update(Session session) throws UnknownSessionException {
getShiroSessionRepository().saveSession(session);
}
@Override
public 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);
}
@Override
public Collection<Session> getActiveSessions() {
return getShiroSessionRepository().getAllSessions();
}
@Override
protected Serializable doCreate(Session session) {
Serializable sessionId = this.generateSessionId(session);
this.assignSessionId(session, sessionId);
getShiroSessionRepository().saveSession(session);
return sessionId;
}
@Override
protected 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();
- }
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 implements
- ShiroSessionRepository {
- /**
- * redis session key前缀
- */
- private final String REDIS_SHIRO_SESSION = "shiro-session:";
- @Autowired
- private JedisPool jedisPool;
- @Override
- protected JedisPool getJedisPool() {
- return jedisPool;
- }
- @Override
- protected JedisDataType getJedisDataType() {
- return JedisDataType.SESSION_CACHE;
- }
- @Override
- public 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);
- }
- }
- @Override
- public 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);
- }
- }
- @Override
- public 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;
- }
- @Override
- public 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;
- }
- }
public class JedisShiroSessionRepository extends JedisManager implements
ShiroSessionRepository {
/**
* redis session key前缀
*/
private final String REDIS_SHIRO_SESSION = "shiro-session:";
@Autowired
private JedisPool jedisPool;
@Override
protected JedisPool getJedisPool() {
return jedisPool;
}
@Override
protected JedisDataType getJedisDataType() {
return JedisDataType.SESSION_CACHE;
}
@Override
public 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);
}
}
@Override
public 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);
}
}
@Override
public 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;
}
@Override
public 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" />
<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的共享问题。