Java使用reids,以及redis与shiro集成
什么是redis:redis是一个key-value存储系统。和Memcached类似,它支持存储的value类型相对更多,包括string(字符串)、list(链表)、set(集合)、zset(sorted set --有序集合)和hash(哈希类型)。这些数据类型都支持push/pop、add/remove及取交集并集和差集及更丰富的操作,而且这些操作都是原子性的。在此基础上,redis支持各种不同方式的排序。与memcached一样,为了保证效率,数据都是缓存在内存中。区别的是redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步。(我也是百度的,个人理解reids就是个比较轻量级的key——value模式的数据库,这个数据库的存储性能很好,可以用来代替缓存实现的很多功能。而且这个redis数据库是第三方独立的服务,可以用在负载均衡情况下多个服务器,多个web容器之间公用数据的缓存。)
要使用reids,首先可以到官网reids的jar包和redis。
然后把redis的jar包导入到项目中。
我用的版本是2.1.0. 系统是32位的,所以我解压了32位的redis服务。
打开以后使用里面的redis-servier。exe来启动redis服务。启动后的结果如下图所示
package org.calonlan.security.component; import java.util.Iterator; import java.util.Set; import org.springframework.beans.factory.annotation.Value; import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisPool; import redis.clients.jedis.JedisPoolConfig; /** * @author Administrator * redismanager主要用来给用户提供一个设计完备的,通过jedis的jar包来管理redis内存数据库的各种方法 */ public class RedisManager { // ip和port属性都定义在了properties文件中,这里通过spring的注解方式来直接使用 @Value("${redis.ip}") private String host; @Value("${redis.port}") private int port; // 设置为0的话就是永远都不会过期 private int expire = 0; // 定义一个管理池,所有的redisManager共同使用。 private static JedisPool jedisPool = null; public RedisManager() { } /** * * 初始化方法,在这个方法中通过host和port来初始化jedispool。 * * */ public void init() { if (null == host || 0 == port) { System.out.println("请初始化redis配置文件"); throw new NullPointerException("找不到redis配置"); } if (jedisPool == null) { jedisPool = new JedisPool(new JedisPoolConfig(), host, port); } } /** * get value from redis * * @param key * @return */ public byte[] get(byte[] key) { byte[] value = null; Jedis jedis = jedisPool.getResource(); try { value = jedis.get(key); } finally { jedisPool.returnResource(jedis); } return value; } /** * get value from redis * * @param key * @return */ public String get(String key) { String value = null; Jedis jedis = jedisPool.getResource(); try { value = jedis.get(key); } finally { jedisPool.returnResource(jedis); } return value; } /** * set * * @param key * @param value * @return */ public byte[] set(byte[] key, byte[] value) { Jedis jedis = jedisPool.getResource(); try { jedis.set(key, value); if (this.expire != 0) { jedis.expire(key, this.expire); } } finally { jedisPool.returnResource(jedis); } return value; } /** * set * * @param key * @param value * @return */ public String set(String key, String value) { Jedis jedis = jedisPool.getResource(); try { jedis.set(key, value); if (this.expire != 0) { jedis.expire(key, this.expire); } } finally { jedisPool.returnResource(jedis); } return value; } /** * set * * @param key * @param value * @param expire * @return */ public byte[] set(byte[] key, byte[] value, int expire) { Jedis jedis = jedisPool.getResource(); try { jedis.set(key, value); if (expire != 0) { jedis.expire(key, expire); } } finally { jedisPool.returnResource(jedis); } return value; } /** * set * * @param key * @param value * @param expire * @return */ public String set(String key, String value, int expire) { Jedis jedis = jedisPool.getResource(); try { jedis.set(key, value); if (expire != 0) { jedis.expire(key, expire); } } finally { jedisPool.returnResource(jedis); } return value; } /** * del * * @param key */ public void del(byte[] key) { Jedis jedis = jedisPool.getResource(); try { jedis.del(key); } finally { jedisPool.returnResource(jedis); } } /** * del * * @param key */ public void del(String key) { Jedis jedis = jedisPool.getResource(); try { jedis.del(key); } finally { jedisPool.returnResource(jedis); } } /** * flush */ public void flushDB() { Jedis jedis = jedisPool.getResource(); try { jedis.flushDB(); } finally { jedisPool.returnResource(jedis); } } /** * size */ public Long dbSize() { Long dbSize = 0L; Jedis jedis = jedisPool.getResource(); try { dbSize = jedis.dbSize(); } finally { jedisPool.returnResource(jedis); } return dbSize; } /** * keys * * @param regex * @return */ public Set<byte[]> keys(String pattern) { Set<byte[]> keys = null; Jedis jedis = jedisPool.getResource(); try { keys = jedis.keys(pattern.getBytes()); } finally { jedisPool.returnResource(jedis); } return keys; } public void dels(String pattern) { Set<byte[]> keys = null; Jedis jedis = jedisPool.getResource(); try { keys = jedis.keys(pattern.getBytes()); Iterator<byte[]> ito = keys.iterator(); while (ito.hasNext()) { jedis.del(ito.next()); } } finally { jedisPool.returnResource(jedis); } } public String getHost() { return host; } public void setHost(String host) { this.host = host; } public int getPort() { return port; } public void setPort(int port) { this.port = port; } public int getExpire() { return expire; } public void setExpire(int expire) { this.expire = expire; } }
这里的redisManager是通过spring来进行管理的,所以直接使用了@value来读取properties文件中的属性。properties文件的内容如下:
<span style="white-space:pre"> </span>redis.ip=127.0.0.1 <span style="white-space:pre"> </span> redis.port=6379
ip定义为本机的ip,端口是redis默认的6379端口。
<bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <property name="locations"> <list> <value>classpath:/config/jdbc.properties</value> <value>classpath:/config/redis.properties</value> </list> </property> </bean>
在spring中就是在这个地方把reids服务的配置文件redis.properties文件加载到spring中管理的。
这样redis服务就集成到了项目中,具体的使用,通过对shiro默认缓存和session数据的缓存的保存过程来展示。
项目中遇到的问题是这样的,在同时使用一个机器使用多个web容器或者多个机器同时来为一个项目进行负载均衡时,shiro的默认缓存和session数据无法在多个web容器或者多个机器之间进行同步。为了达到多web容器或多机器负载均衡的目的,我们修改了shiro的默认session管理器和缓存管理器来使shiro通过redis来管理session和缓存。这样多个web容器或机器之间的数据就可以互通共用了。
首先是shiro的配置文件,我用的是spring集成的方式。
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" xmlns:util="http://www.springframework.org/schema/util" xmlns:jee="http://www.springframework.org/schema/jee" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation=" http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.0.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-3.0.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd" default-lazy-init="false"> <!-- 缓存管理器 --> <bean id="cacheManager" class="org.calonlan.security.spring.SpringCacheManagerWrapper"> <property name="cacheManager" ref="springCacheManager" /> </bean> <!-- 凭证匹配器 --> <bean id="credentialsMatcher" class="org.calonlan.security.credentials.RetryLimitHashedCredentialsMatcher"> <constructor-arg ref="cacheManager" /> <property name="hashAlgorithmName" value="md5" /> <property name="hashIterations" value="2" /> <property name="storedCredentialsHexEncoded" value="true" /> </bean> <!-- Realm实现 --> <bean id="userRealm" class="org.calonlan.security.realm.UserRealm"> <property name="credentialsMatcher" ref="credentialsMatcher" /> <property name="cachingEnabled" value="true" /> <!--<property name="authenticationCachingEnabled" value="true"/> --> <!--<property name="authenticationCacheName" value="authenticationCache"/> --> <!--<property name="authorizationCachingEnabled" value="true"/> --> <!--<property name="authorizationCacheName" value="authorizationCache"/> --> </bean> <!-- 会话ID生成器 --> <bean id="sessionIdGenerator" class="org.apache.shiro.session.mgt.eis.JavaUuidSessionIdGenerator" /> <!-- 会话Cookie模板 --> <bean id="sessionIdCookie" class="org.apache.shiro.web.servlet.SimpleCookie"> <constructor-arg value="sid" /> <property name="httpOnly" value="true" /> <property name="maxAge" value="-1" /> </bean> <bean id="rememberMeCookie" class="org.apache.shiro.web.servlet.SimpleCookie"> <constructor-arg value="rememberMe" /> <property name="httpOnly" value="true" /> <property name="maxAge" value="2592000" /><!-- 30天 --> </bean> <!-- rememberMe管理器 --> <bean id="rememberMeManager" class="org.apache.shiro.web.mgt.CookieRememberMeManager"> <!-- rememberMe cookie加密的密钥 建议每个项目都不一样 默认AES算法 密钥长度(128 256 512 位) --> <property name="cipherKey" value="#{T(org.apache.shiro.codec.Base64).decode('4AvVhmFLUs0KTA3Kprsdag==')}" /> <property name="cookie" ref="rememberMeCookie" /> </bean> <!-- 会话DAO --> <bean id="sessionDAO" class="org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO"> <property name="activeSessionsCacheName" value="shiro-activeSessionCache" /> <property name="sessionIdGenerator" ref="sessionIdGenerator" /> </bean> <!-- 会话验证调度器 --> <bean id="sessionValidationScheduler" class="org.apache.shiro.session.mgt.quartz.QuartzSessionValidationScheduler"> <property name="sessionValidationInterval" value="1800000" /> <property name="sessionManager" ref="sessionManager" /> </bean> <!-- 会话管理器 --> <bean id="sessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager"> <property name="globalSessionTimeout" value="1800000" /> <property name="deleteInvalidSessions" value="true" /> <property name="sessionValidationSchedulerEnabled" value="true" /> <property name="sessionValidationScheduler" ref="sessionValidationScheduler" /> <property name="sessionDAO" ref="<span style="color:#ff0000;">customShiroSessionDAO</span>" /> //这里指定shiro的sessionManager使用我们指定的存储方式来存放session信息 <property name="sessionIdCookieEnabled" value="true" /> <property name="sessionIdCookie" ref="sessionIdCookie" /> </bean> <span style="color:#ff0000;"><bean id="customShiroSessionDAO" class="org.calonlan.security.component.CustomShiroSessionDao"> <property name="shiroSessionRepository" ref="jedisShiroSessionRepository" />//自己定义的sessiondao </bean></span> <span style="color:#ff0000;"> <bean id="jedisShiroSessionRepository" class="org.calonlan.security.component.JedisShiroSessionRepository"> <property name="redisManager" ref="redisManager"></property> </bean></span> <span style="color:#ff0000;"><bean id="redisManager" class="org.calonlan.security.component.RedisManager"></bean>//注册上面实现的redisManager到spring中</span> <span style="background-color: rgb(255, 0, 0);"><bean id="jedisShiroCacheManager" class="org.calonlan.security.component.JedisShiroCacheManager"> <property name="redisManager" ref="redisManager"></property> </bean></span><span style="background-color: rgb(255, 255, 255);"> </span> <span style="color:#ff0000;"> <bean id="customShiroCacheManager" class="org.calonlan.security.component.CustomShiroCacheManager"> <property name="shrioCacheManager" ref="jedisShiroCacheManager"></property> </bean></span> <!-- 安全管理器 --> <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> <property name="realm" ref="userRealm" /> <property name="sessionManager" ref="sessionManager" /> <property name="cacheManager" ref="<span style="color:#ff0000;">customShiroCacheManager</span>" /> <property name="rememberMeManager" ref="rememberMeManager" /> </bean> <!-- 相当于调用SecurityUtils.setSecurityManager(securityManager) --> <bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean"> <property name="staticMethod" value="org.apache.shiro.SecurityUtils.setSecurityManager" /> <property name="arguments" ref="securityManager" /> </bean> <!-- 基于Form表单的身份验证过滤器 --> <bean id="formAuthenticationFilter" class="org.apache.shiro.web.filter.authc.FormAuthenticationFilter"> <property name="usernameParam" value="username" /> <property name="passwordParam" value="password" /> <property name="rememberMeParam" value="rememberMe" /> <property name="loginUrl" value="/login" /> </bean> <bean id="sysUserFilter" class="org.calonlan.security.web.shiro.filter.SysUserFilter" /> <!-- Shiro的Web过滤器 --> <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <property name="securityManager" ref="securityManager" /> <!-- 逻辑上正确,不起作用 --> <property name="loginUrl" value="/login" /> <property name="successUrl" value="/admin/index" /> <property name="filters"> <util:map> <entry key="authc" value-ref="formAuthenticationFilter" /> <entry key="sysUser" value-ref="sysUserFilter" /> </util:map> </property> <property name="filterChainDefinitions"> <value> /img/** =anon /ueditor/jsp/upload/** =anon /login = authc /authenticated = authc /css/** = anon /common/** = anon /js/** = anon /admin/** = user,sysUser //*=anon </value> </property> </bean> <!-- Shiro生命周期处理器 --> <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor" /> </beans>
主要看上图的红色部分:其中customShiroSessionDAO、jedisShiroSessionRepository用来处理session;customShiroCacheManager、jedisShiroCacheManager用来处理缓存。
他们的源代码如下:
package org.calonlan.security.component; import java.io.Serializable; import java.util.Collection; import org.apache.shiro.session.Session; import org.apache.shiro.session.UnknownSessionException; import org.apache.shiro.session.mgt.eis.AbstractSessionDAO; public class CustomShiroSessionDao extends AbstractSessionDAO { private ShiroSessionRepository shiroSessionRepository; public ShiroSessionRepository getShiroSessionRepository() { return shiroSessionRepository; } public void setShiroSessionRepository( ShiroSessionRepository shiroSessionRepository) { this.shiroSessionRepository = shiroSessionRepository; } @Override public void delete(Session session) { if (session == null) { System.out.println("错误"); return; } Serializable id = session.getId(); if (id != null) getShiroSessionRepository().deleteSession(id); } @Override public Collection<Session> getActiveSessions() { return getShiroSessionRepository().getAllSessions(); } @Override public void update(Session session) throws UnknownSessionException { getShiroSessionRepository().saveSession(session); } @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); } }
这里我们是继承了shiro的AbstractSessionDAO,然后仿照shiro的模式,给他一个shiroSessionRepository,来进行详细的session存储操作。
package org.calonlan.security.component; import java.io.Serializable; import java.util.Collection; import org.apache.shiro.session.Session; public interface ShiroSessionRepository { void saveSession(Session session); void deleteSession(Serializable sessionId); Session getSession(Serializable sessionId); Collection<Session> getAllSessions(); }
package org.calonlan.security.component; import java.io.Serializable; import java.util.Collection; import java.util.HashSet; import java.util.Set; import org.apache.shiro.session.Session; public class JedisShiroSessionRepository implements ShiroSessionRepository { /** * * redis session key 前缀 * * */ private final String REDIS_SHIRO_SESSION = "shiro-session"; private RedisManager redisManager; @Override public void saveSession(Session session) { redisManager.init(); if (session == null || session.getId() == null) { System.out.println("session 或者 session ID 为空"); } byte[] key = SerializeUtils.serialize(getRedisSessionKey(session .getId())); byte[] value = SerializeUtils.serialize(session); Long timeOut = session.getTimeout() / 1000; redisManager.set(key, value, Integer.parseInt(timeOut.toString())); } @Override public void deleteSession(Serializable sessionId) { redisManager.init(); if (sessionId == null) { System.out.println("id为空"); } redisManager.del(SerializeUtils .serialize(getRedisSessionKey(sessionId))); } @Override public Session getSession(Serializable sessionId) { redisManager.init(); if (null == sessionId) { System.out.println("id为空"); return null; } Session session = null; byte[] value = redisManager.get(SerializeUtils .serialize(getRedisSessionKey(sessionId))); if (null == value) return null; session = (Session) SerializeUtils.deserialize(value); return session; } @Override public Collection<Session> getAllSessions() { redisManager.init(); Set<Session> sessions = new HashSet<Session>(); Set<byte[]> byteKeys = redisManager .keys(this.REDIS_SHIRO_SESSION + "*"); if (byteKeys != null && byteKeys.size() > 0) { for (byte[] bs : byteKeys) { Session s = (Session) SerializeUtils.deserialize(redisManager .get(bs)); sessions.add(s); } } return sessions; } /** * 获取redis中的session key * * @param sessionId * @return */ private String getRedisSessionKey(Serializable sessionId) { return this.REDIS_SHIRO_SESSION + sessionId; } public RedisManager getRedisManager() { return redisManager; } public void setRedisManager(RedisManager redisManager) { this.redisManager = redisManager; } public JedisShiroSessionRepository() { } // public static void main(String[] args) { // Jedis jj = new Jedis("localhost"); // //jj.set("key2", "232323231========="); // String ss = jj.get("key1"); // System.out.println(jj.get("key2")); // System.out.println(ss); // } }
package org.calonlan.security.component; import org.apache.shiro.cache.Cache; import org.apache.shiro.cache.CacheException; import org.apache.shiro.cache.CacheManager; import org.apache.shiro.util.Destroyable; public class CustomShiroCacheManager implements CacheManager, Destroyable { private ShiroCacheManager shrioCacheManager; public ShiroCacheManager getShrioCacheManager() { return shrioCacheManager; } public void setShrioCacheManager(ShiroCacheManager shrioCacheManager) { this.shrioCacheManager = shrioCacheManager; } @Override public void destroy() throws Exception { getShrioCacheManager().destroy(); } @Override public <K, V> Cache<K, V> getCache(String name) throws CacheException { return getShrioCacheManager().getCache(name); } }
package org.calonlan.security.component; import org.apache.shiro.cache.Cache; public interface ShiroCacheManager { <K, V> Cache<K, V> getCache(String name); void destroy(); }
package org.calonlan.security.component; import org.apache.shiro.cache.Cache; public class JedisShiroCacheManager implements ShiroCacheManager { private RedisManager redisManager; public RedisManager getRedisManager() { return redisManager; } public void setRedisManager(RedisManager redisManager) { this.redisManager = redisManager; } @Override public <K, V> Cache<K, V> getCache(String name) { return new JedisShiroCache<K, V>(redisManager, name); } @Override public void destroy() { redisManager.init(); redisManager.flushDB(); } }
具体的使用就如如上代码所示,为什么要弄一个什么ShiroCacheManager和ShiroSessionRepository接口呢,我想各位大侠也能知道的。有了这个两个接口,我们就可以通过这个两个接口来实现更多形式的shiro的session和缓存的管理了。
在很多地方使用到了SerializeUtils,它的作用就是把对象转化为byte数组,或把byte数组转化为对象。源代码如下:
package org.calonlan.security.component; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; public class SerializeUtils { public static byte[] serialize(Object o) { ByteArrayOutputStream out = new ByteArrayOutputStream(); try { ObjectOutputStream outo = new ObjectOutputStream(out); outo.writeObject(o); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } return out.toByteArray(); } public static Object deserialize(byte[] b) { ObjectInputStream oin; try { oin = new ObjectInputStream(new ByteArrayInputStream(b)); try { return oin.readObject(); } catch (ClassNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); return null; } } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); return null; } } }
从图中可以看到,我登录后,session信息已经保存到了redis数据库中。多个tomcat做负载均衡的测试结果也很良好,但是我这个笔记本没有安装那种环境,所以无法测试。这都是个人一点小东西,各位大神勿喷。
12.10日补充更新,为了让redis数据库能安全的使用,(在实际生产环境中我们也是这样要求的)我们需要给redis数据库设置密码;在windows环境下是这样的。
首先进入32bit目录中,找到redis.conf文件,在文件中做如下修改:
找到# requirepass foobared
修改为 requirepass 你的密码。
然后在DOS环境下进入你的redis-server.exe所在的目录
执行如下命令:redis-server.exe reids.conf 从dos中来启动redis服务,这样你的conf中配置才会生效。
对应的我修改了redisManager的代码。
package org.calonlan.security.component; import java.util.Iterator; import java.util.Set; import org.springframework.beans.factory.annotation.Value; import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisPool; import redis.clients.jedis.JedisPoolConfig; /** * @author Administrator * redismanager主要用来给用户提供一个设计完备的,通过jedis的jar包来管理redis内存数据库的各种方法 */ public class RedisManager { // ip和port属性都定义在了properties文件中,这里通过spring的注解方式来直接使用 @Value("${redis.ip}") private String host; @Value("${redis.port}") private int port; // 设置为0的话就是永远都不会过期 private int expire = 0; // 定义一个管理池,所有的redisManager共同使用。 private static JedisPool jedisPool = null; public RedisManager() { } /** * * 初始化方法,在这个方法中通过host和port来初始化jedispool。 * * */ public void init() { if (null == host || 0 == port) { System.out.println("请初始化redis配置文件"); throw new NullPointerException("找不到redis配置"); } if (jedisPool == null) { jedisPool = new JedisPool(new JedisPoolConfig(), host, port); } } /** * get value from redis * * @param key * @return */ public byte[] get(byte[] key) { byte[] value = null; Jedis jedis = jedisPool.getResource(); jedis.auth("你的密码"); try { value = jedis.get(key); } finally { jedisPool.returnResource(jedis); } return value; } /** * get value from redis * * @param key * @return */ public String get(String key) { String value = null; Jedis jedis = jedisPool.getResource(); jedis.auth("你的密码"); try { value = jedis.get(key); } finally { jedisPool.returnResource(jedis); } return value; } /** * set * * @param key * @param value * @return */ public byte[] set(byte[] key, byte[] value) { Jedis jedis = jedisPool.getResource(); jedis.auth("你的密码"); try { jedis.set(key, value); if (this.expire != 0) { jedis.expire(key, this.expire); } } finally { jedisPool.returnResource(jedis); } return value; } /** * set * * @param key * @param value * @return */ public String set(String key, String value) { Jedis jedis = jedisPool.getResource(); jedis.auth("你的密码"); try { jedis.set(key, value); if (this.expire != 0) { jedis.expire(key, this.expire); } } finally { jedisPool.returnResource(jedis); } return value; } /** * set * * @param key * @param value * @param expire * @return */ public byte[] set(byte[] key, byte[] value, int expire) { Jedis jedis = jedisPool.getResource(); jedis.auth("你的密码"); try { jedis.set(key, value); if (expire != 0) { jedis.expire(key, expire); } } finally { jedisPool.returnResource(jedis); } return value; } /** * set * * @param key * @param value * @param expire * @return */ public String set(String key, String value, int expire) { Jedis jedis = jedisPool.getResource(); jedis.auth("你的密码"); try { jedis.set(key, value); if (expire != 0) { jedis.expire(key, expire); } } finally { jedisPool.returnResource(jedis); } return value; } /** * del * * @param key */ public void del(byte[] key) { Jedis jedis = jedisPool.getResource(); jedis.auth("你的密码"); try { jedis.del(key); } finally { jedisPool.returnResource(jedis); } } /** * del * * @param key */ public void del(String key) { Jedis jedis = jedisPool.getResource(); jedis.auth("你的密码"); try { jedis.del(key); } finally { jedisPool.returnResource(jedis); } } /** * flush */ public void flushDB() { Jedis jedis = jedisPool.getResource(); jedis.auth("你的密码"); try { jedis.flushDB(); } finally { jedisPool.returnResource(jedis); } } /** * size */ public Long dbSize() { Long dbSize = 0L; Jedis jedis = jedisPool.getResource(); jedis.auth("你的密码"); try { dbSize = jedis.dbSize(); } finally { jedisPool.returnResource(jedis); } return dbSize; } /** * keys * * @param regex * @return */ public Set<byte[]> keys(String pattern) { Set<byte[]> keys = null; Jedis jedis = jedisPool.getResource(); jedis.auth("你的密码"); try { System.out.println("我调用了keys方法=---shiro"); keys = jedis.keys(("*" + pattern).getBytes()); if (null != keys) System.out.println(keys.size()); } finally { jedisPool.returnResource(jedis); } return keys; } public void dels(String pattern) { Set<byte[]> keys = null; Jedis jedis = jedisPool.getResource(); jedis.auth("你的密码"); try { System.out.println("我调用了dels方法=---shiro"); keys = jedis.keys(("*" + pattern).getBytes());//这里我做了修改,在redis数据库中我们的id前面还加了一堆东西,这样修改后就可以全部查看到了 Iterator<byte[]> ito = keys.iterator(); while (ito.hasNext()) { jedis.del(ito.next()); } } finally { jedisPool.returnResource(jedis); } } public String getHost() { return host; } public void setHost(String host) { this.host = host; } public int getPort() { return port; } public void setPort(int port) { this.port = port; } public int getExpire() { return expire; } public void setExpire(int expire) { this.expire = expire; } }
我在代码中还做了一下修改,代码中也做了注释。修改的地方是keys = jedis.keys(("*" + pattern).getBytes()); 在所有的pattern前面都加上*号了,因为我看到在redis实际使用的时候不加*号是查询不到结果的。因为redis在我们的id前面加了一堆东西;但是在使用get的时候却是没问题的,直接通过id就能取到值··这个我还没弄懂· ··希望有高手看到以后能指点一下;我也会继续研究。
12.10下午6点··今天休息··多想想了··问题解决了。更新的代码主要包括一下内容:
package org.calonlan.security.component; import java.io.Serializable; import java.util.Collection; import java.util.HashSet; import java.util.Set; import org.apache.shiro.session.Session; public class JedisShiroSessionRepository implements ShiroSessionRepository { /** * * redis session key 前缀 * * */ private final String REDIS_SHIRO_SESSION = "shiro-session"; private RedisManager redisManager; @Override public void saveSession(Session session) { redisManager.init(); if (session == null || session.getId() == null) { System.out.println("session 或者 session ID 为空"); } byte[] key = getRedisSessionKey(session.getId()).getBytes(); byte[] value = SerializeUtils.serialize(session); Long timeOut = session.getTimeout() / 1000; redisManager.set(key, value, Integer.parseInt(timeOut.toString())); } @Override public void deleteSession(Serializable sessionId) { redisManager.init(); if (sessionId == null) { System.out.println("id为空"); } redisManager.del(getRedisSessionKey(sessionId).getBytes()); } @Override public Session getSession(Serializable sessionId) { redisManager.init(); if (null == sessionId) { System.out.println("id为空"); return null; } Session session = null; byte[] value = redisManager.get(getRedisSessionKey(sessionId) .getBytes()); if (null == value) return null; session = (Session) SerializeUtils.deserialize(value); return session; } @Override public Collection<Session> getAllSessions() { redisManager.init(); Set<Session> sessions = new HashSet<Session>(); Set<byte[]> byteKeys = redisManager .keys(this.REDIS_SHIRO_SESSION + "*"); if (byteKeys != null && byteKeys.size() > 0) { for (byte[] bs : byteKeys) { Session s = (Session) SerializeUtils.deserialize(redisManager .get(bs)); sessions.add(s); } } return sessions; } /** * 获取redis中的session key * * @param sessionId * @return */ private String getRedisSessionKey(Serializable sessionId) { return this.REDIS_SHIRO_SESSION + sessionId; } public RedisManager getRedisManager() { return redisManager; } public void setRedisManager(RedisManager redisManager) { this.redisManager = redisManager; } public JedisShiroSessionRepository() { } // public static void main(String[] args) { // Jedis jj = new Jedis("localhost"); // //jj.set("key2", "232323231========="); // String ss = jj.get("key1"); // System.out.println(jj.get("key2")); // System.out.println(ss); // } }
package org.calonlan.security.component; import java.util.Collection; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Set; import org.apache.shiro.cache.Cache; import org.apache.shiro.cache.CacheException; public class JedisShiroCache<K, V> implements Cache<K, V> { private final String REDIS_SHIRO_CACHE = "shiro-cache"; private RedisManager redisManager; private String name; public JedisShiroCache(RedisManager redisManager, String name) { this.redisManager = redisManager; this.name = name; } public String getName() { if (null == null) { return ""; } return name; } public void setName(String name) { this.name = name; } @Override public void clear() throws CacheException { redisManager.init(); String keysPattern = this.REDIS_SHIRO_CACHE + "*"; redisManager.flushDB(); } @Override public V get(K key) throws CacheException { redisManager.init(); byte[] byteKey = getCacheKey(key).getBytes(); byte[] byteValue = redisManager.get(byteKey); if (null == byteValue) return null; return (V) SerializeUtils.deserialize(byteValue); } @Override public Set<K> keys() { redisManager.init(); Set<byte[]> byteSet = redisManager.keys(this.REDIS_SHIRO_CACHE + "*"); Set<K> keys = new HashSet<K>(); for (byte[] bs : byteSet) { keys.add((K) SerializeUtils.deserialize(bs)); } return keys; } @Override public V put(K key, V value) throws CacheException { redisManager.init(); V previos = get(key); redisManager.set(getCacheKey(key).getBytes(), SerializeUtils.serialize(value)); return previos; } @Override public V remove(K key) throws CacheException { redisManager.init(); V previos = get(key); redisManager.del(getCacheKey(key).getBytes()); return previos; } @Override public int size() { redisManager.init(); if (keys() == null) return 0; return keys().size(); } @Override public Collection<V> values() { Set<byte[]> byteSet = redisManager.keys(this.REDIS_SHIRO_CACHE + "*"); List<V> result = new LinkedList<V>(); for (byte[] bs : byteSet) { result.add((V) SerializeUtils.deserialize(redisManager.get(bs))); } return result; } private String getCacheKey(Object key) { return this.REDIS_SHIRO_CACHE + getName() + ":" + key; } }
package org.calonlan.security.component; import java.util.Iterator; import java.util.Set; import org.springframework.beans.factory.annotation.Value; import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisPool; import redis.clients.jedis.JedisPoolConfig; /** * @author Administrator * redismanager主要用来给用户提供一个设计完备的,通过jedis的jar包来管理redis内存数据库的各种方法 */ public class RedisManager { // ip和port属性都定义在了properties文件中,这里通过spring的注解方式来直接使用 @Value("${redis.ip}") private String host; @Value("${redis.port}") private int port; // 设置为0的话就是永远都不会过期 private int expire = 0; // 定义一个管理池,所有的redisManager共同使用。 private static JedisPool jedisPool = null; public RedisManager() { } /** * * 初始化方法,在这个方法中通过host和port来初始化jedispool。 * * */ public void init() { if (null == host || 0 == port) { System.out.println("请初始化redis配置文件"); throw new NullPointerException("找不到redis配置"); } if (jedisPool == null) { jedisPool = new JedisPool(new JedisPoolConfig(), host, port); } } /** * get value from redis * * @param key * @return */ public byte[] get(byte[] key) { byte[] value = null; Jedis jedis = jedisPool.getResource(); jedis.auth("你的密码"); try { value = jedis.get(key); } finally { jedisPool.returnResource(jedis); } return value; } /** * get value from redis * * @param key * @return */ public String get(String key) { String value = null; Jedis jedis = jedisPool.getResource(); jedis.auth("你的密码"); try { value = jedis.get(key); } finally { jedisPool.returnResource(jedis); } return value; } /** * set * * @param key * @param value * @return */ public byte[] set(byte[] key, byte[] value) { Jedis jedis = jedisPool.getResource(); jedis.auth("你的密码"); try { jedis.set(key, value); if (this.expire != 0) { jedis.expire(key, this.expire); } } finally { jedisPool.returnResource(jedis); } return value; } /** * set * * @param key * @param value * @return */ public String set(String key, String value) { Jedis jedis = jedisPool.getResource(); jedis.auth("你的密码"); try { jedis.set(key, value); if (this.expire != 0) { jedis.expire(key, this.expire); } } finally { jedisPool.returnResource(jedis); } return value; } /** * set * * @param key * @param value * @param expire * @return */ public byte[] set(byte[] key, byte[] value, int expire) { Jedis jedis = jedisPool.getResource(); jedis.auth("你的密码"); try { jedis.set(key, value); if (expire != 0) { jedis.expire(key, expire); } } finally { jedisPool.returnResource(jedis); } return value; } /** * set * * @param key * @param value * @param expire * @return */ public String set(String key, String value, int expire) { Jedis jedis = jedisPool.getResource(); jedis.auth("你的密码"); try { jedis.set(key, value); if (expire != 0) { jedis.expire(key, expire); } } finally { jedisPool.returnResource(jedis); } return value; } /** * del * * @param key */ public void del(byte[] key) { Jedis jedis = jedisPool.getResource(); jedis.auth("你的密码"); try { jedis.del(key); } finally { jedisPool.returnResource(jedis); } } /** * del * * @param key */ public void del(String key) { Jedis jedis = jedisPool.getResource(); jedis.auth("你的密码"); try { jedis.del(key); } finally { jedisPool.returnResource(jedis); } } /** * flush */ public void flushDB() { Jedis jedis = jedisPool.getResource(); jedis.auth("你的密码"); try { jedis.flushDB(); } finally { jedisPool.returnResource(jedis); } } /** * size */ public Long dbSize() { Long dbSize = 0L; Jedis jedis = jedisPool.getResource(); jedis.auth("你的密码"); try { dbSize = jedis.dbSize(); } finally { jedisPool.returnResource(jedis); } return dbSize; } /** * keys * * @param regex * @return */ public Set<byte[]> keys(String pattern) { Set<byte[]> keys = null; Jedis jedis = jedisPool.getResource(); jedis.auth("你的密码"); try { System.out.println("我调用了keys方法=---shiro"); keys = jedis.keys(pattern.getBytes()); if (null != keys) System.out.println(keys.size()); } finally { jedisPool.returnResource(jedis); } return keys; } public void dels(String pattern) { Set<byte[]> keys = null; Jedis jedis = jedisPool.getResource(); jedis.auth("你的密码"); try { System.out.println("我调用了dels方法=---shiro"); keys = jedis.keys(pattern.getBytes()); Iterator<byte[]> ito = keys.iterator(); while (ito.hasNext()) { jedis.del(ito.next()); } } finally { jedisPool.returnResource(jedis); } } public String getHost() { return host; } public void setHost(String host) { this.host = host; } public int getPort() { return port; } public void setPort(int port) { this.port = port; } public int getExpire() { return expire; } public void setExpire(int expire) { this.expire = expire; } }
主要更改了这三个类,做的主要修改就是这句,byte[] key = getRedisSessionKey(session.getId()).getBytes();这句很重要,以前我们的key和value一样是用serilizableUtil在转换为byte数组的。但是我们这样转换会有一个问题。就是会有OBJECT标识,因为我们使用了对象流··。大家可以看serilizableutil的代码。然后我给大家看看修改前和修改后在数据库中看到的不同景象;
修改前:
可以看到我们定义的key前面多了一堆/xac/xed#$^%^$%^$%^$这样的东西,个人理解这就是类标识吧···
修改后:
可以看到我们的id就是按我们定义的样子在显示着··虽然是用byte数组传递过去的··
然后的redismanager中,我们使用keys(“shiro-session*”)也可以列出需要的所有的key了···
就这样··也许还会有问题····也希望高手能给批评意见。
有人反应我出现在配置文件中的类有几个没有贴出来,首先很感谢您的指正;在这里我主要说shiro和redis的结合。至于shiro的配置使用不在这篇博文中讨论。但是鉴于有人要看我就贴出来。
SpringCacheManagerWrapper
/** * Copyright (c) 2005-2012 https://github.com/zhangkaitao * * Licensed under the Apache License, Version 2.0 (the "License"); */ package org.calonlan.security.spring; import net.sf.ehcache.Ehcache; import org.apache.shiro.cache.Cache; import org.apache.shiro.cache.CacheException; import org.apache.shiro.cache.CacheManager; import org.apache.shiro.util.CollectionUtils; import org.springframework.cache.support.SimpleValueWrapper; import java.util.*; /** * 包装Spring cache抽象 * <p>User: Zhang Kaitao * <p>Date: 13-3-23 上午8:26 * <p>Version: 1.0 */ public class SpringCacheManagerWrapper implements CacheManager { private org.springframework.cache.CacheManager cacheManager; /** * 设置spring cache manager * * @param cacheManager */ public void setCacheManager(org.springframework.cache.CacheManager cacheManager) { this.cacheManager = cacheManager; } @Override public <K, V> Cache<K, V> getCache(String name) throws CacheException { org.springframework.cache.Cache springCache = cacheManager.getCache(name); return new SpringCacheWrapper(springCache); } static class SpringCacheWrapper implements Cache { private org.springframework.cache.Cache springCache; SpringCacheWrapper(org.springframework.cache.Cache springCache) { this.springCache = springCache; } @Override public Object get(Object key) throws CacheException { Object value = springCache.get(key); if (value instanceof SimpleValueWrapper) { return ((SimpleValueWrapper) value).get(); } return value; } @Override public Object put(Object key, Object value) throws CacheException { springCache.put(key, value); return value; } @Override public Object remove(Object key) throws CacheException { springCache.evict(key); return null; } @Override public void clear() throws CacheException { springCache.clear(); } @Override public int size() { if(springCache.getNativeCache() instanceof Ehcache) { Ehcache ehcache = (Ehcache) springCache.getNativeCache(); return ehcache.getSize(); } throw new UnsupportedOperationException("invoke spring cache abstract size method not supported"); } @Override public Set keys() { if(springCache.getNativeCache() instanceof Ehcache) { Ehcache ehcache = (Ehcache) springCache.getNativeCache(); return new HashSet(ehcache.getKeys()); } throw new UnsupportedOperationException("invoke spring cache abstract keys method not supported"); } @Override public Collection values() { if(springCache.getNativeCache() instanceof Ehcache) { Ehcache ehcache = (Ehcache) springCache.getNativeCache(); List keys = ehcache.getKeys(); if (!CollectionUtils.isEmpty(keys)) { List values = new ArrayList(keys.size()); for (Object key : keys) { Object value = get(key); if (value != null) { values.add(value); } } return Collections.unmodifiableList(values); } else { return Collections.emptyList(); } } throw new UnsupportedOperationException("invoke spring cache abstract values method not supported"); } } }
RetryLimitHashedCredentialsMatcher
package org.calonlan.security.credentials; import org.apache.shiro.authc.AuthenticationInfo; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authc.ExcessiveAttemptsException; import org.apache.shiro.authc.credential.HashedCredentialsMatcher; import org.apache.shiro.cache.Cache; import org.apache.shiro.cache.CacheManager; import java.util.concurrent.atomic.AtomicInteger; /** * <p>User: Zhang Kaitao * <p>Date: 14-1-28 * <p>Version: 1.0 */ public class RetryLimitHashedCredentialsMatcher extends HashedCredentialsMatcher { private Cache<String, AtomicInteger> passwordRetryCache; public RetryLimitHashedCredentialsMatcher(CacheManager cacheManager) { passwordRetryCache = cacheManager.getCache("passwordRetryCache"); } @Override public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) { String username = (String)token.getPrincipal(); //retry count + 1 AtomicInteger retryCount = passwordRetryCache.get(username); if(retryCount == null) { retryCount = new AtomicInteger(0); passwordRetryCache.put(username, retryCount); } if(retryCount.incrementAndGet() > 5) { //if retry count > 5 throw throw new ExcessiveAttemptsException(); } boolean matches = super.doCredentialsMatch(token, info); if(matches) { //clear retry count passwordRetryCache.remove(username); } return matches; } }
package org.calonlan.security.realm; import org.apache.log4j.Logger; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.AuthenticationInfo; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authc.LockedAccountException; import org.apache.shiro.authc.SimpleAuthenticationInfo; import org.apache.shiro.authz.AuthorizationInfo; import org.apache.shiro.authz.SimpleAuthorizationInfo; import org.apache.shiro.cache.Cache; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.subject.PrincipalCollection; import org.apache.shiro.util.ByteSource; import org.calonlan.security.entity.User; import org.calonlan.security.service.ResourceService; import org.calonlan.security.service.UserRoleService; import org.calonlan.security.service.UserService; /** * @ClassName: UserRealm * @Description: TODO(这里用一句话描述这个类的作用) * @author mbc * @date 2014-6-7 上午11:49:06 * */ public class UserRealm extends AuthorizingRealm { private static final Logger logger = Logger.getLogger(UserRealm.class); @javax.annotation.Resource private UserRoleService userRoleService; @javax.annotation.Resource private ResourceService resourceService; @javax.annotation.Resource private UserService userService; /* * (非 Javadoc) <p>Title: doGetAuthorizationInfo</p> <p>Description:权限授权 </p> * * @param principals * * @return * * @see * org.apache.shiro.realm.AuthorizingRealm#doGetAuthorizationInfo(org.apache * .shiro.subject.PrincipalCollection) */ @Override protected AuthorizationInfo doGetAuthorizationInfo( PrincipalCollection principals) { // 为空的情况直接返回null if (null == principals) { return null; } SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo(); String username = (String) principals.getPrimaryPrincipal(); Cache<Object, AuthenticationInfo> cache = getAuthenticationCache(); System.out.println(cache == null); logger.info("[用户:" + username + "|权限授权]"); authorizationInfo.setRoles(userRoleService .loadRoleIdsByUsername(username)); authorizationInfo.setStringPermissions(resourceService .loadPermissionsByUsername(username)); logger.info("[用户:" + username + "|权限授权完成]"); return authorizationInfo; } /* * (非 Javadoc) <p>Title: doGetAuthenticationInfo</p> <p>Description:权限认证 * </p> * * @param token * * @return * * @throws AuthenticationException * * @see * org.apache.shiro.realm.AuthenticatingRealm#doGetAuthenticationInfo(org * .apache.shiro.authc.AuthenticationToken) */ @Override protected AuthenticationInfo doGetAuthenticationInfo( AuthenticationToken token) throws AuthenticationException { SimpleAuthenticationInfo authenticationInfo; String username = (String) token.getPrincipal(); logger.info("[用户:" + username + "|系统权限认证]"); User user = userService.loadByUsername(username); if (user != null) { if ("帐号锁定".equals(user.getState())) { throw new LockedAccountException();// 帐号锁定 } // 交给AuthenticatingRealm使用CredentialsMatcher进行密码匹配,如果觉得人家的不好可以自定义实现 authenticationInfo = new SimpleAuthenticationInfo( user.getUsername(), user.getPassword(), ByteSource.Util.bytes(user.getCredentialsSalt()), getName());// realm logger.info("[用户:" + username + "|系统权限认证完成]"); return authenticationInfo; // name } else return null; } }