shiro+redis+springMvc整合配置及说明

本文介绍了如何将Shiro安全框架与Redis缓存服务相结合,并利用SpringMVC进行整合。主要涉及了PermissionManagerLocalImpl、AccountManagerLocalImpl、RestAuthRealm、RestUrlChainManager、RestAuthShiroFilter、JedisPoolManager、RedisCacheManager和RedisSessionDAO等关键组件的配置和作用,特别是通过Redis实现Shiro的CacheManager和SessionDAO,实现了高效的安全管理与会话持久化。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

技术背景:shiro安全框架,redis作缓存,再整合spring。

1、配置web.xml

    <filter>
        <filter-name>ShiroFilter</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
    </filter>

    <filter-mapping>
        <filter-name>ShiroFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
2、配置spring-shiro.xml的配置文件

    <!--shiro配置--> 
    <!--securityManager是shiro核心部分-->
    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
         <property name="sessionManager" ref="webSessionManager" />
         <property name="realm" ref="restfulAuthRealm"/>
         <property name="rememberMeManager.cookie.name" value="rememberMe"/>
    	 <property name="rememberMeManager.cookie.maxAge" value="${rememberMeManager.cookie.maxAge}"/>
    </bean>
    <!--配置shiro的sessionManager-->
    <bean id="webSessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
    	 <property name="sessionDAO" ref="redisSessionDAO"></property>
    </bean>
    <!--权限操作bean-->
    <bean id="permissionManager" class="com.securityframework.local.PermissionManagerLocalImpl" />
    <!--账号操作bean-->
    <bean id="accountManagerImpl" class="com.securityframework.local.AccountManagerLocalImpl"/>
    <!--自定义realm-->
    <bean id ="restfulAuthRealm" class="com.isoftstone.securityframework.restful.client.web.shiro.realm.RestAuthRealm">
	    <property name="accountManagerImpl" ref="accountManagerImpl"></property>
	    <property name="permissionManagerImpl" ref="permissionManager"></property>
	    <property name="cacheManager" ref="redisCacheManager"></property>
            <property name="platformLabel">
		 <value>${platformLabel}</value>
	    </property>
     </bean>     
     <!--动态获取filterchaindefinitions,此处与下面ShiroFilter bean所引用的类对应-->
     <bean id="systemUrlChainManager" class="com.securityframework.restful.client.web.shiro.mgt.RestUrlChainManager">
	      <property name="permissionManager" ref="permissionManager"></property>
		 <property name="platformLabel">
			 <value>${platformLabel}</value>
		 </property>
		<property name="systemLabel">
 			<value>${systemLabel}</value>
		</property>
      </bean>
      <!--与web.xml中配置的filter同名,它对应的类原本是<code class="xml string">org.apache.shiro.spring.web.ShiroFilterFactoryBean,</code>-->
      <!--这里为了动态获取filterchaindefinitions改写了<code class="xml string">ShiroFilterFactoryBean类,它们的作用是一样的</code>--> 
      <bean id="ShiroFilter" class="com.securityframework.restful.client.web.shiro.filter.RestAuthShiroFilter">
        <property name="urlChainManager" ref="systemUrlChainManager" />
        <property name="securityManager" ref="securityManager"/>
        <property name="loginUrl" value="../../res/user/login.html"/>
        <property name="unauthorizedUrl" value="/html/413.html"/>
        <property name="filters">
            <util:map>
                <entry key="authc">
                    <bean class="org.apache.shiro.web.filter.authc.PassThruAuthenticationFilter"/>
                </entry>
            </util:map>
        </property>                      
        <property name="filterChainDefinitions">
            <value>
            	/images/** =anon
            	/help/** =anon
            	/css/** = anon
            	/easyui/** =anon
            	/javascript/** =anon
            	/commons/** =anon
            	/jsplugin/** =anon
            	/ueditor/** =anon
            	/html/** =anon          	
            	/index.html = anon
            	/ = anon
             	/** = user
            </value>
         </property>
    </bean>
<span><span class="comments">    <!-- 保证实现了Shiro内部lifecycle函数的bean执行 --></span><span></span></span>
<pre name="code" class="java">    <bean id="lifecycleBeanPostProcessor"   class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/> 
     
     <!--redis配置-->
<!-- basic jedis pool configuration -->
    <bean id="basicPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
        <property name="maxActive" value="${redis.pool.maxActive}" />  
        <property name="maxIdle" value="${redis.pool.maxIdle}" />  
        <property name="maxWait" value="${redis.pool.maxWaitTime}" />  
        <property name="testOnBorrow" value="${redis.pool.testOnBorrow}" />  
    </bean>
    <!-- JedisPool  configuration-->
    <bean id="jedisPool" class="redis.clients.jedis.JedisPool">  
        <constructor-arg index="0" ref="basicPoolConfig" />  
        <constructor-arg index="1" value="${redis.host.ip}" />  
        <constructor-arg index="2" value="${redis.host.port}" />  
    </bean>  
    <!-- JedisPool manager -->
    <bean id="jedisPoolManager" class="com.isoftstone.securityframework.support.redis.JedisPoolManager">
        <property name="jedisPool" ref="jedisPool"></property>
    </bean>
    <!--redisCacheManager要实现org.apache.shiro.cache.CacheManager接口,让shiro使用redis的缓存-->
    <bean id="redisCacheManager" class="com.securityframework.support.redis.RedisCacheManager">
        <property name="redisManager" ref="jedisPoolManager"></property>
    </bean>
    <!-- Redis session dao -->
<!--redisSessionDAO继承实现了org.apache.shiro.session.mgt.eis.SessionDAO的AbstractSessionDAO-->
    <bean id="redisSessionDAO" class="com.securityframework.support.redis.RedisSessionDAO">
        <property name="redisManager" ref="jedisPoolManager"></property>
        <property name="expire" value="${shiro.session.timeout}"></property>
    </bean>

 3、上述bean对应的类(我们自己重写的) 

  • com.securityframework.local.PermissionManagerLocalImpl
  • com.securityframework.local.AccountManagerLocalImpl
  • com.securityframework.restful.client.web.shiro.realm.RestAuthRealm
  • com.securityframework.restful.client.web.shiro.mgt.RestUrlChainManager
  • com.securityframework.restful.client.web.shiro.filter.RestAuthShiroFilter
  • com.securityframework.support.redis.JedisPoolManager
  • com.securityframework.support.redis.RedisCacheManager
  • com.securityframework.support.redis.RedisSessionDAO

     (1)com.securityframework.local.PermissionManagerLocalImpl

         对权限信息的增删改查

     (2)com.securityframework.local.AccountManagerLocalImpl

         对账号信息的增删改查

     (3)com.securityframework.restful.client.web.shiro.realm.RestAuthRealm

         在shiro学习和使用实例(2)——登陆认证和授权 有详细解释

     (4)com.securityframework.restful.client.web.shiro.mgt.RestUrlChainManager        

public class RestUrlChainManager {
	PermissionManager permissionManager; 
	
	private String platformLabel ;
	
	private String systemLabel;
	
	public Map<String,String> buildChainMap(){
		
		Map<String,String> p = new HashMap<String,String>();
		//获取权限信息,以便获得每个权限下面的资源
		List<com.isoftstone.securityframework.api.Permission> perms = permissionManager.findSubsystemPermission(platformLabel, systemLabel);
		
		if(null != perms && perms.size() > 0){
			System.out.println(" SystemUrlChainManager.buildChainMap----- STATUS_OK -----");
		}
		if (null != perms){
			String value ;
			for (int i = 0; i < perms.size(); i++) {
				value = "";
				com.isoftstone.securityframework.api.Permission perm = perms.get(i);
				if(i == 0){
					value = "authc," + perm.getPermissionName();
				}else{
					value = perm.getPermissionName();
				}
				List<com.isoftstone.securityframework.api.Resource> resources = perm.getResources();
				
				if(null != resources){
					for (int j = 0; j < resources.size(); j++) {
						com.isoftstone.securityframework.api.Resource resource = resources.get(j);
                                                //格式{'mvc/web/usermgt/add.json','perms[com.securitycenter.user:ADD]'},它
                                                //对应spring-shiro.xml配置文件中ShiroFilter bean下的filterChainDefinitions
                                                p.put(resource.getRes(), String.format("perms[%s]", value));
					}
				}
			}
		}
		return p;
	}

        /**
         *  setter和getter方法省略
         **/
 }
      RestUrlChainManager类中的buildChainMap方法是从数据库获取资源,组成动态的filterChainDefinitions,以便系统对所有的资源请求的拦截都可以动态可配置

     (5)com.securityframework.restful.client.web.shiro.filter.RestAuthShiroFilter

public Map<String,String> getChainFilterMap(){
		return getUrlChainManager().buildChainMap();
	}
public void setFilterChainDefinitionMap(Map<String, String> filterChainDefinitionMap) {
        Map<String, String> allFilterChainDefinitionMap = getFilterChainDefinitionMap();
        if (CollectionUtils.isEmpty(allFilterChainDefinitionMap)){
            this.filterChainDefinitionMap = filterChainDefinitionMap;
        }else{
            //add custom chain definition
            allFilterChainDefinitionMap.putAll(filterChainDefinitionMap);
        }
                
    }
public void setFilterChainDefinitions(String definitions) {
        Ini ini = new Ini();
        ini.load(definitions);
        //did they explicitly state a 'urls' section?  Not necessary, but just in case:
        Ini.Section section = ini.getSection(IniFilterChainResolverFactory.URLS);
        if (CollectionUtils.isEmpty(section)) {
            //no urls section.  Since this _is_ a urls chain definition property, just assume the
            //default section contains only the definitions:
            section = ini.getSection(Ini.DEFAULT_SECTION_NAME);
        }
        setFilterChainDefinitionMap(section);
        
        //add custom path filter map 
        try{
            Map<String,String> permFilterMap = this.getChainFilterMap();
            if(!CollectionUtils.isEmpty(permFilterMap)){
                setFilterChainDefinitionMap(permFilterMap);
            }
        }catch(Exception ex){
            log.error("Load custom path filters to shiro filter failure.");
            ex.printStackTrace();
        }
        
    }
      RestAuthShiroFilter其实就是使用的 org.apache.shiro.spring.web.ShiroFilterFactoryBean,里面的方法完全一样,只是为了动态获取filterChainDefinitions在 ShiroFilterFactoryBean类的基础上,增改了上述三个方法。实现的目的就是将RestUrlChainManager组装的{资源,权限}map加入到filterChainDefinitions中,实现拦截。从而能够动态的维护shiro拦截器拦截的内容。

     (6)com.securityframework.support.redis.JedisPoolManager

/**
 * JedisPool 管理类 
 * 用于单个redis 集群, 每个redis集群由master-salve组成
 */
public class JedisPoolManager {

	private static Log log = LogFactory.getLog(JedisPoolManager.class);

	private JedisPool jedisPool;
	/**
	 * redis的List集合 ,向key这个list添加元素
	 */
	public long rpush(String key, String string) {
		Jedis jedis = null;
		try {
			jedis = jedisPool.getResource();
			long ret = jedis.rpush(key, string);
			jedisPool.returnResource(jedis);
			return ret;
		} catch (Exception e) {
			log.error(e);
			if (jedis != null) {
				jedisPool.returnBrokenResource(jedis);
			}
			throw new JedisException(e);
		}
	}
	/**
	 * 获取key这个List,从第几个元素到第几个元素 LRANGE key start
	 * stop返回列表key中指定区间内的元素,区间以偏移量start和stop指定。
	 * 下标(index)参数start和stop都以0为底,也就是说,以0表示列表的第一个元素,以1表示列表的第二个元素,以此类推。
	 * 也可以使用负数下标,以-1表示列表的最后一个元素,-2表示列表的倒数第二个元素,以此类推。
	 */
	public List<String> lrange(String key, long start, long end) {
		Jedis jedis = null;
		try {
			jedis = jedisPool.getResource();
			List<String> ret = jedis.lrange(key, start, end);
			jedisPool.returnResource(jedis);
			return ret;
		} catch (Exception e) {
			log.error(e);
			if (jedis != null) {
				jedisPool.returnBrokenResource(jedis);
			}
			throw new JedisException(e);
		}
	}
	/**
	 * 将哈希表key中的域field的值设为value。
	 */
	public void hset(String key, String field, String value) {
		Jedis jedis = null;
		try {
			jedis = jedisPool.getResource();
			jedis.hset(key, field, value);
			jedisPool.returnResource(jedis);
		} catch (Exception e) {
			log.error(e);
			if (jedis != null) {
				jedisPool.returnBrokenResource(jedis);
			}
			throw new JedisException(e);
		}
	}
	/**
	 * 向key赋值
	 */
	public void set(String key, String value) {
		Jedis jedis = null;
		try {
			jedis = jedisPool.getResource();
			jedis.set(key, value);
			jedisPool.returnResource(jedis);
		} catch (Exception e) {
			log.error(e);
			if (jedis != null) {
				jedisPool.returnBrokenResource(jedis);
			}
			throw new JedisException(e);
		}
	}
	/**
	 * 向key赋值
	 */
	public void set(byte[] key, byte[] value) {
		Jedis jedis = null;
		try {
			jedis = jedisPool.getResource();
			jedis.set(key, value);
			jedisPool.returnResource(jedis);
		} catch (Exception e) {
			log.error(e);
			if (jedis != null) {
				jedisPool.returnBrokenResource(jedis);
			}
			throw new JedisException(e);
		}
	}
	/**
	 * 获取key的值
	 */
	public String get(String key) {
		Jedis jedis = null;
		try {
			jedis = jedisPool.getResource();
			String value = jedis.get(key);
			jedisPool.returnResource(jedis);
			return value;
		} catch (Exception e) {
			log.error(e);
			if (jedis != null) {
				jedisPool.returnBrokenResource(jedis);
			}
			throw new JedisException(e);
		}
	}
	/**
	 * 获取key的值
	 */
	public byte[] get(byte[] key) {
		Jedis jedis = null;
		try {
			jedis = jedisPool.getResource();
			byte[] value = jedis.get(key);
			jedisPool.returnResource(jedis);
			return value;
		} catch (Exception e) {
			log.error(e);
			if (jedis != null) {
				jedisPool.returnBrokenResource(jedis);
			}
			throw new JedisException(e);
		}

	}
	/**
	 * 将多个field - value(域-值)对设置到哈希表key中。
	 */
	public void hmset(String key, Map<String, String> map) {
		Jedis jedis = null;
		try {
			 jedis = jedisPool.getResource();
			jedis.hmset(key, map);
			jedisPool.returnResource(jedis);
		} catch (Exception e) {
			log.error(e);
			if (jedis != null) {
				jedisPool.returnBrokenResource(jedis);
			}
			throw new JedisException(e);
		}
	}
	/**
	  * 给key赋值,并生命周期设置为seconds
	 */
	public void setex(String key, int seconds, String value) {
		Jedis jedis = null;
		try {
			 jedis = jedisPool.getResource();
			jedis.setex(key, seconds, value);
			jedisPool.returnResource(jedis);
		 } catch (Exception e) {
			log.error(e);
			if (jedis != null) {
 				jedisPool.returnBrokenResource(jedis);
 			}
			throw new JedisException(e);
		}
	}
	/**
	 * 给key赋值,并生命周期设置为seconds
	 */
	public byte[] setex(byte[] key, byte[] value, int seconds) {
		Jedis jedis = null;
		try {
			 jedis = jedisPool.getResource();
			jedis.setex(key, seconds, value);
			jedisPool.returnResource(jedis);
			return value;
		} catch (Exception e) {
			log.error(e);
 			if (jedis != null) {
				jedisPool.returnBrokenResource(jedis);
			}
			throw new JedisException(e);
		}
		

	}
	/**
	 * 为给定key设置生命周期
	 */
	public void expire(String key, int seconds) {
		Jedis jedis = null;
		try {
			 jedis = jedisPool.getResource();
			jedis.expire(key, seconds);
			jedisPool.returnResource(jedis);
		} catch (Exception e) {
			log.error(e);
			if (jedis != null) {
				jedisPool.returnBrokenResource(jedis);
			}
			 throw new JedisException(e);
		}
	}
	/**
	 * 检查key是否存在
	 */
	public boolean exists(String key) {
		Jedis jedis = null;
		try {
			jedis = jedisPool.getResource();
			boolean bool = jedis.exists(key);
			jedisPool.returnResource(jedis);
			return bool;
		} catch (Exception e) {
			log.error(e);
			if (jedis != null) {
				jedisPool.returnBrokenResource(jedis);
			}
			throw new JedisException(e);
		}
	}
        /**
	 * 检查key是否存在
	 */
	public boolean exists(byte[] key) {
		Jedis jedis = null;
		try {
			jedis = jedisPool.getResource();
		    Set<byte[]> hashSet = jedis.keys(key);
		    jedisPool.returnResource(jedis);
		    if (null != hashSet && hashSet.size() >0 ){
		    	return true;
		    }else{
		    	return false;
		    }

		} catch (Exception e) {
			 log.error(e);
			if (jedis != null) {
				jedisPool.returnBrokenResource(jedis);
			}
			throw new JedisException(e);
		}
	}
	/**
	 * 返回key值的类型 none(key不存在),string(字符串),list(列表),set(集合),zset(有序集),hash(哈希表)
	 */
	public String type(String key) {
		Jedis jedis = null;
		try {
			jedis = jedisPool.getResource();
			String type = jedis.type(key);
			jedisPool.returnResource(jedis);
			return type;
 		} catch (Exception e) {
			log.error(e);
			if (jedis != null) {
				jedisPool.returnBrokenResource(jedis);
			}
			throw new JedisException(e);
		}
	}
	/**
	 * 从哈希表key中获取field的value
	 */
	public String hget(String key, String field) {
		Jedis jedis = null;
		try {
			 jedis = jedisPool.getResource();
			String value = jedis.hget(key, field);
			jedisPool.returnResource(jedis);
			return value;
 		} catch (Exception e) {
			log.error(e);
			if (jedis != null) {
				jedisPool.returnBrokenResource(jedis);
			}
			throw new JedisException(e);
		}
	}
	/**
	 * 返回哈希表key中,所有的域和值
	 */
	public Map<String, String> hgetAll(String key) {
		Jedis jedis = null;
		try {
			 jedis = jedisPool.getResource();
			Map<String, String> map = jedis.hgetAll(key);
			jedisPool.returnResource(jedis);
			return map;
		} catch (Exception e) {
			 log.error(e);
			if (jedis != null) {
				jedisPool.returnBrokenResource(jedis);
			}
			throw new JedisException(e);
		}

	}
	/**
	 * 返回哈希表key中,所有的域和值
	 */
	public Set<?> smembers(String key) {
		Jedis jedis = null;
		try {
			jedis = jedisPool.getResource();
			 Set<?> set = jedis.smembers(key);
			jedisPool.returnResource(jedis);
 			return set;
 		} catch (Exception e) {
			log.error(e);
			if (jedis != null) {
				jedisPool.returnBrokenResource(jedis);
			}
			 throw new JedisException(e);
		}
	}	
	/**
	 * 返回匹配的 keys 列表
	 */
	 public Set<byte[]> keys(String pattern) {
 		Jedis jedis = null;
 		try {
			jedis = jedisPool.getResource();
			Set<byte[]> keys = jedis.keys(pattern.getBytes());
			jedisPool.returnResource(jedis);
			return keys;
 		} catch (Exception e) {
			log.error(e);
			if (jedis != null) {
				jedisPool.returnBrokenResource(jedis);
			}
			throw new JedisException(e);
		}

	}
	/**
	 * 移除set集合中的member元素
	 */
	public void delSetObj(String key, String field) {
		Jedis jedis = null;
		try {
			jedis = jedisPool.getResource();
			jedis.srem(key, field);
			jedisPool.returnResource(jedis);
		} catch (Exception e) {
 			log.error(e);
			if (jedis != null) {
				jedisPool.returnBrokenResource(jedis);
			}
			throw new JedisException(e);
		}
	}
	/**
	 * 删除元素
	 */
	public void del(byte[] key) {
		 Jedis jedis = null;
 		try {
			jedis = jedisPool.getResource();
			jedis.del(key);
 			jedisPool.returnResource(jedis);
 		} catch (Exception e) {
			log.error(e);
			if (jedis != null) {
				jedisPool.returnBrokenResource(jedis);
			}
			throw new JedisException(e);
		}
	}
	/**
	 * 判断member元素是否是集合key的成员。是(true),否则(false)
	 */
	public boolean isNotField(String key, String field) {
		Jedis jedis = null;
		try {
			jedis = jedisPool.getResource();
			 boolean bool = jedis.sismember(key, field);
			jedisPool.returnResource(jedis);
			 return bool;
		} catch (Exception e) {
			 log.error(e);
			if (jedis != null) {
				jedisPool.returnBrokenResource(jedis);
			}
			throw new JedisException(e);
		}
	}
	/**
	 * 如果key已经存在并且是一个字符串,将value追加到key原来的值之后
	 */
	public void append(String key, String value) {
		Jedis jedis = null;
 		try {
 			jedis = jedisPool.getResource();
			jedis.append(key, value);
			jedisPool.returnResource(jedis);
		} catch (Exception e) {
			log.error(e);
			if (jedis != null) {
				jedisPool.returnBrokenResource(jedis);
			}
 			throw new JedisException(e);
		}
	}
	/**
	 * 清空当前的redis 库
	 */
	public void flushDB() {
		Jedis jedis = null;
		try {
			jedis = jedisPool.getResource();
 			jedis.flushDB();
 			jedisPool.returnResource(jedis);
		} catch (Exception e) {
			log.error(e);
			if (jedis != null) {
				jedisPool.returnBrokenResource(jedis);
			}
			throw new JedisException(e);
		}

	 }
	/**
	 * 返回当前redis库所存储数据的大小
	 */
	public Long dbSize() {
		
		Long dbSize = 0L;
		
		Jedis jedis = null;
		try {
			jedis = jedisPool.getResource();
			 jedis.dbSize();
 			jedisPool.returnResource(jedis);
			return dbSize;
		} catch (Exception e) {
			log.error(e);
			if (jedis != null) {
				jedisPool.returnBrokenResource(jedis);
			}
			throw new JedisException(e);
		}

	}
	/**
	 * 关闭 Redis
	 */
	public void destory() {
		jedisPool.destroy();
	}

       public JedisPool getJedisPool() {
		return jedisPool;
	}
  	public void setJedisPool(JedisPool jedisPool) {
		this.jedisPool = jedisPool;
	}
}
JedisPool 管理类 ,对redis的java客户端jedis的封装

     (7)com.securityframework.support.redis.RedisCacheManager

public class RedisCacheManager implements CacheManager {

	private static final Logger logger = LoggerFactory.getLogger(RedisCacheManager.class);

	// fast lookup by name map
	private final ConcurrentMap<String, Cache> caches = new ConcurrentHashMap<String, Cache>();

	private JedisPoolManager redisManager;

	public <K, V> Cache<K, V> getCache(String name) throws CacheException {
		logger.debug("获取名称为: " + name + " 的RedisCache实例");
		Cache c = caches.get(name);
		if (c == null) {
			c = new RedisCache<K, V>(redisManager);
			caches.put(name, c);
		}
		return c;
	}
        //setter和getter方法省略
}
     RedisCacheManager实现org.apache.shiro.cache.CacheManager接口,是shiro使用redis的缓存

     (8)com.securityframework.support.redis.RedisSessionDAO

public class RedisSessionDAO extends AbstractSessionDAO {

	private static Logger logger = LoggerFactory.getLogger(RedisSessionDAO.class);
	/**
	 * shiro-redis的session对象前缀
	 */
	private final String SHIRO_REDIS_SESSION_PRE = "shiro_redis_session:";
	
	/**
	 * 存放uid的对象前缀
	 */
	private final String SHIRO_SHESSIONID_PRE = "shiro_sessionid:";
	
	/**
	 * 存放uid 当前状态的前缀
	 */
	private final String UID_PRE = "uid:";
	
	/**
	 * 存放用户权限的前缀
	 */
	private final String PERMISSION_PRE ="permission:";

       private JedisPoolManager redisManager;

	private long expire=180000;
	
	@Override
	public void update(Session session) throws UnknownSessionException {
		this.saveSession(session);
	}

	/**
	 * save session
	 * 
	 * @param session
	 * @throws UnknownSessionException
	 */
	private void saveSession(Session session) throws UnknownSessionException {
		if (session == null || session.getId() == null) {
			logger.error("session or session id is null");
			return;
		}
	    session.setTimeout(expire);
	    Long redisExpire = expire/1000;
	    int timeout = redisExpire.intValue();
	    	JedisPool jedisPool = this.redisManager.getJedisPool();
		Jedis jedis = null;
		try {
			jedis =  jedisPool.getResource();			
			//保存用户会话
			jedis.setex(this.getByteKey(this.SHIRO_REDIS_SESSION_PRE,session.getId()), timeout, SerializeUtils.serialize(session));
			String uid = this.getUserId(session);
			if (null != uid && !"".equals(uid)){
				//保存用户会话对应的UID
				jedis.setex(this.getByteKey(this.SHIRO_SHESSIONID_PRE,session.getId()),timeout, uid.getBytes());
				//保存在线UID
				jedis.setex(this.getByteKey(this.UID_PRE,uid), timeout,"online".getBytes());
			}
            jedisPool.returnResource(jedis);
		}catch(Exception ex){
			if (jedis != null) {
				jedisPool.returnBrokenResource(jedis);
			}
			throw new JedisException(ex);
		}
		
		
	}
	
	@Override
	public void delete(Session session) {
		if (session == null || session.getId() == null) {
			logger.error("session or session id is null");
			return;
		}
		
		JedisPool jedisPool = this.redisManager.getJedisPool();
		Jedis jedis = null;
		try {
			jedis =  jedisPool.getResource();
			
			//删除用户会话
			jedis.del(this.getByteKey(this.SHIRO_REDIS_SESSION_PRE,session.getId()));
			//获取缓存的用户会话对应的UID
			byte[] uid = jedis.get(this.getByteKey(this.SHIRO_SHESSIONID_PRE,session.getId()));
			
			//删除用户会话对应的UID
			jedis.del(this.getByteKey(this.SHIRO_SHESSIONID_PRE,session.getId()));
			
			//删除在线UID
			jedis.del(this.getByteKey(this.UID_PRE,new String(uid)));
			
			//删除用户缓存的权限
			jedis.del(this.getByteKey(this.PERMISSION_PRE, new String(uid)));
			
            jedisPool.returnResource(jedis);
		}catch(Exception ex){
			if (jedis != null) {
				jedisPool.returnBrokenResource(jedis);
			}
			throw new JedisException(ex);
		}
	
 	}

        @Override
	public Collection<Session> getActiveSessions() {
		Set<Session> sessions = new HashSet<Session>();

		Set<byte[]> keys = redisManager
				.keys(this.SHIRO_REDIS_SESSION_PRE + "*");
		if (keys != null && keys.size() > 0) {
			for (byte[] key : keys) {
				Session s = (Session) SerializeUtils.deserialize(redisManager
						.get(key));
				sessions.add(s);
			}
		}

		return sessions;
	}
	
	public boolean isOnLine(String uid){
		
		Set<byte[]>keys = redisManager.keys(this.UID_PRE + uid);
		if (keys != null && keys.size() > 0){
		  return true;
		}
		return false;
	}

	@Override
	protected Serializable doCreate(Session session) {
		Serializable sessionId = this.generateSessionId(session);
		this.assignSessionId(session, sessionId);
		this.saveSession(session);
		return sessionId;
	}

	@Override
	protected Session doReadSession(Serializable sessionId) {
		if (sessionId == null) {
			logger.error("session id is null");
			return null;
		}
		
		logger.debug("#####Redis.SessionId=" + new String(getByteKey(this.SHIRO_REDIS_SESSION_PRE,sessionId)));
		
		Session s = (Session) SerializeUtils.deserialize(redisManager.get(this
				.getByteKey(this.SHIRO_REDIS_SESSION_PRE,sessionId)));
		return s;
	}
	
	/**
	 * 获得byte[]型的key
	 * 
	 * @param key
	 * @return
	 */
	private byte[] getByteKey(String preKey,Serializable sessionId) {
		String key = preKey + sessionId;
		return key.getBytes();
	
	}
	
	/**
	 * 获取用户唯一标识
	 * @param session
	 * @return
	 */
	private String getUserId(Session session){
		SimplePrincipalCollection pricipal = (SimplePrincipalCollection)session.getAttribute("org.apache.shiro.subject.support.DefaultSubjectContext_PRINCIPALS_SESSION_KEY");
        if (null != pricipal){
        	Account account = ((Account) pricipal.getPrimaryPrincipal());
        	return account.getAccountId();
        }
        return  "";
	}
	//setter和getter省略

}
    RedisSessionDAO继承org.apache.shiro.session.mgt.eis.AbstractSessionDAO,而AbstractSessionDAO是实现了org.apache.shiro.session.mgt.eis.SessionDAO接口的。

    通过redis实现shiro的CacheManager接口,继承AbstractSessionDAO,实现shiro和redis的整合。


本项目详细介绍请看:http://www.sojson.com/shiro (强烈推荐) Demo已经部署到线上,地址是http://shiro.itboy.net, 管理员帐号:admin,密码:sojson.com 如果密码错误,请用sojson。 PS:你可以注册自己的帐号,然后用管理员赋权限给你自己的帐号,但是,每20分钟会把数据初始化一次。建议自己下载源码,让Demo跑起来,然后跑的更快,有问题加群解决。 声明: 本人提供这个Shiro + SpringMvc + Mybatis + Redis 的Demo 本着学习的态度,如果有欠缺和不足的地方,给予指正,并且多多包涵。 “去其糟粕取其精华”。如果觉得写的好的地方就给个赞,写的不好的地方,也请多多包涵。 使用过程: 1.创建数据库。 创建语句 :tables.sql 2.插入初始化数据 插入初始化数据:init.data.sql 3.运行。 管理员帐号:admin 密码:sojson ps:定时任务的sql会把密码改变为sojson.com 新版本说明:http://www.sojson.com/blog/164.html 和 http://www.sojson.com/blog/165.html 主要解决是之前说的问题:Shiro 教程,关于最近反应的相关异常问题,解决方法合集。 项目在本页面的附件中提取。 一、Cache配置修改。 配置文件(spring-cache.xml )中已经修改为如下配置: <!-- redis 配置,也可以把配置挪到properties配置文件中,再读取 --> <!-- 这种 arguments 构造的方式,之前配置有缺点。 这里之前的配置有问题,因为参数类型不一致,有时候jar和环境的问题,导致参数根据index对应,会处理问题, 理论上加另一个 name,就可以解决,现在把name 和type都加上,更保险。 --> 二、登录获取上一个URL地址报错。 当没有获取到退出前的request ,为null 的时候会报错。在(UserLoginController.java )135行处有所修改。 /** * shiro 获取登录之前的地址 * 之前0.1版本这个没判断空。 */ SavedRequest savedRequest = WebUtils.getSavedRequest(request); String url = null ; if(null != savedRequest){ url = savedRequest.getRequestUrl(); } /** * 我们平常用的获取上一个请求的方式,在Session不一致的情况下是获取不到的 * String url = (String) request.getAttribute(WebUtils.FORWARD_REQUEST_URI_ATTRIBUTE); */ 三、删除了配置文件中的cookie写入域的问题。 在配置文件里(spring-shiro.xml )中的配置有所修改。 <!-- 会话Cookie模板 --> <!--cookie的name,我故意取名叫xxxxbaidu --> <!--cookie的有效时间 --> <!-- 配置存储Session Cookie的domain为 一级域名 --> 上面配置是去掉了 Session 的存储Key 的作用域,之前设置的.itboy.net ,是写到当前域名的 一级域名 下,这样就可以做到N 个 二级域名 下,三级、四级....下 Session 都是共享的。 <!-- 用户信息记住我功能的相关配置 --> <!-- 配置存储rememberMe Cookie的domain为 一级域名 --> <!-- 30天时间,记住我30天 --> 记住我登录的信息配置。和上面配置是一样的道理,可以在相同 一级域名 下的所有域名都可以获取到登录的信息。 四、简单实现了单个帐号只能在一处登录。 我们在其他的系统中可以看到,单个帐号只允许一人使用,在A处登录了,B处再登录,那A处就被踢出了。如下图所示。 但是此功能不是很完美,当A处被踢出后,再重新登录,这时候B处反应有点慢,具体我还没看,因为是之前加的功能,现在凌晨了,下次我有空再瞧瞧,同学你也可以看看,解决了和我说一声,我把功能修复。 五、修复功能(BUG) 1.修复权限添加功能BUG。 之前功能有问题,每当添加一个权限的时候,默认都给角色为“管理员”的角色默认添加当前新添加的权限。这样达到管理员的权限永远是最大的。由于代码有BUG ,导致所有权限删除了。现已修复。 2.修复项目只能部署到Root目录下的问题。 问题描述:之前项目只能部署到Root 下才能正常运行,目前已经修复,可以带项目路径进行访问了,之前只能这样访问,http://localhost:8080 而不能http://localhost:8080/shiro.demo/ 访问,目前是可以了。 解决方案:在 FreeMarkerViewExtend.java 33行处 增加了BasePath ,通过BasePath 来控制请求目录,在 Freemarker 中可以自由使用,而 JSP 中是直接在 JSP 中获取BasePath 使用。 解决后遗症:因为我们的权限是通过URL 来控制的,那么增加了项目的目录,导致权限不能正确的判断,再加上我们的项目名称(目录)可以自定义,导致更不好判断。 后遗症解决方案:PermissionFilter.java 50行处 解决了这个问题,详情请看代码和注释,其实就是replace 了一下。 HttpServletRequest httpRequest = ((HttpServletRequest)request); /** * 此处是改版后,为了兼容项目不需要部署到root下,也可以正常运行,但是权限没设置目前必须到root 的URI, * 原因:如果你把这个项目叫 ShiroDemo,那么路径就是 /ShiroDemo/xxxx.shtml ,那另外一个人使用,又叫Shiro_Demo,那么就要这么控制/Shiro_Demo/xxxx.shtml * 理解了吗? * 所以这里替换了一下,使用根目录开始的URI */ String uri = httpRequest.getRequestURI();//获取URI String basePath = httpRequest.getContextPath();//获取basePath if(null != uri && uri.startsWith(basePath)){ uri = uri.replace(basePath, ""); } 3.项目启动的时候报错,关于JNDI的错误提示。 其实也不是错,但是看着不舒服,所以还得解决这个问题。解决这个问题需要在web.xml 中的开始部位加入以下代码。 spring.profiles.active dev spring.profiles.default dev spring.liveBeansView.mbeanDomain dev 4.项目Maven打包问题。 打包的时候,不同版本的 Eclipse 还有IDEA 会有打包打不进去Mapper.xml 文件,这个时候要加如下代码(群里同学提供的)。 src/main/java **/*.properties **/*.xml false 在 标签内加入即可,如果还是不能解决,那么请你加群(改名后)说明你的问题,有人会回答你。 5.Tomcat7以上在访问JSP页面的时候,提示JSTL错误。 这个错误是因为Tomcat7 中没有 JSTL 的jar包,现在已经在项目pom.xml 中增加了如下 jar 的引入管理。 javax.servlet jstl 1.2 javax.servlet jsp-api 2.0 provided 如果还是不能解决问题,请在官方群(群号:259217951)内搜索“jstl” 如图下载依赖包。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值