这里会显示出您选择的修订版和当前版本之间的差别。
后一修订版 | 前一修订版 | ||
分享:技术:shiro:spring整合shiro实现会话管理 [2015/09/20 23:02] gxx 创建 |
分享:技术:shiro:spring整合shiro实现会话管理 [2015/09/22 16:01] (当前版本) gxx [不同节点共享会话] |
||
---|---|---|---|
行 1: | 行 1: | ||
====== spring整合shiro实现会话管理 ====== | ====== spring整合shiro实现会话管理 ====== | ||
+ | 本wiki是在[[分享:技术:shiro:spring整合shiro实现权限管理|spring整合shiro实现权限管理]]的基础上继续修改实现的会话管理 | ||
+ | ===== application-shiro.xml ===== | ||
+ | <code xml application-shiro.xml> | ||
+ | <?xml version="1.0" encoding="UTF-8"?> | ||
+ | <beans xmlns="http://www.springframework.org/schema/beans" | ||
+ | xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" | ||
+ | xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx" | ||
+ | xmlns:jpa="http://www.springframework.org/schema/data/jpa" | ||
+ | xmlns:security="http://www.springframework.org/schema/security" | ||
+ | xsi:schemaLocation=" | ||
+ | http://www.springframework.org/schema/beans | ||
+ | http://www.springframework.org/schema/beans/spring-beans-4.0.xsd | ||
+ | http://www.springframework.org/schema/util | ||
+ | http://www.springframework.org/schema/util/spring-util-4.0.xsd | ||
+ | http://www.springframework.org/schema/context | ||
+ | http://www.springframework.org/schema/context/spring-context-4.0.xsd"> | ||
+ | |||
+ | <!-- 继承自AuthorizingRealm的自定义Realm,即指定Shiro验证用户登录的类为自定义的ManageAuthorizingRealm.java --> | ||
+ | <bean id="manageAuthorizingRealm" class="com.gxx.manage.shiro.ManageAuthorizingRealm" /> | ||
+ | |||
+ | <!-- Shiro默认会使用Servlet容器的Session,可通过sessionMode属性来指定使用Shiro原生Session --> | ||
+ | <!-- 即<property name="sessionMode" value="native"/>,详细说明见官方文档 --> | ||
+ | <!-- 这里主要是设置自定义的单Realm应用,若有多个Realm,可使用'realms'属性代替 --> | ||
+ | <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> | ||
+ | <property name="realm" ref="manageAuthorizingRealm" /> | ||
+ | <property name="sessionManager" ref="defaultWebSessionManager"/> | ||
+ | <property name="cacheManager" ref="customShiroCacheManager"/> | ||
+ | </bean> | ||
+ | |||
+ | <!-- default web session manager,session expire time 20 minutes --> | ||
+ | <bean id="defaultWebSessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager"> | ||
+ | <property name="globalSessionTimeout" value="1200000"/> | ||
+ | <property name="sessionDAO" ref="customShiroSessionDAO"/> | ||
+ | <property name="sessionIdCookie" ref="simpleCookie"/> | ||
+ | <!-- <property name="sessionListeners"> | ||
+ | <list> | ||
+ | <ref local="customSessionListener"/> | ||
+ | </list> | ||
+ | </property> --> | ||
+ | <property name="sessionValidationScheduler" ref="sessionValidationScheduler"/> | ||
+ | <property name="sessionValidationSchedulerEnabled" value="true"/> | ||
+ | <property name="deleteInvalidSessions" value="true"/> | ||
+ | </bean> | ||
+ | |||
+ | <bean id="sessionValidationScheduler" | ||
+ | class="org.apache.shiro.session.mgt.ExecutorServiceSessionValidationScheduler"> | ||
+ | <property name="sessionManager" ref="defaultWebSessionManager"/> | ||
+ | <property name="interval" value="1800000"/> | ||
+ | </bean> | ||
+ | |||
+ | <bean id="simpleCookie" class="org.apache.shiro.web.servlet.SimpleCookie"> | ||
+ | <constructor-arg index="0" value="JSESSIONID_COOKIE"/> | ||
+ | <property name="httpOnly" value="true"/> | ||
+ | </bean> | ||
+ | | ||
+ | <!-- custom sessionDAO,must use class impl CustomShiroSessionRepository interface --> | ||
+ | <bean id="customShiroSessionDAO" class="com.gxx.manage.shiro.session.CustomShiroSessionDAO"> | ||
+ | <property name="shiroSessionRepository" ref="jedisShiroSessionRepository"/> | ||
+ | </bean> | ||
+ | |||
+ | <!-- custom save session by redis class impl ShiroSessionRepository interface --> | ||
+ | <bean id="jedisShiroSessionRepository" class="com.gxx.manage.shiro.session.JedisShiroSessionRepository"> | ||
+ | <property name="jedisManager" ref="jedisManager"/> | ||
+ | </bean> | ||
+ | | ||
+ | <!-- redis manager class --> | ||
+ | <bean id="jedisManager" class="com.gxx.manage.shiro.JedisManager"> | ||
+ | <property name="jedisPool" ref="jedisPool"/> | ||
+ | </bean> | ||
+ | | ||
+ | <!-- redis pool --> | ||
+ | <bean id="jedisPool" class="redis.clients.jedis.JedisPool"> | ||
+ | <constructor-arg index="0" ref="poolConfig"/> | ||
+ | <constructor-arg index="1" value="${redis.host}"/> | ||
+ | <constructor-arg index="2" value="${redis.port}"/> | ||
+ | <constructor-arg index="3" value="2000"/> | ||
+ | <constructor-arg index="4" value="${redis.pass}"/> | ||
+ | <constructor-arg index="5" value="0"/> | ||
+ | </bean> | ||
+ | | ||
+ | <!-- custom shiro authorization info cache manager class--> | ||
+ | <bean id="customShiroCacheManager" class="com.gxx.manage.shiro.cache.CustomShiroCacheManager"> | ||
+ | <property name="shiroCacheManager" ref="jedisShiroCacheManager"/> | ||
+ | </bean> | ||
+ | | ||
+ | <!-- ShiroCacheManager interface impl class by redis,save authorization info to redis --> | ||
+ | <bean id="jedisShiroCacheManager" class="com.gxx.manage.shiro.cache.JedisShiroCacheManager"> | ||
+ | <property name="jedisManager" ref="jedisManager"/> | ||
+ | </bean> | ||
+ | |||
+ | <!-- Shiro主过滤器本身功能十分强大,其强大之处就在于它支持任何基于URL路径表达式的、自定义的过滤器的执行 --> | ||
+ | <!-- Web应用中,Shiro可控制的Web请求必须经过Shiro主过滤器的拦截,Shiro对基于Spring的Web应用提供了完美的支持 --> | ||
+ | <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> | ||
+ | <!-- Shiro的核心安全接口,这个属性是必须的 --> | ||
+ | <property name="securityManager" ref="securityManager" /> | ||
+ | <!-- 要求登录时的链接(可根据项目的URL进行替换),非必须的属性,默认会自动寻找Web工程根目录下的"/login.jsp"页面 --> | ||
+ | <property name="loginUrl" value="/login.htm" /> | ||
+ | <!-- 登录成功后要跳转的连接(本例中此属性用不到,因为登录成功后的处理逻辑在LoginController里硬编码为main.jsp了) --> | ||
+ | <!-- <property name="successUrl" value="/system/main"/> --> | ||
+ | <!-- 用户访问未对其授权的资源时,所显示的连接 --> | ||
+ | <!-- 若想更明显的测试此属性可以修改它的值,如unauthor.jsp,然后用[玄玉]登录后访问/admin/listUser.jsp就看见浏览器会显示unauthor.jsp --> | ||
+ | <property name="unauthorizedUrl" value="/unauthorized.htm" /> | ||
+ | <!-- 过滤器定义 --> | ||
+ | <property name="filters"> | ||
+ | <map> | ||
+ | <entry key="perms" value-ref="urlPermissionsFilter" /> | ||
+ | </map> | ||
+ | </property> | ||
+ | <!-- Shiro连接约束配置,即过滤链的定义 --> | ||
+ | <!-- 此处可配合我的这篇文章来理解各个过滤连的作用http://blog.csdn.net/jadyer/article/details/12172839 --> | ||
+ | <!-- 下面value值的第一个'/'代表的路径是相对于HttpServletRequest.getContextPath()的值来的 --> | ||
+ | <!-- anon:它对应的过滤器里面是空的,什么都没做,这里.do和.jsp后面的*表示参数,比方说login.jsp?main这种 --> | ||
+ | <!-- authc:该过滤器下的页面必须验证后才能访问,它是Shiro内置的一个拦截器org.apache.shiro.web.filter.authc.FormAuthenticationFilter --> | ||
+ | <property name="filterChainDefinitions"> | ||
+ | <value> | ||
+ | /login.htm=anon | ||
+ | /logout.htm=logout | ||
+ | /unauthorized.htm=anon | ||
+ | /** = authc,perms | ||
+ | </value> | ||
+ | </property> | ||
+ | </bean> | ||
+ | |||
+ | <!-- 自定义鉴权拦截器 --> | ||
+ | <!-- 基于URL的权限判断过滤器 我们自动根据URL产生所谓的权限字符串,这一项在Shiro示例中是写在配置文件里面的,默认认为权限不可动态配置 --> | ||
+ | <bean id="urlPermissionsFilter" class="com.gxx.manage.shiro.UrlPermissionsFilter" /> | ||
+ | |||
+ | <!-- 保证实现了Shiro内部lifecycle函数的bean执行 --> | ||
+ | <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor" /> | ||
+ | |||
+ | <!-- 开启Shiro的注解(如@RequiresRoles,@RequiresPermissions),需借助SpringAOP扫描使用Shiro注解的类,并在必要时进行安全逻辑验证 --> | ||
+ | <!-- 配置以下两个bean即可实现此功能 --> | ||
+ | <!-- Enable Shiro Annotations for Spring-configured beans. Only run after | ||
+ | the lifecycleBeanProcessor has run --> | ||
+ | <!-- 由于本例中并未使用Shiro注解,故注释掉这两个bean(个人觉得将权限通过注解的方式硬编码在程序中,查看起来不是很方便,没必要使用) --> | ||
+ | <!-- | ||
+ | <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" | ||
+ | depends-on="lifecycleBeanPostProcessor"/> | ||
+ | <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor"> | ||
+ | <property name="securityManager" ref="securityManager"/> | ||
+ | </bean> | ||
+ | --> | ||
+ | </beans> | ||
+ | </code> | ||
+ | ===== SerializeUtil.java ===== | ||
+ | <code java SerializeUtil.java> | ||
+ | package com.gxx.manage.shiro; | ||
+ | |||
+ | import java.io.*; | ||
+ | |||
+ | /** | ||
+ | * <dl> | ||
+ | * <dt><b>Title:</b></dt> | ||
+ | * <dd> | ||
+ | * 序列化工具类 | ||
+ | * </dd> | ||
+ | * <dt><b>Description:</b></dt> | ||
+ | * <dd> | ||
+ | * <p>none | ||
+ | * </dd> | ||
+ | * </dl> | ||
+ | * | ||
+ | * @author Administrator | ||
+ | * @version 1.0, 2015年9月22日 | ||
+ | * @since manage | ||
+ | * | ||
+ | */ | ||
+ | public class SerializeUtil { | ||
+ | |||
+ | public static byte[] serialize(Object value) { | ||
+ | if (value == null) { | ||
+ | throw new NullPointerException("Can't serialize null"); | ||
+ | } | ||
+ | byte[] rv = null; | ||
+ | ByteArrayOutputStream bos = null; | ||
+ | ObjectOutputStream os = null; | ||
+ | try { | ||
+ | bos = new ByteArrayOutputStream(); | ||
+ | os = new ObjectOutputStream(bos); | ||
+ | os.writeObject(value); | ||
+ | os.close(); | ||
+ | bos.close(); | ||
+ | rv = bos.toByteArray(); | ||
+ | } catch (Exception e) { | ||
+ | e.printStackTrace(); | ||
+ | System.out.println("serialize error"); | ||
+ | } finally { | ||
+ | close(os); | ||
+ | close(bos); | ||
+ | } | ||
+ | return rv; | ||
+ | } | ||
+ | |||
+ | public static Object deserialize(byte[] in) { | ||
+ | return deserialize(in, Object.class); | ||
+ | } | ||
+ | |||
+ | @SuppressWarnings("unchecked") | ||
+ | public static <T> T deserialize(byte[] in, Class<T> requiredType) { | ||
+ | Object rv = null; | ||
+ | ByteArrayInputStream bis = null; | ||
+ | ObjectInputStream is = null; | ||
+ | try { | ||
+ | if (in != null) { | ||
+ | bis = new ByteArrayInputStream(in); | ||
+ | is = new ObjectInputStream(bis); | ||
+ | rv = is.readObject(); | ||
+ | } | ||
+ | } catch (Exception e) { | ||
+ | e.printStackTrace(); | ||
+ | System.out.println("deserialize error"); | ||
+ | } finally { | ||
+ | close(is); | ||
+ | close(bis); | ||
+ | } | ||
+ | return (T) rv; | ||
+ | } | ||
+ | |||
+ | private static void close(Closeable closeable) { | ||
+ | if (closeable != null) | ||
+ | try { | ||
+ | closeable.close(); | ||
+ | } catch (IOException e) { | ||
+ | e.printStackTrace(); | ||
+ | System.out.println("close stream error"); | ||
+ | } | ||
+ | } | ||
+ | |||
+ | } | ||
+ | </code> | ||
+ | ===== JedisManager.java ===== | ||
+ | <code java JedisManager.java> | ||
+ | package com.gxx.manage.shiro; | ||
+ | |||
+ | import redis.clients.jedis.Jedis; | ||
+ | import redis.clients.jedis.JedisPool; | ||
+ | import redis.clients.jedis.JedisPoolConfig; | ||
+ | import redis.clients.jedis.exceptions.JedisConnectionException; | ||
+ | |||
+ | /** | ||
+ | * <dl> | ||
+ | * <dt><b>Title:</b></dt> | ||
+ | * <dd> | ||
+ | * Jedis工具类 | ||
+ | * </dd> | ||
+ | * <dt><b>Description:</b></dt> | ||
+ | * <dd> | ||
+ | * <p>none | ||
+ | * </dd> | ||
+ | * </dl> | ||
+ | * | ||
+ | * @author Administrator | ||
+ | * @version 1.0, 2015年9月22日 | ||
+ | * @since manage | ||
+ | * | ||
+ | */ | ||
+ | public class JedisManager { | ||
+ | |||
+ | private JedisPool jedisPool; | ||
+ | |||
+ | public Jedis getJedis() { | ||
+ | Jedis jedis = null; | ||
+ | try { | ||
+ | jedis = getJedisPool().getResource(); | ||
+ | } catch (Exception e) { | ||
+ | throw new JedisConnectionException(e); | ||
+ | } | ||
+ | return jedis; | ||
+ | } | ||
+ | |||
+ | public void returnResource(Jedis jedis, boolean isBroken) { | ||
+ | if (jedis == null) | ||
+ | return; | ||
+ | if (isBroken) | ||
+ | getJedisPool().returnBrokenResource(jedis); | ||
+ | else | ||
+ | getJedisPool().returnResource(jedis); | ||
+ | } | ||
+ | |||
+ | public byte[] getValueByKey(int dbIndex, byte[] key) throws Exception { | ||
+ | Jedis jedis = null; | ||
+ | byte[] result = null; | ||
+ | boolean isBroken = false; | ||
+ | try { | ||
+ | jedis = getJedis(); | ||
+ | jedis.select(dbIndex); | ||
+ | result = jedis.get(key); | ||
+ | } catch (Exception e) { | ||
+ | isBroken = true; | ||
+ | throw e; | ||
+ | } finally { | ||
+ | returnResource(jedis, isBroken); | ||
+ | } | ||
+ | return result; | ||
+ | } | ||
+ | |||
+ | public void deleteByKey(int dbIndex, byte[] key) throws Exception { | ||
+ | Jedis jedis = null; | ||
+ | boolean isBroken = false; | ||
+ | try { | ||
+ | jedis = getJedis(); | ||
+ | jedis.select(dbIndex); | ||
+ | jedis.del(key); | ||
+ | } catch (Exception e) { | ||
+ | isBroken = true; | ||
+ | throw e; | ||
+ | } finally { | ||
+ | returnResource(jedis, isBroken); | ||
+ | } | ||
+ | } | ||
+ | |||
+ | public void saveValueByKey(int dbIndex, byte[] key, byte[] value, int expireTime) | ||
+ | throws Exception { | ||
+ | Jedis jedis = null; | ||
+ | boolean isBroken = false; | ||
+ | try { | ||
+ | jedis = getJedis(); | ||
+ | jedis.select(dbIndex); | ||
+ | jedis.set(key, value); | ||
+ | if (expireTime > 0) | ||
+ | jedis.expire(key, expireTime); | ||
+ | } catch (Exception e) { | ||
+ | isBroken = true; | ||
+ | throw e; | ||
+ | } finally { | ||
+ | returnResource(jedis, isBroken); | ||
+ | } | ||
+ | } | ||
+ | |||
+ | public JedisPool getJedisPool() { | ||
+ | return jedisPool; | ||
+ | } | ||
+ | |||
+ | public void setJedisPool(JedisPool jedisPool) { | ||
+ | /** | ||
+ | * 配置文件注入有问题,这里手动创建 | ||
+ | * TODO | ||
+ | */ | ||
+ | JedisPoolConfig config = new JedisPoolConfig(); | ||
+ | config.setMaxTotal(1000); | ||
+ | config.setMaxIdle(600); | ||
+ | config.setMinIdle(300); | ||
+ | config.setTestOnBorrow(true); | ||
+ | JedisPool pool = new JedisPool(config, "121.43.104.34", 6379, 20000); | ||
+ | this.jedisPool = pool; | ||
+ | } | ||
+ | } | ||
+ | </code> | ||
+ | ===== CustomShiroSessionDAO.java ===== | ||
+ | <code java CustomShiroSessionDAO.java> | ||
+ | package com.gxx.manage.shiro.session; | ||
+ | |||
+ | import org.apache.shiro.session.Session; | ||
+ | import org.apache.shiro.session.UnknownSessionException; | ||
+ | import org.apache.shiro.session.mgt.eis.AbstractSessionDAO; | ||
+ | |||
+ | import java.io.Serializable; | ||
+ | import java.util.Collection; | ||
+ | |||
+ | /** | ||
+ | * <dl> | ||
+ | * <dt><b>Title:</b></dt> | ||
+ | * <dd> | ||
+ | * 自定义shiro会话DAO | ||
+ | * </dd> | ||
+ | * <dt><b>Description:</b></dt> | ||
+ | * <dd> | ||
+ | * <p>none | ||
+ | * </dd> | ||
+ | * </dl> | ||
+ | * | ||
+ | * @author Administrator | ||
+ | * @version 1.0, 2015年9月22日 | ||
+ | * @since manage | ||
+ | * | ||
+ | */ | ||
+ | public class CustomShiroSessionDAO extends AbstractSessionDAO { | ||
+ | |||
+ | private ShiroSessionRepository shiroSessionRepository; | ||
+ | |||
+ | @Override | ||
+ | public void update(Session session) throws UnknownSessionException { | ||
+ | getShiroSessionRepository().saveSession(session); | ||
+ | } | ||
+ | |||
+ | @Override | ||
+ | public void delete(Session session) { | ||
+ | if (session == null) { | ||
+ | return; | ||
+ | } | ||
+ | Serializable id = session.getId(); | ||
+ | if (id != null) { | ||
+ | getShiroSessionRepository().deleteSession(id); | ||
+ | } | ||
+ | //TODO if session is too large,when session destory clear shiro cache | ||
+ | } | ||
+ | |||
+ | @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 ShiroSessionRepository getShiroSessionRepository() { | ||
+ | return shiroSessionRepository; | ||
+ | } | ||
+ | |||
+ | public void setShiroSessionRepository( | ||
+ | ShiroSessionRepository shiroSessionRepository) { | ||
+ | this.shiroSessionRepository = shiroSessionRepository; | ||
+ | } | ||
+ | |||
+ | } | ||
+ | </code> | ||
+ | ===== JedisShiroSessionRepository.java ===== | ||
+ | <code java JedisShiroSessionRepository.java> | ||
+ | package com.gxx.manage.shiro.session; | ||
+ | |||
+ | import java.io.Serializable; | ||
+ | import java.util.Collection; | ||
+ | |||
+ | import org.apache.shiro.session.Session; | ||
+ | |||
+ | import com.gxx.manage.shiro.JedisManager; | ||
+ | import com.gxx.manage.shiro.SerializeUtil; | ||
+ | |||
+ | /** | ||
+ | * <dl> | ||
+ | * <dt><b>Title:</b></dt> | ||
+ | * <dd> | ||
+ | * Jedis实现shiro会话管理接口 | ||
+ | * </dd> | ||
+ | * <dt><b>Description:</b></dt> | ||
+ | * <dd> | ||
+ | * <p>none | ||
+ | * </dd> | ||
+ | * </dl> | ||
+ | * | ||
+ | * @author Administrator | ||
+ | * @version 1.0, 2015年9月22日 | ||
+ | * @since manage | ||
+ | * | ||
+ | */ | ||
+ | public class JedisShiroSessionRepository implements ShiroSessionRepository { | ||
+ | |||
+ | private static final String REDIS_SHIRO_SESSION = "shiro-session:"; | ||
+ | private static final int SESSION_VAL_TIME_SPAN = 18000; | ||
+ | private static final int DB_INDEX = 0; | ||
+ | |||
+ | private JedisManager jedisManager; | ||
+ | |||
+ | @Override | ||
+ | public void saveSession(Session session) { | ||
+ | if (session == null || session.getId() == null) | ||
+ | throw new NullPointerException("session is empty"); | ||
+ | try { | ||
+ | byte[] key = SerializeUtil.serialize(buildRedisSessionKey(session.getId())); | ||
+ | byte[] value = SerializeUtil.serialize(session); | ||
+ | long sessionTimeOut = session.getTimeout() / 1000; | ||
+ | Long expireTime = sessionTimeOut + SESSION_VAL_TIME_SPAN + (5 * 60); | ||
+ | getJedisManager().saveValueByKey(DB_INDEX, key, value, expireTime.intValue()); | ||
+ | } catch (Exception e) { | ||
+ | e.printStackTrace(); | ||
+ | } | ||
+ | } | ||
+ | |||
+ | @Override | ||
+ | public void deleteSession(Serializable id) { | ||
+ | if (id == null) { | ||
+ | throw new NullPointerException("session id is empty"); | ||
+ | } | ||
+ | try { | ||
+ | getJedisManager().deleteByKey(DB_INDEX, | ||
+ | SerializeUtil.serialize(buildRedisSessionKey(id))); | ||
+ | } catch (Exception e) { | ||
+ | e.printStackTrace(); | ||
+ | } | ||
+ | } | ||
+ | |||
+ | @Override | ||
+ | public Session getSession(Serializable id) { | ||
+ | if (id == null) | ||
+ | throw new NullPointerException("session id is empty"); | ||
+ | Session session = null; | ||
+ | try { | ||
+ | byte[] value = getJedisManager().getValueByKey(DB_INDEX, SerializeUtil | ||
+ | .serialize(buildRedisSessionKey(id))); | ||
+ | session = SerializeUtil.deserialize(value, Session.class); | ||
+ | } catch (Exception e) { | ||
+ | e.printStackTrace(); | ||
+ | } | ||
+ | return session; | ||
+ | } | ||
+ | |||
+ | @Override | ||
+ | public Collection<Session> getAllSessions() { | ||
+ | //TODO | ||
+ | return null; | ||
+ | } | ||
+ | |||
+ | private String buildRedisSessionKey(Serializable sessionId) { | ||
+ | return REDIS_SHIRO_SESSION + sessionId; | ||
+ | } | ||
+ | |||
+ | public JedisManager getJedisManager() { | ||
+ | return jedisManager; | ||
+ | } | ||
+ | |||
+ | public void setJedisManager(JedisManager jedisManager) { | ||
+ | this.jedisManager = jedisManager; | ||
+ | } | ||
+ | } | ||
+ | </code> | ||
+ | ===== ShiroSessionRepository.java ===== | ||
+ | <code java ShiroSessionRepository.java> | ||
+ | package com.gxx.manage.shiro.session; | ||
+ | |||
+ | import org.apache.shiro.session.Session; | ||
+ | |||
+ | import java.io.Serializable; | ||
+ | import java.util.Collection; | ||
+ | |||
+ | /** | ||
+ | * <dl> | ||
+ | * <dt><b>Title:</b></dt> | ||
+ | * <dd> | ||
+ | * 自定义shiro会话管理接口 | ||
+ | * </dd> | ||
+ | * <dt><b>Description:</b></dt> | ||
+ | * <dd> | ||
+ | * <p>none | ||
+ | * </dd> | ||
+ | * </dl> | ||
+ | * | ||
+ | * @author Administrator | ||
+ | * @version 1.0, 2015年9月22日 | ||
+ | * @since manage | ||
+ | * | ||
+ | */ | ||
+ | public interface ShiroSessionRepository { | ||
+ | |||
+ | void saveSession(Session session); | ||
+ | |||
+ | void deleteSession(Serializable sessionId); | ||
+ | |||
+ | Session getSession(Serializable sessionId); | ||
+ | |||
+ | Collection<Session> getAllSessions(); | ||
+ | } | ||
+ | </code> | ||
+ | ===== CustomShiroCacheManager.java ===== | ||
+ | <code java CustomShiroCacheManager.java> | ||
+ | package com.gxx.manage.shiro.cache; | ||
+ | |||
+ | import org.apache.shiro.cache.Cache; | ||
+ | import org.apache.shiro.cache.CacheException; | ||
+ | import org.apache.shiro.cache.CacheManager; | ||
+ | import org.apache.shiro.util.Destroyable; | ||
+ | |||
+ | /** | ||
+ | * <dl> | ||
+ | * <dt><b>Title:</b></dt> | ||
+ | * <dd> | ||
+ | * shiro缓存管理类 | ||
+ | * </dd> | ||
+ | * <dt><b>Description:</b></dt> | ||
+ | * <dd> | ||
+ | * <p>none | ||
+ | * </dd> | ||
+ | * </dl> | ||
+ | * | ||
+ | * @author Administrator | ||
+ | * @version 1.0, 2015年9月22日 | ||
+ | * @since manage | ||
+ | * | ||
+ | */ | ||
+ | public class CustomShiroCacheManager implements CacheManager, Destroyable { | ||
+ | |||
+ | private ShiroCacheManager shiroCacheManager; | ||
+ | |||
+ | @Override | ||
+ | public <K, V> Cache<K, V> getCache(String name) throws CacheException { | ||
+ | return getShiroCacheManager().getCache(name); | ||
+ | } | ||
+ | |||
+ | @Override | ||
+ | public void destroy() throws Exception { | ||
+ | shiroCacheManager.destroy(); | ||
+ | } | ||
+ | |||
+ | public ShiroCacheManager getShiroCacheManager() { | ||
+ | return shiroCacheManager; | ||
+ | } | ||
+ | |||
+ | public void setShiroCacheManager(ShiroCacheManager shiroCacheManager) { | ||
+ | this.shiroCacheManager = shiroCacheManager; | ||
+ | } | ||
+ | |||
+ | } | ||
+ | </code> | ||
+ | ===== JedisShiroCache.java ===== | ||
+ | <code java JedisShiroCache.java> | ||
+ | package com.gxx.manage.shiro.cache; | ||
+ | |||
+ | import java.util.Collection; | ||
+ | import java.util.Set; | ||
+ | |||
+ | import org.apache.shiro.cache.Cache; | ||
+ | import org.apache.shiro.cache.CacheException; | ||
+ | |||
+ | import com.gxx.manage.shiro.JedisManager; | ||
+ | import com.gxx.manage.shiro.SerializeUtil; | ||
+ | |||
+ | /** | ||
+ | * <dl> | ||
+ | * <dt><b>Title:</b></dt> | ||
+ | * <dd> | ||
+ | * Jedis实现Shiro缓存 | ||
+ | * </dd> | ||
+ | * <dt><b>Description:</b></dt> | ||
+ | * <dd> | ||
+ | * <p>none | ||
+ | * </dd> | ||
+ | * </dl> | ||
+ | * | ||
+ | * @author Administrator | ||
+ | * @version 1.0, 2015年9月22日 | ||
+ | * @since manage | ||
+ | * | ||
+ | */ | ||
+ | public class JedisShiroCache<K, V> implements Cache<K, V> { | ||
+ | |||
+ | private static final String REDIS_SHIRO_CACHE = "shiro-cache:"; | ||
+ | private static final int DB_INDEX = 1; | ||
+ | |||
+ | private JedisManager jedisManager; | ||
+ | |||
+ | private String name; | ||
+ | |||
+ | public JedisShiroCache(String name, JedisManager jedisManager) { | ||
+ | this.name = name; | ||
+ | this.jedisManager = jedisManager; | ||
+ | } | ||
+ | |||
+ | /** | ||
+ | * 自定义relm中的授权/认证的类名加上授权/认证英文名字 | ||
+ | */ | ||
+ | public String getName() { | ||
+ | if (name == null) | ||
+ | return ""; | ||
+ | return name; | ||
+ | } | ||
+ | |||
+ | public void setName(String name) { | ||
+ | this.name = name; | ||
+ | } | ||
+ | |||
+ | @SuppressWarnings("unchecked") | ||
+ | @Override | ||
+ | public V get(K key) throws CacheException { | ||
+ | byte[] byteKey = SerializeUtil.serialize(buildCacheKey(key)); | ||
+ | byte[] byteValue = new byte[0]; | ||
+ | try { | ||
+ | byteValue = jedisManager.getValueByKey(DB_INDEX, byteKey); | ||
+ | } catch (Exception e) { | ||
+ | e.printStackTrace(); | ||
+ | System.out.println("get cache error"); | ||
+ | } | ||
+ | return (V) SerializeUtil.deserialize(byteValue); | ||
+ | } | ||
+ | |||
+ | @Override | ||
+ | public V put(K key, V value) throws CacheException { | ||
+ | V previos = get(key); | ||
+ | try { | ||
+ | jedisManager.saveValueByKey(DB_INDEX, SerializeUtil.serialize(buildCacheKey(key)), | ||
+ | SerializeUtil.serialize(value), -1); | ||
+ | } catch (Exception e) { | ||
+ | e.printStackTrace(); | ||
+ | System.out.println("put cache error"); | ||
+ | } | ||
+ | return previos; | ||
+ | } | ||
+ | |||
+ | @Override | ||
+ | public V remove(K key) throws CacheException { | ||
+ | V previos = get(key); | ||
+ | try { | ||
+ | jedisManager.deleteByKey(DB_INDEX, SerializeUtil.serialize(buildCacheKey(key))); | ||
+ | } catch (Exception e) { | ||
+ | e.printStackTrace(); | ||
+ | System.out.println("remove cache error"); | ||
+ | } | ||
+ | return previos; | ||
+ | } | ||
+ | |||
+ | @Override | ||
+ | public void clear() throws CacheException { | ||
+ | //TODO | ||
+ | } | ||
+ | |||
+ | @Override | ||
+ | public int size() { | ||
+ | if (keys() == null) | ||
+ | return 0; | ||
+ | return keys().size(); | ||
+ | } | ||
+ | |||
+ | @Override | ||
+ | public Set<K> keys() { | ||
+ | //TODO | ||
+ | return null; | ||
+ | } | ||
+ | |||
+ | @Override | ||
+ | public Collection<V> values() { | ||
+ | //TODO | ||
+ | return null; | ||
+ | } | ||
+ | |||
+ | private String buildCacheKey(Object key) { | ||
+ | return REDIS_SHIRO_CACHE + getName() + ":" + key; | ||
+ | } | ||
+ | |||
+ | } | ||
+ | </code> | ||
+ | ===== JedisShiroCacheManager.java ===== | ||
+ | <code java JedisShiroCacheManager.java> | ||
+ | package com.gxx.manage.shiro.cache; | ||
+ | |||
+ | import org.apache.shiro.cache.Cache; | ||
+ | |||
+ | import com.gxx.manage.shiro.JedisManager; | ||
+ | |||
+ | /** | ||
+ | * <dl> | ||
+ | * <dt><b>Title:</b></dt> | ||
+ | * <dd> | ||
+ | * Jedis实现shiro缓存管理接口 | ||
+ | * </dd> | ||
+ | * <dt><b>Description:</b></dt> | ||
+ | * <dd> | ||
+ | * <p>none | ||
+ | * </dd> | ||
+ | * </dl> | ||
+ | * | ||
+ | * @author Administrator | ||
+ | * @version 1.0, 2015年9月22日 | ||
+ | * @since manage | ||
+ | * | ||
+ | */ | ||
+ | public class JedisShiroCacheManager implements ShiroCacheManager { | ||
+ | |||
+ | private JedisManager jedisManager; | ||
+ | |||
+ | @Override | ||
+ | public <K, V> Cache<K, V> getCache(String name) { | ||
+ | return new JedisShiroCache<K, V>(name, getJedisManager()); | ||
+ | } | ||
+ | |||
+ | @Override | ||
+ | public void destroy() { | ||
+ | getJedisManager().getJedis().shutdown(); | ||
+ | } | ||
+ | |||
+ | public JedisManager getJedisManager() { | ||
+ | return jedisManager; | ||
+ | } | ||
+ | |||
+ | public void setJedisManager(JedisManager jedisManager) { | ||
+ | this.jedisManager = jedisManager; | ||
+ | } | ||
+ | } | ||
+ | </code> | ||
+ | ===== ShiroCacheManager.java ===== | ||
+ | <code java ShiroCacheManager.java> | ||
+ | package com.gxx.manage.shiro.cache; | ||
+ | |||
+ | import org.apache.shiro.cache.Cache; | ||
+ | |||
+ | /** | ||
+ | * <dl> | ||
+ | * <dt><b>Title:</b></dt> | ||
+ | * <dd> | ||
+ | * shiro缓存管理接口 | ||
+ | * </dd> | ||
+ | * <dt><b>Description:</b></dt> | ||
+ | * <dd> | ||
+ | * <p>none | ||
+ | * </dd> | ||
+ | * </dl> | ||
+ | * | ||
+ | * @author Administrator | ||
+ | * @version 1.0, 2015年9月22日 | ||
+ | * @since manage | ||
+ | * | ||
+ | */ | ||
+ | public interface ShiroCacheManager { | ||
+ | |||
+ | <K, V> Cache<K, V> getCache(String name); | ||
+ | |||
+ | void destroy(); | ||
+ | |||
+ | } | ||
+ | </code> | ||
+ | ===== SessionController.java ===== | ||
+ | <code java SessionController.java> | ||
+ | package com.gxx.manage.web.session; | ||
+ | |||
+ | import java.util.HashMap; | ||
+ | import java.util.Map; | ||
+ | |||
+ | import javax.servlet.http.HttpServletRequest; | ||
+ | |||
+ | import org.apache.log4j.Logger; | ||
+ | import org.apache.shiro.SecurityUtils; | ||
+ | import org.apache.shiro.session.Session; | ||
+ | import org.apache.shiro.subject.Subject; | ||
+ | import org.springframework.stereotype.Controller; | ||
+ | import org.springframework.web.bind.annotation.RequestMapping; | ||
+ | import org.springframework.web.bind.annotation.ResponseBody; | ||
+ | |||
+ | /** | ||
+ | * UserController | ||
+ | * | ||
+ | * @author gxx | ||
+ | */ | ||
+ | @Controller | ||
+ | @RequestMapping("/session/") | ||
+ | public class SessionController { | ||
+ | /** | ||
+ | * 日志处理器 | ||
+ | */ | ||
+ | private final Logger logger = Logger.getLogger(SessionController.class); | ||
+ | |||
+ | /** | ||
+ | * 设置session | ||
+ | * @param request | ||
+ | * @return | ||
+ | */ | ||
+ | @RequestMapping(value = "/set",produces="application/json") | ||
+ | public @ResponseBody Map<String, Object> set(HttpServletRequest request) { | ||
+ | logger.info("设置session:key=[" + request.getParameter("key") + "],value=[" + request.getParameter("value") + "]"); | ||
+ | /** | ||
+ | * 设置session | ||
+ | */ | ||
+ | Subject currentUser = SecurityUtils.getSubject(); | ||
+ | Session session = currentUser.getSession(); | ||
+ | session.setAttribute(request.getParameter("key"), request.getParameter("value")); | ||
+ | /** | ||
+ | * 返回结果 | ||
+ | */ | ||
+ | Map<String, Object> resultMap = new HashMap<String, Object>(); | ||
+ | resultMap.put("success", true); | ||
+ | return resultMap; | ||
+ | } | ||
+ | |||
+ | /** | ||
+ | * 获取session | ||
+ | * @param request | ||
+ | * @return | ||
+ | */ | ||
+ | @RequestMapping(value = "/get",produces="application/json") | ||
+ | public @ResponseBody Map<String, Object> get(HttpServletRequest request) { | ||
+ | logger.info("获取session:key=[" + request.getParameter("key") + "]"); | ||
+ | /** | ||
+ | * 设置session | ||
+ | */ | ||
+ | Subject currentUser = SecurityUtils.getSubject(); | ||
+ | Session session = currentUser.getSession(); | ||
+ | Object value = session.getAttribute(request.getParameter("key")); | ||
+ | logger.info("获取session:value=[" + value + "]"); | ||
+ | /** | ||
+ | * 返回结果 | ||
+ | */ | ||
+ | Map<String, Object> resultMap = new HashMap<String, Object>(); | ||
+ | resultMap.put("success", true); | ||
+ | resultMap.put("value", value); | ||
+ | return resultMap; | ||
+ | } | ||
+ | } | ||
+ | </code> | ||
+ | ===== 数据库数据截图 ===== | ||
+ | {{:分享:技术:shiro:permission_1.png?300|}} | ||
+ | {{:分享:技术:shiro:role_permission_1.png?300|}} | ||
+ | ===== 代码操作 ===== | ||
+ | 原来操作session是: | ||
+ | request.getSession().setAttribute("K","V"); | ||
+ | request.getSession().getAttribute("K"); | ||
+ | shiro操作session是: | ||
+ | Subject currentUser = SecurityUtils.getSubject(); | ||
+ | Session session = currentUser.getSession(); | ||
+ | session.setAttribute("K","V"); | ||
+ | session.getAttribute("K"); | ||
+ | ===== 无状态节点重启依然保存会话 ===== | ||
+ | 有了shiro之后,会话的存取就不依赖容器了,各个应用节点就是无状态的,所有的会话存储都在redis中,即使容器挂掉,重启之后,用户从redis中读取会话信息依然可以保持登录状态以及获取之前设置的session数据,只要redis不挂(redis可以本地持久化,如果有必要redis还可以做集群)。 | ||
+ | ===== 不同节点共享会话 ===== | ||
+ | 同一机器下,不同端口P1和P2两个tomcat T1和T2,都部署相同的应用manage,指向同一个redis。T1访问登录成功,T2可以直接访问登录后页面;T1设置session的键Key和值Value,T2可以直接通过键Key取到对应的值Value。 |