首先要想通过Redis进行会话管理和缓存的话 就要实现这些各类 Cache、CacheManager、CachingSessionDao都是shiro里面的类。
讲一下在写代码中遇到的坑
1.序列化和反序列化的问题
序列化的问题主要是体现在对session的id进行序列化的时候会出现一个问题,可以利用Apache的common的lang3组件里面有对序列化操作的工具类。但是我在处理的过程中出现了一个小bug,还不知道原因是什么,这边说一下,经过RedisSessionDao操作后,在Redis里面存储了一个session,但是其key值很奇怪,在我写的key值前面还加了一些内容,当时不知道怎么回事,后来通过对key值进行处理,才解决了这个问题。
处理代码:
private byte[] getByteKey(Object k){ if(k instanceof String){ String key = PREFIX+k; return key.getBytes(); }else { return SerializationUtils.serialize((Serializable) k); } }
2.会话管理和缓存执行的顺序的问题
这里要说一下,Redis的会话管理主要是为了能够做到服务器集群的,所以其 主要作用是对Redis的时间进行设置的,在进行RedisSessionDao操作之后shiro还会进行缓存设置,也就是再在Redis上面设置session,所以2个key值要一样,不然会出现2个session,会导致一些小问题。这里要讲一下授权,授权会通过Redis缓存,把数据缓存到Redis上面,他的key值一般是对象。
3.会话管理的一些小问题
如果你要用Redis实现会话管理,就必须要自己实现Redis的缓存,这样才可以使用Redis的会话管理;当你只要缓存的时候可以不去实现会话管理,用shiro提供的默认会话管理也是可以的。
下面就献上我的代码:
pom.xml文件
org.springframework.boot spring-boot-starter-parent 1.5.1.RELEASE 1.8 org.springframework.boot spring-boot-starter-data-jpa org.springframework.data spring-data-jpa 1.11.3.RELEASE org.hibernate.javax.persistence hibernate-jpa-2.1-api 1.0.0.Final org.springframework.boot spring-boot-starter-thymeleaf net.sourceforge.nekohtml nekohtml 1.9.22 org.springframework.boot spring-boot-starter-web org.apache.shiro shiro-spring 1.4.0 mysql mysql-connector-java runtime com.alibaba druid 1.0.11 com.github.penggle kaptcha 2.3.2 javax.servlet javax.servlet-api redis.clients jedis org.apache.commons commons-lang3 3.4 commons-lang commons-lang 2.6 org.springframework.boot spring-boot-maven-plugin
shiro配置
import com.google.code.kaptcha.servlet.KaptchaServlet; import com.youxiong.dao.UserReposisty; import com.youxiong.domain.Permission; import com.youxiong.domain.Role; import com.youxiong.domain.UserInfo; import com.youxiong.filter.FormValid; import com.youxiong.redis.JedisCacheManager; import com.youxiong.redis.RedisSessionDao; import com.youxiong.redis.RedisSessionListener; import com.youxiong.redis.RediseSessionFactory; import com.youxiong.shiro.MyShiroRealm; import org.apache.shiro.authc.credential.HashedCredentialsMatcher; import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.session.SessionListener; import org.apache.shiro.session.mgt.SessionFactory; import org.apache.shiro.session.mgt.SessionManager; import org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO; import org.apache.shiro.session.mgt.eis.SessionDAO; import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor; import org.apache.shiro.spring.web.ShiroFilterFactoryBean; import org.apache.shiro.web.mgt.DefaultWebSecurityManager; import org.apache.shiro.web.servlet.SimpleCookie; import org.apache.shiro.web.session.mgt.DefaultWebSessionManager; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.web.servlet.ServletRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.handler.SimpleMappingExceptionResolver; import javax.servlet.Filter; import java.util.*; @Configuration public class ShiroConfig { @Autowired private UserReposisty userReposisty; @Bean public ShiroFilterFactoryBean createShiroFilter(SecurityManager securityManager) { System.out.println("--------ShiroFilterFactoryBean-------"); ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); shiroFilterFactoryBean.setSecurityManager(securityManager); MapJedisUtil工具类 ---》对Redis进行操作的类, Filter> filterMap = new HashMap<>(); //map里面key值要为authc才能使用自定义的过滤器 filterMap.put("authc", formValid()); // can go to login shiroFilterFactoryBean.setLoginUrl("/login.html"); //doLogin success go to page shiroFilterFactoryBean.setSuccessUrl("/success.html"); //do not Unauthorized page shiroFilterFactoryBean.setUnauthorizedUrl("/403.html"); Map , String> map = new LinkedHashMap , String>(); //验证码的路径 不要跟下面需要认证的写在一个路径里 会被拦截的 map.put("/servlet/**", "anon"); //需要把要授权的URL 全部装到filterChain中去过滤 UserInfo userInfo = userReposisty.findByUid(1); for (Role role : userInfo.getRoles()) { for (Permission permission : role.getPermissions()) { if (permission.getUrl() != "") { String permissions = "perms[" + permission.getPermission() + "]"; map.put(permission.getUrl(), permissions); } } } map.put("/user*/*", "authc"); shiroFilterFactoryBean.setFilterChainDefinitionMap(map); shiroFilterFactoryBean.setFilters(filterMap); return shiroFilterFactoryBean; } //自己定义realm @Bean public MyShiroRealm myShiroRealm() { MyShiroRealm myShiroRealm = new MyShiroRealm(); return myShiroRealm; } @Bean public SecurityManager securityManager() { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); securityManager.setRealm(myShiroRealm()); //缓存管理 securityManager.setCacheManager(jedisCacheManager()); //会话管理 securityManager.setSessionManager(sessionManager()); return securityManager; } //密码盐 可以不必实现 因为一般密码可以自己定义自己的密码加密规则 /* @Bean public HashedCredentialsMatcher hashedCredentialsMatcher(){ HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher(); hashedCredentialsMatcher.setHashAlgorithmName("md5"); hashedCredentialsMatcher.setHashIterations(2); return hashedCredentialsMatcher; }*/ //开启aop注解 @Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) { AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor(); authorizationAttributeSourceAdvisor.setSecurityManager(securityManager); return authorizationAttributeSourceAdvisor; } @Bean(name = "simpleMappingExceptionResolver") public SimpleMappingExceptionResolver createSimpleMappingExceptionResolver() { SimpleMappingExceptionResolver r = new SimpleMappingExceptionResolver(); Properties mappings = new Properties(); mappings.setProperty("DatabaseException", "databaseError");//数据库异常处理 mappings.setProperty("UnauthorizedException", "403"); r.setExceptionMappings(mappings); // None by default r.setDefaultErrorView("error"); // No default r.setExceptionAttribute("ex"); // Default is "exception" //r.setWarnLogCategory("example.MvcLogger"); // No default return r; } //servlet注册器 -----》验证码的路径 @Bean public ServletRegistrationBean servletRegistrationBean() { System.out.println("----验证码---"); return new ServletRegistrationBean(new KaptchaServlet(), "/servlet/kaptcha.jpg"); } //自定义过滤器 ---》里面实现了对验证码校验 @Bean("myFilter") public FormValid formValid() { return new FormValid(); } //jedis缓存 @Bean public JedisCacheManager jedisCacheManager() { return new JedisCacheManager(); } @Bean public SessionManager sessionManager() { DefaultWebSessionManager defaultWebSessionManager = new DefaultWebSessionManager(); defaultWebSessionManager.setSessionIdCookie(simpleCookie()); defaultWebSessionManager.setSessionDAO(sessionDAO()); //可以设置shiro提供的会话管理机制 //defaultWebSessionManager.setSessionDAO(new EnterpriseCacheSessionDAO()); return defaultWebSessionManager; } //这里就是会话管理的操作类 @Bean public SessionDAO sessionDAO() { return new RedisSessionDao(); } //这里需要设置一个cookie的名称 原因就是会跟原来的session的id值重复的 @Bean public SimpleCookie simpleCookie() { SimpleCookie simpleCookie = new SimpleCookie("REDISSESSION"); return simpleCookie; } }
import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisPool; import redis.clients.jedis.JedisPoolConfig; public class JedisUtil { private static JedisPool jedisPool; static { JedisPoolConfig jedisPoolConfig = new JedisPoolConfig(); jedisPoolConfig.setMaxTotal(100); jedisPoolConfig.setMaxIdle(10); jedisPoolConfig.setMaxWaitMillis(100); jedisPool = new JedisPool(jedisPoolConfig,"127.0.0.1",6379); } public static Jedis getJedis(){ return jedisPool.getResource(); } public static void closeJedis(Jedis jedis){ jedis.close(); } }
JedisCache ----》这个类就是做缓存用的
import org.apache.commons.lang3.SerializationUtils; import org.apache.shiro.cache.Cache; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import redis.clients.jedis.Jedis; import java.io.Serializable; import java.util.*; public class JedisCache<K,V> implements Cache<K, V> ,Serializable{ private static final Logger LOGGER = LoggerFactory.getLogger(JedisCache.class); private static final String PREFIX = "SHIRO_SESSION_ID"; private byte[] getByteKey(K k){ if(k instanceof String){ String key = PREFIX+k; return key.getBytes(); }else { return SerializationUtils.serialize((Serializable) k); } } @Override public int size() { Jedis jedis = JedisUtil.getJedis(); Long size = jedis.dbSize(); return size.intValue(); } @Override public Set<K> keys() { Jedis jedis = JedisUtil.getJedis(); Set<byte[]> bytes = jedis.keys( (PREFIX + new String("*")).getBytes()); Set<K> keys = new HashSet<>(); if(bytes!=null){ for (byte[] b: bytes) { keys.add(SerializationUtils.deserialize(b)); } } JedisUtil.closeJedis(jedis); return keys; } @Override public Collection<V> values() { Set<K> keys = this.keys(); Jedis jedis = JedisUtil.getJedis(); List<V> lists = new ArrayList<>(); for (K k:keys) { byte[] bytes = jedis.get(getByteKey(k)); lists.add(SerializationUtils.deserialize(bytes)); } JedisUtil.closeJedis(jedis); return lists; } @Override public void clear() { JedisUtil.getJedis().flushDB(); } @Override public V put(K k, V v) { LOGGER.info("key---->"+k+"value---->"+v); Jedis jedis = JedisUtil.getJedis(); jedis.set(getByteKey(k), SerializationUtils.serialize((Serializable) v)); jedis.expire(getByteKey(k),10000); byte[] bytes = jedis.get(SerializationUtils.serialize(getByteKey(k))); JedisUtil.closeJedis(jedis); if(bytes==null){ return null; } return SerializationUtils.deserialize(bytes); } @Override public V get(K k) { LOGGER.info("get------>key="+k); if(k==null){ return null; } //System.out.println(k); Jedis jedis = JedisUtil.getJedis(); byte[] bytes = jedis.get(getByteKey(k)); JedisUtil.closeJedis(jedis); if(bytes==null){ return null; } return SerializationUtils.deserialize(bytes); } @Override public V remove(K k) { Jedis jedis = JedisUtil.getJedis(); byte[] bytes = jedis.get(getByteKey(k)); jedis.del(getByteKey(k)); JedisUtil.closeJedis(jedis); if(bytes==null){ return null; } return SerializationUtils.deserialize(bytes); } }
JedisCacheManager ----》这个类主要是创建JedisCache的
import org.apache.shiro.cache.Cache; import org.apache.shiro.cache.CacheException; import org.apache.shiro.cache.CacheManager; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; public class JedisCacheManager implements CacheManager { private final ConcurrentMap, Cache> caches = new ConcurrentHashMap , Cache>(); //cache @Override public <K, V> Cache<K, V> getCache(String s) throws CacheException { Cache cache = caches.get(s); if(cache == null){ cache = new JedisCache(); caches.put(s,cache); } return cache; } }
RedisSessionDao ----》这个类就是对会话就行操作的,也就管理会话的,有创建、更新、移除等操作,都可以自己实现
import org.apache.commons.lang3.SerializationUtils; import org.apache.shiro.session.Session; import org.apache.shiro.session.UnknownSessionException; import org.apache.shiro.session.mgt.SimpleSession; import org.apache.shiro.session.mgt.ValidatingSession; import org.apache.shiro.session.mgt.eis.CachingSessionDAO; import org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO; import org.apache.shiro.session.mgt.eis.SessionDAO; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import redis.clients.jedis.Jedis; import redis.clients.jedis.Transaction; import java.io.Serializable; import java.util.Base64; import java.util.Collection; public class RedisSessionDao extends CachingSessionDAO { private static final String PREFIX = "SHIRO_SESSION_ID"; private static final int EXPRIE = 10000; private static final Logger LOGGER = LoggerFactory.getLogger(RedisSessionDao.class); @Override protected Serializable doCreate(Session session) { LOGGER.info("--------doCreate-----"); Serializable serializable = this.generateSessionId(session); assignSessionId(session, serializable); Jedis jedis = JedisUtil.getJedis(); session.setTimeout(EXPRIE*1000); /*jedis.set(getByteKey(serializable),SerializationUtils.serialize((Serializable)session)); jedis.expire(SerializationUtils.serialize(getByteKey(serializable)),EXPRIE);*/ jedis.setex(getByteKey(serializable),EXPRIE,SerializationUtils.serialize((Serializable)session) ); JedisUtil.closeJedis(jedis); return serializable; } @Override protected Session doReadSession(Serializable serializable) { LOGGER.info("--------doReadSession-----"); Jedis jedis = JedisUtil.getJedis(); Session session = null; byte[] s = jedis.get(getByteKey(serializable)); if (s != null) { session = SerializationUtils.deserialize(s); jedis.expire((PREFIX+serializable).getBytes(),EXPRIE); } //判断是否有会话 没有返回NULL if(session==null){ return null; } JedisUtil.closeJedis(jedis); return session; } private byte[] getByteKey(Object k){ if(k instanceof String){ String key = PREFIX+k; return key.getBytes(); }else { return SerializationUtils.serialize((Serializable) k); } } @Override protected void doUpdate(Session session) { LOGGER.info("--------doUpdate-----"); if(session==null){ return ; } Jedis jedis = JedisUtil.getJedis(); session.setTimeout(EXPRIE*1000); /*jedis.set(getByteKey(session.getId()),SerializationUtils.serialize((Serializable)session)); jedis.expire(SerializationUtils.serialize((PREFIX+session.getId())),EXPRIE);*/ jedis.setex(getByteKey(session.getId()),EXPRIE,SerializationUtils.serialize((Serializable)session) ); } @Override protected void doDelete(Session session) { LOGGER.info("--------doDelete-----"); Jedis jedis = JedisUtil.getJedis(); jedis.del(getByteKey(session.getId())); JedisUtil.closeJedis(jedis); } }