1. 程式人生 > >Shiro與Redis整合,叢集下的session共享

Shiro與Redis整合,叢集下的session共享

概述

在叢集環境中,session共享一般通過應用伺服器的session複製或者儲存在公用的快取伺服器上,本文主要介紹通過Shiro管理session,並將session快取到redis中,這樣可以在叢集中使用。

Shiro除了在管理session上使用redis,也在可以快取使用者許可權,即cacheManager可以通過redis來擴充套件。

下面從cacheManager 和 sessionManager這兩部分講解Shiro與Redis的整合

spring-shiro.xml配置

    <bean id="cacheManager" class="com.cnpc.framework.base.pojo.RedisCacheManager"
/>
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> <property name="realm" ref="adminRealm"/> <property name="cacheManager" ref="cacheManager"/> <!--將session託管給redis進行管理,便於搭建集群系統--> <property name="sessionManager"
ref="webSessionManager"/>
</bean> <!--redis管理session--> <bean id="webSessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager"> <property name="sessionDAO" ref="redisSessionDao"/> <property name="deleteInvalidSessions" value
="true"/>
<property name="globalSessionTimeout" value="${shiro.session.timeout}"/> <property name="sessionIdCookie" ref="sharesession"/> <property name="sessionIdUrlRewritingEnabled" value="false"/> <!-- 定時檢查失效的session --> <property name="sessionValidationSchedulerEnabled" value="true"/> </bean> <!-- sessionIdCookie的實現,用於重寫覆蓋容器預設的JSESSIONID --> <bean id="sharesession" class="org.apache.shiro.web.servlet.SimpleCookie"> <!-- cookie的name,對應的預設是 JSESSIONID --> <constructor-arg name="name" value="SHAREJSESSIONID"/> <!-- jsessionId的path為 / 用於多個系統共享jsessionId --> <property name="path" value="/"/> </bean> <bean id="redisSessionDao" class="com.cnpc.framework.base.pojo.RedisSessionDao"> <property name="expire" value="${shiro.session.timeout}"/> </bean>

Shiro和Redis共同託管session

由Redis來管理session,包括session建立、讀取、刪除等,還可以統計線上使用者,下面是核心程式碼RedisSessionDao的實現:

package com.cnpc.framework.base.pojo;


import com.cnpc.framework.base.dao.RedisDao;
import com.cnpc.framework.base.entity.BaseEntity;
import com.cnpc.framework.constant.RedisConstant;
import com.cnpc.framework.utils.StrUtil;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.session.Session;
import org.apache.shiro.session.UnknownSessionException;
import org.apache.shiro.session.mgt.eis.AbstractSessionDAO;
import org.apache.shiro.subject.SimplePrincipalCollection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.SerializationUtils;

import javax.annotation.Resource;
import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;

/**
 * Created by billJiang on 2017/4/13.
 * e-mail:[email protected]  qq:475572229
 * 通過如下方式呼叫
 * RedisSessionDao redisSession = (RedisSessionDao)SpringContextUtil.getBean("redisSessionDao");
 */
//@Service("redisSessionDao")
public class RedisSessionDao extends AbstractSessionDAO {

    private static Logger logger = LoggerFactory.getLogger(RedisSessionDao.class);

    private long expire;

    @Resource
    private RedisDao redisDao;

    @Override
    protected Serializable doCreate(Session session) {
        Serializable sessionId = this.generateSessionId(session);
        this.assignSessionId(session, sessionId);
        this.saveSession(session);
        return sessionId;
    }

    @Override
    protected Session doReadSession(Serializable sessionId) {
        if (sessionId == null) {
            logger.error("session id is null");
            return null;
        }

        logger.debug("Read Redis.SessionId=" + new String(getKey(RedisConstant.SHIRO_REDIS_SESSION_PRE, sessionId.toString())));

        Session session = (Session) SerializationUtils.deserialize(redisDao.getByte(getKey(RedisConstant.SHIRO_REDIS_SESSION_PRE, sessionId.toString())));
        return session;
    }

    @Override
    public void update(Session session) throws UnknownSessionException {
        this.saveSession(session);
    }

    int i=0;
    public void saveSession(Session session) {
        if (session == null || session.getId() == null) {
            logger.error("session or session id is null");
            return;
        }
        session.setTimeout(expire);
        long timeout = expire / 1000;
        //儲存使用者會話
        redisDao.add(this.getKey(RedisConstant.SHIRO_REDIS_SESSION_PRE, session.getId().toString()), timeout, SerializationUtils.serialize(session));
        //獲取使用者id
        String uid = getUserId(session);
        if (!StrUtil.isEmpty(uid)) {
            //儲存使用者會話對應的UID
            try {
                redisDao.add(this.getKey(RedisConstant.SHIRO_SESSION_PRE, session.getId().toString()), timeout, uid.getBytes("UTF-8"));
                //儲存線上UID
                redisDao.add(this.getKey(RedisConstant.UID_PRE, uid), timeout, ("online"+(i++)+"").getBytes("UTF-8"));
            } catch (UnsupportedEncodingException ex) {
                logger.error("getBytes error:" + ex.getMessage());
            }
        }

    }


    public String getUserId(Session session) {
        SimplePrincipalCollection pricipal = (SimplePrincipalCollection) session.getAttribute("org.apache.shiro.subject.support.DefaultSubjectContext_PRINCIPALS_SESSION_KEY");
        if (null != pricipal) {
            return pricipal.getPrimaryPrincipal().toString();
        }
        return null;
    }

    public String getKey(String prefix, String keyStr) {
        return prefix + keyStr;
    }

    @Override
    public void delete(Session session) {
        if (session == null || session.getId() == null) {
            logger.error("session or session id is null");
            return;
        }
        //刪除使用者會話
        redisDao.delete(this.getKey(RedisConstant.SHIRO_REDIS_SESSION_PRE, session.getId().toString()));
        //獲取快取的使用者會話對應的UID
        String uid = redisDao.get(this.getKey(RedisConstant.SHIRO_SESSION_PRE, session.getId().toString()));
        //刪除使用者會話sessionid對應的uid
        redisDao.delete(this.getKey(RedisConstant.SHIRO_SESSION_PRE, session.getId().toString()));
        //刪除線上uid
        redisDao.delete(this.getKey(RedisConstant.UID_PRE, uid));
        //刪除使用者快取的角色
        redisDao.delete(this.getKey(RedisConstant.ROLE_PRE, uid));
        //刪除使用者快取的許可權
        redisDao.delete(this.getKey(RedisConstant.PERMISSION_PRE, uid));
    }

    @Override
    public Collection<Session> getActiveSessions() {
        Set<Session> sessions = new HashSet<>();

        Set<String> keys = redisDao.keys(RedisConstant.SHIRO_REDIS_SESSION_PRE + "*");
        if (keys != null && keys.size() > 0) {
            for (String key : keys) {
                Session s = (Session) SerializationUtils.deserialize(redisDao.getByte(key));
                sessions.add(s);
            }
        }
        return sessions;
    }

    /**
     * 當前使用者是否線上
     *
     * @param uid 使用者id
     * @return
     */
    public boolean isOnLine(String uid) {
        Set<String> keys = redisDao.keys(RedisConstant.UID_PRE + uid);
        if (keys != null && keys.size() > 0) {
            return true;
        }
        return false;
    }

    public long getExpire() {
        return expire;
    }

    public void setExpire(long expire) {
        this.expire = expire;
    }
}

上述的redisDao程式碼的實現參考我的上篇部落格Spring整合Redis步驟

Redis快取使用者許可權

在realm的doGetAuthorizationInfo中,獲取的SimpleAuthorizationInfo authorizationInfo會存在快取中,本文也用Redis進行快取;

 @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {

        // 因為非正常退出,即沒有顯式呼叫 SecurityUtils.getSubject().logout()
        // (可能是關閉瀏覽器,或超時),但此時快取依舊存在(principals),所以會自己跑到授權方法裡。
        if (!SecurityUtils.getSubject().isAuthenticated()) {
            doClearCache(principals);
            SecurityUtils.getSubject().logout();
            return null;
        }

        if (principals == null) {
            throw new AuthorizationException("parameters principals is null");
        }
        //獲取已認證的使用者名稱(登入名)
        String userId=(String)super.getAvailablePrincipal(principals);
        if(StrUtil.isEmpty(userId)){
            return null;
        }
        Set<String> roleCodes=roleService.getRoleCodeSet(userId);
        //預設使用者擁有所有許可權
        Set<String> functionCodes=functionService.getAllFunctionCode();
       /* Set<String> functionCodes=functionService.getFunctionCodeSet(roleCodes);*/
        SimpleAuthorizationInfo authorizationInfo=new SimpleAuthorizationInfo();
        authorizationInfo.setRoles(roleCodes);
        authorizationInfo.setStringPermissions(functionCodes);
        return authorizationInfo;
    }

RedisCacheManager.java實現

package com.cnpc.framework.base.pojo;

import com.cnpc.framework.base.dao.RedisDao;
import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheException;
import org.apache.shiro.cache.CacheManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.Resource;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

/**
 * Created by billJiang on 2017/4/15.
 * e-mail:[email protected]  qq:475572229
 */
public class RedisCacheManager implements CacheManager {

    private static final Logger logger = LoggerFactory.getLogger(RedisCacheManager.class);

    // fast lookup by name map
    private final ConcurrentMap<String, Cache> caches = new ConcurrentHashMap<String, Cache>();

    @Resource
    private RedisDao redisDao;

    public <K, V> Cache<K, V> getCache(String name) throws CacheException {
        logger.debug("獲取名稱為: " + name + " 的RedisCache例項");
        Cache c = caches.get(name);
        if (c == null) {
            c = new RedisCache<K, V>(redisDao);
            caches.put(name, c);
        }
        return c;
    }
}

RedisCache.java實現

package com.cnpc.framework.base.pojo;

import com.cnpc.framework.base.dao.RedisDao;
import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheException;
import org.apache.shiro.util.CollectionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.SerializationUtils;

import java.util.*;

/**
 * Created by billJiang on 2017/4/15.
 * e-mail:[email protected]  qq:475572229
 */
public class RedisCache<K, V> implements Cache<K, V> {

    private Logger logger = LoggerFactory.getLogger(this.getClass());

    /**
     * The wrapped Jedis instance.
     */
    //private RedisManager redisDao;
    private RedisDao redisDao;

    /**
     * The Redis key prefix for the sessions
     */
    private String keyPrefix = "shiro_redis_session:";

    /**
     * Returns the Redis session keys
     * prefix.
     *
     * @return The prefix
     */
    public String getKeyPrefix() {
        return keyPrefix;
    }

    /**
     * Sets the Redis sessions key
     * prefix.
     *
     * @param keyPrefix The prefix
     */
    public void setKeyPrefix(String keyPrefix) {
        this.keyPrefix = keyPrefix;
    }

    /**
     * 通過一個JedisManager例項構造RedisCache
     */
    public RedisCache(RedisDao redisDao) {
        if (redisDao == null) {
            throw new IllegalArgumentException("Cache argument cannot be null.");
        }
        this.redisDao = redisDao;
    }

    /**
     * Constructs a redisDao instance with the specified
     * Redis manager and using a custom key prefix.
     *
     * @param redisDao  The redisDao manager instance
     * @param prefix The Redis key prefix
     */
    public RedisCache(RedisDao redisDao,
                      String prefix) {

        this(redisDao);

        // set the prefix
        this.keyPrefix = prefix;
    }

    /**
     * 獲得byte[]型的key
     *
     * @param key
     * @return
     */
    private byte[] getByteKey(K key) {
        if (key instanceof String) {
            String preKey = this.keyPrefix + key;
            return preKey.getBytes();
        } else {
            return SerializationUtils.serialize(key);
        }
    }

    @Override
    public V get(K key) throws CacheException {
        logger.debug("根據key從Redis中獲取物件 key [" + key + "]");
        try {
            if (key == null) {
                return null;
            } else {
                byte[] rawValue = redisDao.getByte(key.toString());
                @SuppressWarnings("unchecked")
                V value = (V) SerializationUtils.deserialize(rawValue);
                return value;
            }
        } catch (Throwable t) {
            throw new CacheException(t);
        }

    }

    @Override
    public V put(K key, V value) throws CacheException {
        logger.debug("根據key從儲存 key [" + key + "]");
        try {
            redisDao.set(key.toString(), SerializationUtils.serialize(value));
            return value;
        } catch (Throwable t) {
            throw new CacheException(t);
        }
    }

    @Override
    public V remove(K key) throws CacheException {
        logger.debug("從redis中刪除 key [" + key + "]");
        try {
            V previous = get(key);
            redisDao.delete(key.toString());
            return previous;
        } catch (Throwable t) {
            throw new CacheException(t);
        }
    }

    @Override
    public void clear() throws CacheException {
        logger.debug("從redis中刪除所有元素");
        try {
            redisDao.flushDB();
        } catch (Throwable t) {
            throw new CacheException(t);
        }
    }

    @Override
    public int size() {
        try {
            Long longSize = new Long(redisDao.dbSize());
            return longSize.intValue();
        } catch (Throwable t) {
            throw new CacheException(t);
        }
    }

    @SuppressWarnings("unchecked")
    @Override
    public Set<K> keys() {
        try {
            Set<String> keys = redisDao.keys(this.keyPrefix + "*");
            if (CollectionUtils.isEmpty(keys)) {
                return Collections.emptySet();
            } else {
                Set<K> newKeys = new HashSet<K>();
                for (String key : keys) {
                    newKeys.add((K) key);
                }
                return newKeys;
            }
        } catch (Throwable t) {
            throw new CacheException(t);
        }
    }

    @Override
    public Collection<V> values() {
        try {
            Set<String> keys = redisDao.keys(this.keyPrefix + "*");
            if (!CollectionUtils.isEmpty(keys)) {
                List<V> values = new ArrayList<V>(keys.size());
                for (String key : keys) {
                    @SuppressWarnings("unchecked")
                    V value = get((K) key);
                    if (value != null) {
                        values.add(value);
                    }
                }
                return Collections.unmodifiableList(values);
            } else {
                return Collections.emptyList();
            }
        } catch (Throwable t) {
            throw new CacheException(t);
        }
    }
}

總結

通過以上程式碼實現了Shiro和Redis的整合,Redis快取了使用者session和許可權資訊authorizationInfo。

相關推薦

ShiroRedis整合叢集session共享

概述 在叢集環境中,session共享一般通過應用伺服器的session複製或者儲存在公用的快取伺服器上,本文主要介紹通過Shiro管理session,並將session快取到redis中,這樣可以在叢集中使用。 Shiro除了在管理session上使用re

記一次Shiro有關的錯誤404導致session丟失需要重新登入

一 問題描述 前段時間上司突然叫我幫忙解決老專案上的一個bug,出現的問題是不同使用者賬號,進入同一個頁面,有個別用戶重新整理一下當前頁面就會重定向到登入頁面,需要重新登入。 這是一個幾年前的一個專案,使用的是Srping + Spring MVC + Shiro + Jsp的專案,之前沒用過Shiro,

18.ShiroSpringboot整合登陸驗證UserService未注入的問題

Shiro與Springboot整合下登陸驗證UserService未注入的問題 前言: 剛開始整合的情況下,UserService一執行,就會報空指標異常。 看了網上各位大神的講解,什麼不能用service層,直接用dao層獲取。。。。。。 然後跟著一路再坑。。。。。。。 最後的最後,才發現MyR

spring 和 redis整合並且使用redissession快取伺服器

所需jar commons-pool2-2.0.jar jedis-2.9.0.jar spring-data-redis-1.6.2.RELEASE.jar spring-session-1.2.1.RELEASE.jar 一 . xml配

使用shiroredis結合管理SessionDAO的對Session的CRUD並原始碼分析

SessionDAO的作用是為Session提供CRUD並進行持久化的一個shiro元件,將整合redis快取進行開發 由配置檔案可以知道sessionManager需要注入一個sessionDao <!-- 自定義會話管理配置 --> <bean i

Shiro許可權管理框架(二):Shiro結合Redis實現分散式環境Session共享

首發地址:https://www.guitu18.com/post/2019/07/28/44.html 本篇是Shiro系列第二篇,使用Shiro基於Redis實現分散式環境下的Session共享。在講Session共享之前先說一下為什麼要做Session共享。 為什麼要做Session共享 什麼是Ses

樹形ztree angularjs結合實現級數據異步加載點擊復選框 填寫到輸入框裏

沒有 hide deb out IV UNC -s parent default html:<input value="" type="text" id="river_cut" onclick="

(簡)樹形ztree angularjs結合實現級數據點擊復選框 填寫到輸入框裏

url let 輸入 樹形 fadeout ros mar 分隔符 3.4 html:<link href="vendors/zTreeStyle/zTreeStyle.css" rel="stylesheet" />生態

SpringBoot框架MyBatis整合連線Mysql資料庫

  SpringBoot是一種用來簡化新Spring應用初始搭建及開發過程的框架,它使用特定方式來進行配置,使得開發人員不再需要定義樣板化的配置。MyBatis是一個支援普通SQL查詢、儲存和高階對映的持久層框架,它消除了幾乎所有的JDBC程式碼和引數的手工配置以及對結果集的檢索封裝,可以使用簡單的XML或註

ssm中加入redisspring -redis整合簡單認識redis

1匯入spring和redis的jar包,當然推薦用maven。開啟服務 2.application.xml中配置 定義執行緒池 <bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig">  

Shiro使用Redis作儲存之後更新Session失敗的問題

問題 因為想在多個應用之間共享使用者的登入態,因此實現了自己的SessionDAO,使用Kryo把SimpleSession序列化然後放到redis之中去,同時也使用了shiro.userNativeSessionManager: true來使用shiro自己的儲存。然而之後一直出現丟失更新的問題,例如

Ribbon 框架簡介及搭建(沒有SpringCloud整合獨立使用)

Ribbon簡介 1.  負載均衡框架,支援可插拔式的負載均衡規則 2.  支援多種協議,如HTTP、UDP等 3.  提供負載均衡客戶端 Ribbon子模組 1.  ribbon-core(ribbon的核心,主要包含負載均衡器、負載均衡介面、客戶端介面

springboot專案layuipageHelper整合列表分頁條件查詢

一、前端頁面  重點:1、table.render初始化載入資料             2、reload查詢按鈕觸發,資料重新載入            

SpringBootMyBatis整合底層資料庫為mysql的使用示例

 專案下載連結:https://github.com/DFX339/bootdemo.git   新建maven專案,web專案,專案名為 bootdemo   專案結構目錄如下:還有個pom.xml檔案沒有在截圖裡面   專案需要編寫的檔案主

7、Spring Boot Redis 整合

1.7 Spring Boot 與 Redis 整合 簡介 繼續上篇的MyBatis操作,詳細介紹在Spring Boot中使用RedisCacheManager作為快取管理器,整合業務於一體。 完整原始碼: Spring-Boot-Demos 1.7.1 建立 spring-boot-r

Springboot中Spring-cacheredis整合

也是在整合redis的時候偶然間發現spring-cache的。這也是一個不錯的框架,與spring的事務使用類似,只要新增一些註解方法,就可以動態的去操作快取了,減少程式碼的操作。如果這些註解不滿足專案的需求,我們也可以參考spring-cache的實現思想,使用AOP代理+快取操作來管理快取的使

spring redis 整合及使用

1、實現目標   通過redis快取資料。(目的不是加快查詢的速度,而是減少資料庫的負擔)   2、所需jar包     注意:jdies和commons-pool兩個jar的版本是有對應關係的,注意引入jar包是要配對使用,否則將會報錯。因為commons-pooljar的目錄根據

apache+Tomcat叢集session複製

原地址:https://blog.csdn.net/weiweiai123456/article/details/41750887 因為工作需要,本人需要在本機上做一個apache負載均衡Http請求,交給Tomcat叢集處理,並且Session要在不同的Tomcat上進行復制的D

Redis叢集+tomcat7叢集實現session共享

就在昨天,一個線上的產品突然不能訪問了,經過一系列的排查問題,最終發現原來是redis死掉了,因為做的用redis管理session,redis又是單臺伺服器,當redis宕機後,網站就訪問不上了。為了避免以後redis宕機導致網站上不去,同時也為了網站的穩健性,我決定把re

Springboot Redis 整合 簡易Redis工具類實現

最近專案需要處理一項資料量比較大的業務,考慮之下,高頻訪問/讀取決定使用Redis.自己的Springboot框架下研究了Redis兩天,把成果總結一下 開發環境介紹 JDK1.7 Redis 基礎依賴 org.mybatis.spring.boot myba