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

阅读全文
1 0
原创粉丝点击