Shiro-分布式下的解决方案及其实现
来源:互联网 发布:人工智能与信贷的结合 编辑:程序博客网 时间:2024/05/21 01:43
1.当项目采用Shiro之后,对于分布式的多台服务器间session不会共享,这会造成去每台服务器都会重新登录,并且很有可能造成当用户权限更改后,多台服务器权限不一致的问题(不想这么麻烦的话,也可以采用一致性哈希解决问题)
2.为解决这个问题,我采用了Redis进行ShiroSession共享。本文将着重分析,怎样在分布式下,集成Shiro
3.读本文前,需要对Shiro进行深入了解.
需要重写的类有哪些,为什么要进行重写
1.重写SimpleSession,因为我需要对session进行自定义的更细处理,避免每次请求,都需更新或创建session,大家也可按照自己的逻辑进行更改.另外为了能让shiro创建的是我们自定义的session,我们需要对
SessionFactory进行实现和shiro内进行相应的配置.
/** * shiro session 工厂 创建全局化session * * @author william_zhong * @version 1.5.0 * @time 2017-6-19 **/@Componentpublic class CsxShiroSessionFactory implements SessionFactory { @Override public Session createSession(SessionContext paramSessionContext) { CsxSession session = new CsxSession(); return session; }}
//shiro内的xml如下 <!-- 自定义全局session --> <bean id="shiroSessionFactory" class="com.csx.shiro.CsxShiroSessionFactory" />
2.重写AuthorizingRealm和AuthorizationFilter这个就不说了,懂得都懂
3.重写WebSessionManager,目的是为了每次请求时,都能进入到我自定义的session会话管理器中。由于移动端是采用登录凭证进行访问的,所以我需要对获取的登录凭证进行自己的解密处理
/**** * 重写shiro session管理 * * @author william_zhong * @version 1.5.0 * @time 2017-6-19 * ***/public class CsxAppSessionMannager extends DefaultWebSessionManager { private final Log log = LogFactory.getLog("CsxAppSessionMannager.class"); public CsxAppSessionMannager() { super(); } @Override protected Serializable getSessionId(ServletRequest request, ServletResponse response) { log.info("会话管理开始:获取sessionId"); try { // 获取token String token = request.getParameter(ShiroConstant.tokenContanst); if (StringUtils.isNotEmpty(token)) { // 解析加密的token,获取sessionid String jSESSIONID = null; String userId = RSAsecurity.DecryptStr(token.trim().replaceAll(" ", "+")); // 获取解密后的用户id jSESSIONID = ShiroRedisPool.getObject(String.class,ShiroRedisPool.shiroUserIdKey + userId); if (jSESSIONID != null) { request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, ShiroHttpServletRequest.URL_SESSION_ID_SOURCE); // session来源--url request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, jSESSIONID); request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE); } return jSESSIONID; } } catch (ShiroCustomizeException e) { // 跳转至登录页面进行登录 log.error("会话管理失败原因为:" + e); } return super.getSessionId(request, response); }}
4.重写会话持久层,只需继承CachingSessionDAO或EnterpriseCacheSessionDAO即可,值得注意的是,我是禁用了配置内的本地缓存,目的是为了分布式的多台服务器之间的session同步.
/*** * shiro 自定义会话持久层 继承CachingSessionDAO即可 * * @author william_zhong * @version 1.5.0 * @time 2017-6-14 ***/public class ShiroSessionCustomizeDao extends EnterpriseCacheSessionDAO { private final Log log = LogFactory.getLog("ShiroSessionCustomizeDao.class"); /***** * 创建session,保存到数据库 * * @author william_zhong * @version 1.5.0 * @time 2017-6-15 *****/ @Override protected Serializable doCreate(Session session) { log.info("第一次初始化session" + session); // 自定义获取sessionId Serializable sessionId = super.doCreate(session); // 第一次不存储至redis中 return sessionId; } // 获取session @Override protected Session doReadSession(Serializable sessionId) { // 先从缓存中获取session,如果没有再去数据库中获取 // Session session = super.doReadSession(sessionId); log.info("读取session内容"); Session session = null; try { session = ShiroRedisPool.getSessionToRedis(ShiroRedisPool.shiroUserLoginKey + sessionId.toString()); } catch (NullPointerException e) { log.info("shirosession 获取为空"); } return session; } /** * 更新session的最后一次访问时间 * * @author william_zhong * @version 1.5.0 * @time 2017-6-16 * ***/ @Override protected void doUpdate(Session session) { // super.doUpdate(session);从本地读,但是分布式我禁用了cahce,统一从redis获取 log.info("更新session"); if (session instanceof CsxSession) { CsxSession csxSession = (CsxSession) session; if (csxSession.getIsEffectiveFlag()) { // 若是存储选项,则执行存储操作 if (csxSession.getIsSaveFlag()&&csxSession.getAttribute("userId")!=null) { log.info("对session重新赋值"); csxSession.setIsSaveFlag(false); ShiroRedisPool.setObject(ShiroRedisPool.shiroUserIdKey + csxSession.getAttribute("userId"), session.getId().toString(), ReadProperties.getSHIRO_SESSION_TIMEOUT()); ShiroRedisPool.setSessionToredis(ShiroRedisPool.shiroUserLoginKey + session.getId().toString(), Base64Util.objectToString(csxSession), ReadProperties.getSHIRO_SESSION_TIMEOUT()); } else { ShiroRedisPool.setObject(ShiroRedisPool.shiroUserIdKey + csxSession.getUserId(), session.getId().toString(), ReadProperties.getSHIRO_SESSION_TIMEOUT()); ShiroRedisPool.updateExtensionTime(ShiroRedisPool.shiroUserLoginKey + session.getId().toString(), ReadProperties.getSHIRO_SESSION_TIMEOUT()); log.info("只更新时间"); } } } } @Override public void update(Session session) { this.doUpdate(session); } // 删除session @Override protected void doDelete(Session session) { // super.doDelete(session); log.info("删除session"); ShiroRedisPool.delObject(ShiroRedisPool.shiroUserLoginKey + session.getId().toString()); }}
配置如下
<!-- 安全管理器 --> <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> <property name="realm" ref="csxAppAuthorizingRealm" /> <property name="sessionManager" ref="sessionManager" /> <property name="cacheManager" ref="shiroCacheManager" /> </bean>
<!-- 会话管理器 --> <bean id="sessionManager" class="com.csx.shiro.CsxAppSessionMannager"> <property name="globalSessionTimeout" value="2592000000" /> <property name="deleteInvalidSessions" value="true" /> <property name="sessionFactory" ref="shiroSessionFactory" /> <!-- 会话验证调度器 采用quartz检测会话是否过时,由于我们采用了redis,自带定时销毁所以不用 --> <property name="sessionValidationSchedulerEnabled" value="false" /> <property name="sessionDAO" ref="shiroSessionCustomizeDao" /> <!-- 是否启用/禁用Session Id Cookie,默认是启用的;如果禁用后将不会设置Session Id Cookie,即默认使用了Servlet容器的JSESSIONID,且通过URL重写(URL中的“;JSESSIONID=id”部分)保存Session Id --> <property name="sessionIdCookieEnabled" value="false" /> <property name="sessionListeners" ref="shiroSessionListener" /> </bean>
<!-- 项目自定义的Realm --> <bean id="csxAppAuthorizingRealm" class="com.csx.shiro.CsxAppAuthorizingRealm" />
5.重写报错的shiro异常,目的是为了更好的用户体验
自定义的异常
/*** * shiro 自定义异常 * * 1.解析token 失败,请重新登录 * 2.用户信息过期,请重新登录 * 3.用户权限不够,请切换账户 * * @author william_zhong * @version 1.5.0 * @time 2017-6-16 * ***/public class ShiroCustomizeException extends Exception { private static final long serialVersionUID = -2777701677658086556L; public static final String tokenParseFailMSG = "解析用户信息失败,请重新登录"; public static final String userInformationExpiredMSG="用户信息过期,请重新登录"; public static final String userInsufficientRightsMSG="用户权限不够,请切换账号"; private String description; public ShiroCustomizeException( String description) { super(description); } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append(getClass().getName()); sb.append(getMessage()); if (getDescription() != null) { sb.append(" - "); sb.append(getDescription()); } return sb.toString(); } public String getDescription() { return description; } public void setDescription(String description) { this.description = description; }}
public class CsxAppShiroExceptionResolver implements HandlerExceptionResolver { private static final Log log = LogFactory.getLog("CsxAppShiroExceptionResolver.class"); @Override public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { log.info("shiro 抛出异常"); // 如果是shiro无权操作,因为shiro 在操作auno等一部分不进行转发至无权限url if (ex instanceof ShiroCustomizeException) { ModelAndView mv = new ModelAndView("redirect:/shiroAjaxExceptionDealApp.do?msg="+ex.getMessage()); return mv; }else{ ModelAndView mv = new ModelAndView("redirect:/shiroAjaxExceptionDealApp.do?msg=远程服务器报错"); return mv; } }}
shiro内的配置如下
<!-- 自定义异常处理 --> <bean id="exceptionResolver" class="com.csx.shiro.CsxAppShiroExceptionResolver" />
参考文章:
1.分布式系统唯一ID生成方案汇总 http://www.cnblogs.com/haoxinyue/p/5208136.html
2.使用redis进行基于shiro的session集群共享 http://www.cnblogs.com/sunshine-2015/p/5686750.html
3.Shiro源码分析之两种Session的方式 http://www.th7.cn/Program/java/201507/513741.shtml
4.Shiro多端登录控制 http://jinnianshilongnian.iteye.com/blog/2039760
5.切换角色的处理 http://jinnianshilongnian.iteye.com/blog/2044616
6.shiro 拦截器 http://jinnianshilongnian.iteye.com/blog/2025656
- Shiro-分布式下的解决方案及其实现
- Apache shiro集群实现 (五)分布式集群系统下的高可用session解决方案
- Apache shiro集群实现 (五)分布式集群系统下的高可用session解决方案
- Apache shiro集群实现 (五)分布式集群系统下的高可用session解决方案
- Apache shiro集群实现 (六)分布式集群系统下的高可用session解决方案---Session共享
- Apache shiro集群实现 (六)分布式集群系统下的高可用session解决方案---Session共享
- Apache shiro集群实现 (六)分布式集群系统下的高可用session解决方案---Session共享
- Apache shiro集群实现 (六)分布式集群系统下的高可用session解决方案---Session共享
- Shiro 分布式架构下 Session 的共享实现
- spring boot整合redis实现shiro的分布式session共享
- 分布式事务及其在OFBiz的实现
- 分布式事务及其在OFBiz的实现
- 分布式服务下的交易一致性解决方案
- 分布式事务,高并发下分布式事务的解决方案
- Apache shiro集群实现 (七)分布式集群系统下---cache共享
- Apache shiro集群实现 (七)分布式集群系统下---cache共享
- Apache shiro集群实现 (七)分布式集群系统下---cache共享
- Shiro拦截AJAX的解决方案
- SSL/TLS 运行原理
- leetcode 349. Intersection of Two Arrays
- C# 为什么要重写了object类的ToString()方法
- 常见端口号及其分类
- 南阳理工2解题报告(括号配对问题)
- Shiro-分布式下的解决方案及其实现
- Web前端实现本地存储
- 图论500题——HDU_P1213 How Many Tables
- 剑指offer——解决面试题的思路
- intellij idea常用设置详解
- Mac挂载远程目录到本地
- C# 窗体间传值方法大汇总(转)
- morphia @Reference注解
- 【算法】插入排序(从小到大) 排序范围(0~n-1)n为数组元素个数