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 authorizationInfo
data 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