redis-shiro session 共享 session, SimpleSession序列化大坑

本文介绍如何利用Shiro和Redis实现分布式环境下的Session共享,确保用户在服务器切换时无需重复登录。文章提供了自定义RedisSessionDao和RedisCacheManager的具体实现,并讨论了序列化过程中可能遇到的问题及解决方案。

shiro 通过redis实现session共享

共享Session目的
在分布式服务的工程中,一个服务器断了,负载均衡服务器会把请求分配给其他的服务器,如果设置了session共享,就不需要用户再次登录了.
shiro实现session共享的原理
默认的情况下,shiro的session是在服务器上的,当该服务器宕掉了,session就不存在了,用户必须重新登录,如果我们把shiro的session存到redis服务器上,就可以实现session共享了.
如何实现?
如果我们想通过redis实现shiro的session共享,只需要把shiro的安全管理器(SecurityManager)中的2个属性替换了就可以实现共享
但是序列化还有个大坑,如果你也遇到了,请看到最后

  1. sessionDAO
  2. cacheManager
    下面是自定义的这2个属性的代码
    **RedisSessionDao **
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.data.redis.core.RedisTemplate;

import java.io.Serializable;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.TimeUnit;

public class RedisSessionDao extends AbstractSessionDAO {
    private static Logger logger = LoggerFactory.getLogger(RedisSessionDao.class);
    private RedisTemplate redisTemplate;
    private String keyPrefix = "shiro.redis.session:";

    public RedisSessionDao(RedisTemplate redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    private String getKey(String key) {
        return keyPrefix + key;
    }

    private void saveSession(Session session) throws UnknownSessionException {
        if (session != null && session.getId() != null) {
            String key = this.getKey(session.getId().toString());
            session.setTimeout(30*60*1000);
            redisTemplate.opsForValue().set(key, session, 30*60*1000, TimeUnit.MILLISECONDS);
        } else {
            logger.error("session or session id is null");
        }
    }

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

    @Override
    public void delete(Session session) {
        try {
            String key = getKey(session.getId().toString());
            redisTemplate.delete(key);
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }

    }

    @Override
    public Collection<Session> getActiveSessions() {
        Set<Session> sessions = new HashSet<>();
        Set<String> keys = redisTemplate.keys(getKey("*"));
        if (keys != null && keys.size() > 0) {
            for (String key : keys) {
                Session s = (Session) redisTemplate.opsForValue().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) {
        Session readSession = null;
        try {
            readSession = (Session) redisTemplate.opsForValue().get(getKey(sessionId.toString()));
        } catch (Exception e) {
            logger.error(e.getMessage(),e);
        }
        return readSession;
    }
}

RedisCacheManager

import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheException;
import org.apache.shiro.cache.CacheManager;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.data.redis.core.RedisTemplate;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;

public class RedisCacheManager implements CacheManager {
    private final ConcurrentMap<String, Cache> caches = new ConcurrentHashMap<String, Cache>();

    private RedisTemplate redisTemplate;

    public RedisCacheManager(RedisTemplate redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    @Override
    public <K, V> Cache<K, V> getCache(String name) throws CacheException {
        Cache cache = this.caches.get(name);
        if (cache == null) {
            //自定义shiroCache
            cache = new ShiroRedisCache<K, V>();
            this.caches.put(name, cache);
        }
        return cache;
    }

    private class ShiroRedisCache<K, V> implements Cache<K, V> {
        private long cacheLive = 30*60*1000;
        private String cacheKeyPrefix = "shiro.redis.cacheKey:";

        @Override
        public V get(K k) throws CacheException {
            return (V) redisTemplate.opsForValue().get(this.getRedisCacheKey(k));
        }

        @Override
        public V put(K k, V v) throws CacheException {
            redisTemplate.opsForValue().set(this.getRedisCacheKey(k), v, cacheLive, TimeUnit.MINUTES);
            return v;
        }

        @Override
        public V remove(K k) throws CacheException {
            V obj = (V) redisTemplate.opsForValue().get(this.getRedisCacheKey(k));
            redisTemplate.delete(this.getRedisCacheKey(k));
            return obj;
        }

        @Override
        public void clear() throws CacheException {
            Set keys = redisTemplate.keys(this.cacheKeyPrefix + "*");
            if (null != keys && keys.size() > 0) {
                Iterator itera = keys.iterator();
                redisTemplate.delete(itera.next());
            }
        }

        @Override
        public int size() {
            Set<K> keys = redisTemplate.keys(this.cacheKeyPrefix + "*");
            return keys.size();
        }

        @Override
        public Set<K> keys() {
            return redisTemplate.keys(this.cacheKeyPrefix + "*");
        }

        @Override
        public Collection<V> values() {
            Set<K> keys = redisTemplate.keys(this.cacheKeyPrefix + "*");
            Set<V> values = new HashSet<V>(keys.size());
            for (K key : keys) {
                values.add((V) redisTemplate.opsForValue().get(this.getRedisCacheKey(key)));
            }
            return values;
        }

        private String getRedisCacheKey(K key) {
            Object redisKey = this.getStringRedisKey(key);
            if (redisKey instanceof String) {
                return this.cacheKeyPrefix + redisKey;
            } else {
                return String.valueOf(redisKey);
            }
        }


        private Object getStringRedisKey(K key) {
            Object redisKey;
            if (key instanceof PrincipalCollection) {
                redisKey = this.getRedisKeyFromPrincipalCollection((PrincipalCollection) key);
            } else {
                redisKey = key.toString();
            }
            return redisKey;
        }

        private Object getRedisKeyFromPrincipalCollection(PrincipalCollection key) {
            List realmNames = this.getRealmNames(key);
            Collections.sort(realmNames);
            Object redisKey = this.joinRealmNames(realmNames);
            return redisKey;
        }

        private List<String> getRealmNames(PrincipalCollection key) {
            ArrayList realmArr = new ArrayList();
            Set realmNames = key.getRealmNames();
            Iterator i$ = realmNames.iterator();
            while (i$.hasNext()) {
                String realmName = (String) i$.next();
                realmArr.add(realmName);
            }
            return realmArr;
        }

        private Object joinRealmNames(List<String> realmArr) {
            StringBuilder redisKeyBuilder = new StringBuilder();
            for (int i = 0; i < realmArr.size(); ++i) {
                String s = realmArr.get(i);
                redisKeyBuilder.append(s);
            }
            String redisKey = redisKeyBuilder.toString();
            return redisKey;
        }
    }
}

然后在shiro配置的时候替换这2个属性

@Autowired
    private RedisTemplate redisTemplate;
@Bean
    public DefaultWebSessionManager defaultWebSessionManager() {
        DefaultWebSessionManager defaultWebSessionManager = new DefaultWebSessionManager();
        defaultWebSessionManager.setSessionDAO(new RedisSessionDao(redisTemplate));
        return defaultWebSessionManager;
    }

    @Bean
    public SecurityManager securityManager(@Qualifier("manageShiroRealm") ManageShiroRealm manageShiroRealm, @Qualifier("defaultWebSessionManager") DefaultWebSessionManager defaultWebSessionManager) {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        //设置自己的ShiroRealm
        securityManager.setSessionManager(defaultWebSessionManager);
        securityManager.setCacheManager(new RedisCacheManager(redisTemplate));
        return securityManager;
    }

session序列化大坑

使用上面的代码就可以把shiro的session存到redis上了,但是还有一个序列化大坑在等着我们
shiro的session的实现类是SimpleSession,这个类是个大坑,重写又很麻烦,很少有人重写,下面我说一下我研究的半天的结果
经常使用的序列化方式有以下几种

  1. java原生序列化,序列化后是二进制,难以查看
  2. Jackson JSON 序列化
  3. FastJSON序列化
  4. ProtoBuff序列化,优点:体积小 速度快 缺点:需要自己写.proto文件,二进制存储,难以查看
    下面我来说一下JSON格式的2中序列化
    b) Jackson2JsonRedisSerializer
    // 指定要序列化的域,field,get和set,以及修饰符范围,ANY是都有包括private和public
    // om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
    // // 指定序列化输入的类型,类必须是非final修饰的,final修饰的类
    // om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
    会出以下问题
  5. isValid()方法会序列化出一个valid,反序列化的时候报错,这个可以设置unknow策略搞定
  6. 但是getAttributesLazy也会被序列化,它没有对应的set方法,走到这里我就不再找解决办法了,应该有解决办法,但是也有可能会出新的问题,时间紧,放弃了
    c) 然后我使用fastJSON来序列化,出现了transient变量不能序列化的问题,这个可以通过修改全局配置来解决
static {
        JSON.DEFAULT_GENERATE_FEATURE = SerializerFeature.config(
                JSON.DEFAULT_GENERATE_FEATURE, SerializerFeature.SkipTransientField, false);
    }

感谢 yarightok

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值