Shiro integrates with Redis, session sharing under the cluster

Overview

In a cluster environment, session sharing is generally replicated through the session of the application server or stored on a public cache server. This article mainly introduces the session management through Shiro, and the session is cached in redis, which can be used in the cluster.

In addition to using redis for managing sessions, Shiro can also cache user permissions, that is, cacheManager can be extended through redis.

The following explains the integration of Shiro and Redis from the two parts of cacheManager and sessionManager

For the integration of shiro in spring, please refer to my blog  AdminEAP Framework - Integrating Shiro Security Authentication.

spring-shiro.xml placement

 
<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"/>
        <!--Host the session to redis for management, which is convenient to build a cluster system-->
        <property name="sessionManager" ref="webSessionManager"/>
    </bean>
    <!--redis management 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"/>
        <!-- Regularly check for invalid sessions -->
        <property name="sessionValidationSchedulerEnabled" value="true"/>
    </bean>

    <!-- The implementation of sessionIdCookie, used to override the default JSESSIONID of the container -->
    <bean id="sharesession" class="org.apache.shiro.web.servlet.SimpleCookie">
        <!-- The name of the cookie, the corresponding default is JSESSIONID -->
        <constructor-arg name="name" value="SHAREJSESSIONID"/>
        <!-- The path of jsessionId is / for multiple systems to share 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 and Redis co-host sessions

Redis manages sessions, including session creation, reading, deletion, etc., and can also count online users. The following is the implementation of the core code 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.
 * email:[email protected] qq:475572229
 * Called as follows
 * 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;
        //save user session
        redisDao.add(this.getKey(RedisConstant.SHIRO_REDIS_SESSION_PRE, session.getId().toString()), timeout, SerializationUtils.serialize(session));
        //get user id
        String uid = getUserId(session);
        if (!StrUtil.isEmpty(uid)) {
            //Save the UID corresponding to the user session
            try {
                redisDao.add(this.getKey(RedisConstant.SHIRO_SESSION_PRE, session.getId().toString()), timeout, uid.getBytes("UTF-8"));
                //Save the online 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;
        }
        // delete user session
        redisDao.delete(this.getKey(RedisConstant.SHIRO_REDIS_SESSION_PRE, session.getId().toString()));
        //Get the UID corresponding to the cached user session
        String uid = redisDao.get(this.getKey(RedisConstant.SHIRO_SESSION_PRE, session.getId().toString()));
        //Delete the uid corresponding to the user session sessionid
        redisDao.delete(this.getKey(RedisConstant.SHIRO_SESSION_PRE, session.getId().toString()));
        // delete online uid
        redisDao.delete(this.getKey(RedisConstant.UID_PRE, uid));
        //delete user cached role
        redisDao.delete(this.getKey(RedisConstant.ROLE_PRE, uid));
        //delete user cache permission
        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;
    }

    /**
     * Whether the current user is online
     *
     * @param uid user 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;
    }
}

For the implementation of the above redisDao code, refer to my previous blog Spring integration Redis steps .

Redis cache user permissions

In realm doGetAuthorizationInfo, the obtained SimpleAuthorizationInfo authorizationInfodata will be stored in the cache, and this article also uses Redis for caching;

@Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {

        // Because of abnormal exit, that is, no explicit call to SecurityUtils.getSubject().logout()
        // (may be closing the browser, or timing out), but the cache still exists at this time (principals), so it will run to the authorization method by itself.
        if (!SecurityUtils.getSubject().isAuthenticated()) {
            doClearCache(principals);
            SecurityUtils.getSubject().logout();
            return null;
        }

        if (principals == null) {
            throw new AuthorizationException("parameters principals is null");
        }
        //Get the authenticated username (login name)
        String userId=(String)super.getAvailablePrincipal(principals);
        if(StrUtil.isEmpty(userId)){
            return null;
        }
        Set <String> roleCodes = roleService.getRoleCodeSet (userId);
        //Default user has all permissions
        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 implementation

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.
 * email:[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("Get the RedisCache instance with name: " + name + "");
        Cache c = caches.get(name);
        if (c == null) {
            c = new RedisCache<K, V>(redisDao);
            caches.put(name, c);
        }
        return c;
    }
}

RedisCache.java implementation

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.
 * email:[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;
    }

    /**
     * Construct RedisCache through a JedisManager instance
     */
    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;
    }

    /**
     * Get the key of type byte[]
     *
     * @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("Get object key from Redis according to 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("Store key from 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("Delete key from redis [" + 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("Remove all elements from 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);
        }
    }
}

Summarize

The integration of Shiro and Redis is realized through the above code, and Redis caches the user session and authorization information authorizationInfo.

Reprinted from https://blog.csdn.net/jrn1012/article/details/70373221



Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324601453&siteId=291194637