您的位置:首页 > 数据库 > Redis

我的shiro之旅-session共享-redis

2016-06-29 09:13 471 查看


基于redis缓存的session共享

结合上面的
MSM 思想,由 redis负责 session 数据的存储,而我们自己实现的 session manager 将负责 session 生命周期的管理。




   此架构存在着当redis master故障时, 虽然可以有一到多个备用slave,但是redis不会主动的进行master切换,这时session服务中断。

      为了做到redis的高可用,引入了zookper或者haproxy或者keepalived来解决redis master slave的切换问题。即:



  此体系结构中, redis master出现故障时, 通过haproxy设置redis slave为临时master, redis master重新恢复后,

 再切换回去. 此方案中, redis-master 与redis-slave 是双向同步的, 解决目前redis单点问题. 这样保证了session信息

在redis中的高可用。

 

实现此方案:

nginx        1   192.168.1.102

tomcat1    1  

tomcat2    1

redis-master   1 

redis-slave      1

slave1     1

slave2     1

haproxy vip  1

Apache Shiro集群要解决2个问题,一个是session的共享问题,一个是授权信息的cache共享问题。从DefaultWebSecurityManager入手看源码

发现其父类DefaultSessionManager中有sessionDAO属性,这个属性是真正实现了session储存的类,这个就是我们自己实现的redis
session的储存类。

RedisSessionDAO.java

import org.apache.shiro.session.Session;

import org.apache.shiro.session.UnknownSessionException;

import org.apache.shiro.session.mgt.eis.AbstractSessionDAO;

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

import org.springframework.util.SerializationUtils;

import java.io.Serializable;

import java.util.Collection;

import java.util.HashSet;

import java.util.Set;

public class RedisSessionDAO extends AbstractSessionDAO {

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

    /**

     * shiro-redis的session对象前缀

     */

    private RedisManager redisManager;

    /**

     * The Redis key prefix for the sessions

     */

    private String keyPrefix = "shiro_session:";

    @Override

    public void update(Session session) throws UnknownSessionException {

        this.saveSession(session);

    }

    /**

     * save session

     * @param session

     * @throws UnknownSessionException

     */

    private void saveSession(Session session) throws UnknownSessionException{

        System.out.println("----saveSession---"+session.getId());

        if(session == null || session.getId() == null){

            logger.error("session or session id is null");

            return;

        }

        byte[] key = getByteKey(session.getId());

        byte[] value = SerializationUtils.serialize(session);

       // session.setTimeout(redisManager.getExpire()*1000);

     this.redisManager.set(key, value, Integer.parseInt(session.getTimeout()+""));

    }

    @Override

    public void delete(Session session) {

        if(session == null || session.getId() == null){

            logger.error("session or session id is null");

            return;

        }

        redisManager.del(this.getByteKey(session.getId()));

    }

    @Override

    public Collection<Session> getActiveSessions() {

        Set<Session> sessions = new HashSet<Session>();

        Set<byte[]> keys = redisManager.keys(this.keyPrefix + "*");

        if(keys != null && keys.size()>0){

            for(byte[] key:keys){

                Session s = (Session)SerializationUtils.deserialize(redisManager.get(key));

                sessions.add(s);

            }

        }

        return sessions;

    }

    @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;

        }

        Session s = (Session)SerializationUtils.deserialize(redisManager.get(this.getByteKey(sessionId)));

        return s;

    }

    /**

     * 获得byte[]型的key

     * @param sessionId

     * @return

     */

    private byte[] getByteKey(Serializable sessionId){

        String preKey = this.keyPrefix + sessionId;

        return preKey.getBytes();

    }

    public RedisManager getRedisManager() {

        return redisManager;

    }

    public void setRedisManager(RedisManager redisManager) {

        this.redisManager = redisManager;

        /**

         * 初始化redisManager

         */

        this.redisManager.init();

    }

    /**

     * 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;

    }

}

RedisManager.java

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

import redis.clients.jedis.Jedis;

import redis.clients.jedis.JedisPool;

import redis.clients.jedis.JedisPoolConfig;

import java.util.Set;

public class RedisManager {

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

    private String host;

    private int port;

    // 0 - never expire

    private int expire = 0;

    private static JedisPool jedisPool = null;

    public RedisManager() {

    }

    /**

     * 初始化方法

     */

    public void init(){

        if(null == host || 0 == port){

            logger.error("请初始化redis配置文件!");

            throw new NullPointerException("找不到redis配置");

        }

        if(jedisPool == null){

            //jedisPool = JedisUtil.getJedisPool();

            jedisPool = new JedisPool(new JedisPoolConfig(), host, port);

        }

    }

    /**

     * get value from redis

     * @param key

     * @return

     */

    public byte[] get(byte[] key) {

        byte[] value = null;

        Jedis jedis = jedisPool.getResource();

        try {

            value = jedis.get(key);

        } finally {

            jedisPool.returnResource(jedis);

        }

        return value;

    }

    /**

     * get value from redis

     * @param key

     * @return

     */

    public String get(String key) {

        String value=null;

        Jedis jedis = jedisPool.getResource();

        try {

            value = jedis.get(key);

        } finally {

            jedisPool.returnResource(jedis);

        }

        return value;

    }

    /**

     * set

     * @param key

     * @param value

     * @return

     */

    public byte[] set(byte[] key, byte[] value) {

        Jedis jedis = jedisPool.getResource();

        try {

            jedis.set(key, value);

            if (this.expire != 0) {

                jedis.expire(key, this.expire);

            }

        } finally {

            jedisPool.returnResource(jedis);

        }

        return value;

    }

    /**

     * set

     * @param key

     * @param value

     * @return

     */

    public String set(String key,String value) {

        Jedis jedis = jedisPool.getResource();

        try {

            jedis.set(key, value);

            if (this.expire != 0) {

                jedis.expire(key, this.expire);

            }

        } finally {

            jedisPool.returnResource(jedis);

        }

        return value;

    }

    /**

     * set

     * @param key

     * @param value

     * @param expire

     * @return

     */

    public byte[] set(byte[] key, byte[] value, int expire) {

        Jedis jedis = jedisPool.getResource();

        try {

            jedis.set(key, value);

            if (expire != 0) {

                jedis.expire(key, expire);

            }

        } finally {

            jedisPool.returnResource(jedis);

        }

        return value;

    }

    /**

     * set

     * @param key

     * @param value

     * @param expire

     * @return

     */

    public String set(String key,String value, int expire) {

        Jedis jedis = jedisPool.getResource();

        try {

            jedis.set(key, value);

            if (expire != 0) {

                jedis.expire(key, expire);

            }

        } finally {

            jedisPool.returnResource(jedis);

        }

        return value;

    }

    /**

     * del

     * @param key

     */

    public void del(byte[] key) {

        Jedis jedis = jedisPool.getResource();

        try {

            jedis.del(key);

        } finally {

            jedisPool.returnResource(jedis);

        }

    }

    /**

     * del

     * @param key

     */

    public void del(String key) {

        Jedis jedis = jedisPool.getResource();

        try {

            jedis.del(key);

        } finally {

            jedisPool.returnResource(jedis);

        }

    }

    /**

     * flush

     */

    public void flushDB() {

        Jedis jedis = jedisPool.getResource();

        try {

            jedis.flushDB();

        } finally {

            jedisPool.returnResource(jedis);

        }

    }

    /**

     * size

     */

    public Long dbSize() {

        Long dbSize = 0L;

        Jedis jedis = jedisPool.getResource();

        try {

            dbSize = jedis.dbSize();

        } finally {

            jedisPool.returnResource(jedis);

        }

        return dbSize;

    }

    /**

     * keys

     * @param pattern

     * @return

     */

    public Set<byte[]> keys(String pattern) {

        Set<byte[]> keys = null;

        Jedis jedis = jedisPool.getResource();

        try {

            keys = jedis.keys(pattern.getBytes());

        } finally {

            jedisPool.returnResource(jedis);

        }

        return keys;

    }

    public String getHost() {

        return host;

    }

    public void setHost(String host) {

        this.host = host;

    }

    public int getPort() {

        return port;

    }

    public void setPort(int port) {

        this.port = port;

    }

    public int getExpire() {

        return expire;

    }

    public void setExpire(int expire) {

        this.expire = expire;

    }

    public void destroy(){

        jedisPool.destroy();

    }

}

RedisCacheManager.java

import org.apache.shiro.ShiroException;

import org.apache.shiro.cache.Cache;

import org.apache.shiro.cache.CacheException;

import org.apache.shiro.cache.CacheManager;

import org.apache.shiro.cache.MapCache;

import org.apache.shiro.util.Destroyable;

import org.apache.shiro.util.Initializable;

import org.apache.shiro.util.StringUtils;

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

import redis.clients.jedis.JedisPoolConfig;

import redis.clients.jedis.JedisShardInfo;

import redis.clients.jedis.ShardedJedisPool;

import java.util.ArrayList;

import java.util.List;

import java.util.concurrent.ConcurrentHashMap;

import java.util.concurrent.ConcurrentMap;

public class RedisCacheManager implements CacheManager, Destroyable {

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

    // fast lookup by name map

    private final ConcurrentMap<String, Cache> caches = new ConcurrentHashMap<String, Cache>();

    private RedisManager redisManager;

    /**

     * The Redis key prefix for caches

     */

    private String keyPrefix = "shiro_redis_cache:";

    /**

     * 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;

    }

    @Override

    public <K, V> Cache<K, V> getCache(String name) throws CacheException {

        logger.debug("获取名称为: " + name + " 的RedisCache实例");

        Cache c = caches.get(name);

        if (c == null) {

            // initialize the Redis manager instance

            redisManager.init();

            // create a new cache instance

            c = new RedisCache<K, V>(redisManager, keyPrefix);

            // add it to the cache collection

            caches.put(name, c);

        }

        return c;

    }

    public RedisManager getRedisManager() {

        return redisManager;

    }

    public void setRedisManager(RedisManager redisManager) {

        this.redisManager = redisManager;

    }

    @Override

    public void destroy() throws Exception {

        this.redisManager.destroy();

    }

}

RedisCache.java
package com.zte.alm.cs.ui.shiro.redis;

package com.zte.alm.cs.ui.shiro.redis;

import org.apache.shiro.cache.Cache;

import org.apache.shiro.cache.CacheException;

import org.apache.shiro.subject.PrincipalCollection;

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

import org.springframework.util.CollectionUtils;

import org.springframework.util.SerializationUtils;

import redis.clients.jedis.Jedis;

import redis.clients.jedis.ShardedJedis;

import redis.clients.jedis.ShardedJedisPool;

import java.util.*;

public class RedisCache<K, V> implements Cache<K, V> {

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

    /**

     * The wrapped Jedis instance.

     */

    private RedisManager cache;

    /**

     * The Redis key prefix for the sessions

     */

    private String keyPrefix = "shiro_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(RedisManager cache) {

        if (cache == null) {

            throw new IllegalArgumentException("Cache argument cannot be null.");

        }

        this.cache = cache;

    }

    /**

     * Constructs a cache instance with the specified

     * Redis manager and using a custom key prefix.

     * @param cache The cache manager instance

     * @param prefix The Redis key prefix

     */

    public RedisCache(RedisManager cache, String prefix) {

        this(cache);

        // 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 if(key instanceof PrincipalCollection){

            String preKey = this.keyPrefix + key.toString();

            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 = cache.get(getByteKey(key));

                @SuppressWarnings("unchecked")

                V value = (V) SerializationUtils.deserialize(rawValue);

                return value;

            }

        } catch (Throwable t) {

            throw new CacheException(t);

        }

    }

    public String getStr(String key) throws CacheException {

        logger.debug("根据key从Redis中获取对象 key [" + key + "]");

        try {

            if (key == null) {

                return null;

            } else {

                return cache.get(key);

            }

        } catch (Throwable t) {

            throw new CacheException(t);

        }

    }

    @Override

    public V put(K key, V value) throws CacheException {

        logger.debug("根据key从存储 key [" + key + "]");

        try {

            cache.set(getByteKey(key), SerializationUtils.serialize(value));

            return value;

        } catch (Throwable t) {

            throw new CacheException(t);

        }

    }

    public String putStr(String key, String value) throws CacheException {

        logger.debug("根据key从存储 key [" + key + "]");

        try {

            cache.set(key, value);

            return value;

        } catch (Throwable t) {

            throw new CacheException(t);

        }

    }

    public String put(String key,String value, int expire) throws CacheException {

        logger.debug("根据key从存储 key [" + key + "]");

        try {

            cache.set(key, value, expire);

            return value;

        } catch (Throwable t) {

            throw new CacheException(t);

        }

    }

    public String removeString(String key) throws CacheException {

        logger.debug("从redis中删除 key [" + key + "]");

        try {

            String previous = cache.get(key);

            cache.del(key);

            return previous;

        } 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);

            cache.del(getByteKey(key));

            return previous;

        } catch (Throwable t) {

            throw new CacheException(t);

        }

    }

    @Override

    public void clear() throws CacheException {

        logger.debug("从redis中删除所有元素");

        try {

            cache.flushDB();

        } catch (Throwable t) {

            throw new CacheException(t);

        }

    }

    @Override

    public int size() {

        try {

            Long longSize = new Long(cache.dbSize());

            return longSize.intValue();

        } catch (Throwable t) {

            throw new CacheException(t);

        }

    }

    @SuppressWarnings("unchecked")

    @Override

    public Set<K> keys() {

        try {

            Set<byte[]> keys = cache.keys(this.keyPrefix + "*");

            if (CollectionUtils.isEmpty(keys)) {

                return Collections.emptySet();

            } else {

                Set<K> newKeys = new HashSet<K>();

                for (byte[] key : keys) {

                    newKeys.add((K) key);

                }

                return newKeys;

            }

        } catch (Throwable t) {

            throw new CacheException(t);

        }

    }

    @Override

    public Collection<V> values() {

        try {

            Set<byte[]> keys = cache.keys(this.keyPrefix + "*");

            if (!CollectionUtils.isEmpty(keys)) {

                List<V> values = new ArrayList<V>(keys.size());

                for (byte[] 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);

        }

    }

}
ShiroRealm.java

import java.util.ArrayList;

import java.util.Collection;

import java.util.HashMap;

import java.util.HashSet;

import java.util.List;

import java.util.Map;

import javax.annotation.Resource;

import org.apache.shiro.authc.AuthenticationException;

import org.apache.shiro.authc.AuthenticationInfo;

import org.apache.shiro.authc.AuthenticationToken;

import org.apache.shiro.authc.SimpleAuthenticationInfo;

import org.apache.shiro.authz.AuthorizationInfo;

import org.apache.shiro.authz.SimpleAuthorizationInfo;

import org.apache.shiro.realm.AuthorizingRealm;

import org.apache.shiro.subject.PrincipalCollection;

import org.apache.shiro.util.ByteSource;

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

import org.springframework.stereotype.Service;

import com.zte.alm.cs.persist.entity.sys.SysUser;

import com.zte.alm.cs.service.sys.ISysGroupService;

import com.zte.alm.cs.service.sys.ISysOrgRoleService;

import com.zte.alm.cs.service.sys.ISysOrgService;

import com.zte.alm.cs.service.sys.ISysPermissionResourceService;

import com.zte.alm.cs.service.sys.ISysUserService;

public class ShiroRealm extends AuthorizingRealm {
private static final Logger log = LoggerFactory.getLogger(ShiroRealm.class);

//清空
protected void doClearCache(PrincipalCollection principals) {
super.doClearCache(principals);
this.clearCachedAuthorizationInfo(principals);
}
//修改key的值
public Object getAuthorizationCacheKey(PrincipalCollection principals){
Collection<?> collection = principals.fromRealm(getName());
if (collection == null || collection.isEmpty()) {
return null;
}

ShiroUser shiroUser = (ShiroUser) collection.iterator().next();
return shiroUser.getLoginName();
}

applicationContext-shiro.xml


<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd" default-lazy-init="true">

<description>Shiro安全配置</description>

<!-- 凭证匹配器 -->
<bean id="credentialsMatcher" class="com.test.shiro.CredentialsMatcher">

</bean>
<!-- Shiro's main business-tier object for web-enabled applications -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="sessionManager" ref="defaultWebSessionManager" />
<property name="realm" ref="almShiroRealm" />
<!-- <property name="cacheManager" ref="shiroEhcacheManager" /> -->
<property name="cacheManager" ref="redisCacheManager" />
</bean>
<bean id="defaultWebSessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
<!-- 相隔多久检查一次session的有效性 -->
<property name="sessionValidationInterval" value="1800000"/>
<!-- session 有效时间为半小时 (毫秒单位)-->
<property name="globalSessionTimeout" value="1800000"/>

<!-- 是否在会话过期后会调用SessionDAO的delete方法删除会话 默认true -->
<property name="deleteInvalidSessions" value="true" />

<property name="sessionDAO" ref="redisShiroSessionDAO"/>

</bean>

<!-- 項目自定义的Realm -->
<bean id="almShiroRealm" class="com.test.shiro.ShiroRealm">
<property name="credentialsMatcher" ref="credentialsMatcher" />
</bean>

<!-- Shiro Filter -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager" />
<property name="loginUrl" value="/login" />
<property name="successUrl" value="/login/index" />
<property name="filters">
<map>
<!-- 是否启用验证码检验 -->
<entry key="authc" value-ref="CaptchaFormAuthenticationFilter" />
<entry key="user" value-ref="AlmUserFilter" />
</map>
</property>
<property name="filterChainDefinitions">
<value>
/js/** = anon
/css/**=anon
/img/**=anon
/font/**=anon
/mail/**=anon
/login =authc
/logout = logout
/** = user
</value>
</property>
</bean>

<!-- 用户授权信息Cache, 采用EhCache -->
<bean id="shiroEhcacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
<property name="cacheManagerConfigFile" value="classpath:conf/ehcache/ehcache-shiro.xml" />
</bean>
<bean id="redisShiroSessionDAO" class="com.test.shiro.redis.RedisSessionDAO">
<property name="redisManager" ref="redisManager" />
</bean>
<bean id="redisCacheManager" class="com.test.shiro.redis.RedisCacheManager"><!-- 自定义cacheManager -->
<property name="redisManager" ref="redisManager" />
</bean>
<bean id="redisManager" class="com.test.shiro.redis.RedisManager">
<property name="host" value="127.0.0.1" />
<property name="port" value="6379" />
</bean>

<bean id="CaptchaFormAuthenticationFilter" class="com.test.shiro.CaptchaFormAuthenticationFilter" />
<bean id="AlmUserFilter" class="com.test.shiro.AlmUserFilter" />

<!-- 保证实现了Shiro内部lifecycle函数的bean执行 -->
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor" />

</beans>

/**
* 权限
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(
PrincipalCollection principals) {
log.info("--doGetAuthorizationInfo--");
// TODO Auto-generated method stub
Collection<?> collection = principals.fromRealm(getName());
if (collection == null || collection.isEmpty()) {
return null;
}

ShiroUser shiroUser = (ShiroUser) collection.iterator().next();
return newAuthorizationInfo(shiroUser);
}

private SimpleAuthorizationInfo newAuthorizationInfo(ShiroUser shiroUser) {

}

/**
* 用户认证
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(
AuthenticationToken paramAuthenticationToken)
throws AuthenticationException {

}

}

补充:发现在realm的权限中设置的shiroUser信息没有生效,处理方式,重新设置session的属性值
Subject subject = SecurityUtils.getSubject();

Session session = subject.getSession();

String RUN_AS_PRINCIPALS_SESSION_KEY = "org.apache.shiro.subject.support.DefaultSubjectContext_PRINCIPALS_SESSION_KEY";

PrincipalCollection  principals =  new SimplePrincipalCollection(shiroUser,this.getName());

session.setAttribute(RUN_AS_PRINCIPALS_SESSION_KEY, principals);
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: