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 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>
我们继续看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();
- }
- 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 interface ShiroSessionRepository {
- void saveSession(Session session);
- void deleteSession(Serializable sessionId);
- Session getSession(Serializable sessionId);
- Collection<Session> getAllSessions();
- }
- 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" />
这样第一个问题,session的共享问题我们就解决好了,下一篇介绍另一个问题,cache的共享问题。
源码地址:https://github.com/michaelliuyang/shiro-redis-cluster