Hello Mr.J——shiro 实现session共享

  晚是晚了一点,改补的东西还是要补上的。就怕以后你们看不懂啊- -

  shiro实现session共享主要是因为他们身有一个完整的session管理实现,我们要做的也只是根据自己的业务重写shiro中要将session存储到哪,我们这里就存在redis里面了。

  网上是有现成的实现方案的,地址在https://github.com/alexxiyang/shiro-redis,github上有现成的教程,我们用maven添加依赖,然后在Spring中添加一些配置就可以了。

  首先在pom.xml中添加依赖

    <dependency>
        <groupId>org.crazycake</groupId>
        <artifactId>shiro-redis</artifactId>
        <version>2.4.2.1-RELEASE</version>
    </dependency>
  我这里是直接把源码下载下来,添加到了项目中,学习了一下具体的实现机制,顺便修改了一些自己的东西,所以不要直接按照我的xml文件抄,一定要读懂spring的配置。

  继续在上次的技术上添加spring配置。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
	http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"
       default-lazy-init="true">

    <!-- 用于扫描其他的.properties配置文件 -->
    <bean id="propertyConfigurer"
          class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
        <property name="locations">
            <list>
                <value>classpath:shiro.properties</value>
            </list>
        </property>

    </bean>

    <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
        <!-- Shiro的核心安全接口 -->
        <property name="securityManager" ref="securityManager"/>
        <!-- 要求登录时的链接(登录页面地址),非必须的属性,默认会自动寻找Web工程根目录下的"/login.jsp"页面 -->
        <property name="loginUrl" value="${loginUrl}" />
        <!-- 登录成功后要跳转的连接(一般可以在Controller中进行处理) -->
        <property name="successUrl" value="${SuccessUrl}" />
        <!-- 用户访问未对其授权的资源时,所显示的连接 -->
        <property name="unauthorizedUrl" value="${UnauthorizedUrl}"/>

        <!--配置自定义拦截器,这里是cas的登录和退出拦截器-->
        <property name="filters">
            <map>
                <entry key="casFilter">
                    <bean class="org.apache.shiro.cas.CasFilter">
                        <!--配置验证错误时的失败页面 /main 为系统登录页面 -->
                        <property name="failureUrl" value="/message.jsp" />
                    </bean>
                </entry>
                <!-- 重写shiro的logout,shiro执行完logout后使其跳转到cas的登出地址,执行cas的logout-->
                <entry key="logoutFilter">
                    <bean class="org.apache.shiro.web.filter.authc.LogoutFilter">
                        <property name="redirectUrl"
                                  value="${logoutUrl}"/>
                    </bean>
                </entry>
            </map>
        </property>
        <property name="filterChainDefinitions">
            <value>
                <!--不拦截login的url-->
                /login=anon
                /shiro-cas=casFilter

                /logout=logout
                /**=user
            </value>
        </property>
    </bean>

    <!--shiro核心管理类,这里只配置了自己的realm-->
    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <property name="realm" ref="shiroRealm"/>
        <!--加入cas的工厂-->
        <property name="subjectFactory" ref="casSubjectFactory"/>
        <!--加入Session管理类-->
        <property name="sessionManager" ref="sessionManager"/>
    </bean>

    <!-- shiro于数据交互的类 ,自己写的类的实现-ShiroRealmBean自己重写的类的实现 -->
    <bean id="shiroRealm" class="com.tgb.shiro.realm.shiroRealm">
        <!--指定cas的登录地址和前缀-->
        <property name="casServerUrlPrefix" value="${casServerUrlPrefix}"/>
        <property name="casService" value="${casService}"/>
    </bean>

    <!-- 如果要实现cas的remember me的功能,需要用到下面这个bean,并设置到securityManager的subjectFactory中 -->
    <bean id="casSubjectFactory" class="org.apache.shiro.cas.CasSubjectFactory" />

    <!--Session共享配置-->
    <!-- session管理器 -->
    <bean id="sessionManager"
          class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
        <!-- 设置全局会话超时时间,默认30分钟(1800000) -->
        <property name="globalSessionTimeout" value="1800000" />
        <!-- 是否在会话过期后会调用SessionDAO的delete方法删除会话 默认true -->
        <property name="deleteInvalidSessions" value="true" />

        <!-- 会话验证器调度时间 -->
        <property name="sessionValidationInterval" value="1800000" />

        <!-- session存储的实现 -->
        <property name="sessionDAO" ref="redisShiroSessionDAO" />
        <!-- sessionIdCookie的实现,用于重写覆盖容器默认的JSESSIONID -->
        <property name="sessionIdCookie" ref="sharesession" />

        <!--缓存管理-->
        <property name="cacheManager" ref="cacheManager"/>
        <!-- 定时检查失效的session -->
        <property name="sessionValidationSchedulerEnabled" value="true" />
    </bean>

    <!-- session会话存储的实现类 -->
    <bean id="redisShiroSessionDAO" class="com.tgb.shiro.redis.RedisSessionDAO">
        <property name="redisManager" ref="redisManager" />
    </bean>

    <!-- 自定义cacheManager -->
    <bean id="redisManager" class="com.tgb.shiro.redis.RedisManager"/>

    <!-- 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>

    <!-- 自定义redisManager-redis -->
    <bean id="redisCacheManager" class="com.tgb.shiro.redis.RedisCacheManager">
        <property name="redisManager" ref="redisManager" />
    </bean>
</beans>


  最下面的Session部分的配置,和securityManager中加入sessionManger的引用,主要就是这两部分。至于我的SessionDao,redisCache,RedisCacheManager修改了一些具体的内容就不方便贴了,我就贴一下git上的源码,顺便把里面的英文注释改成中文的吧。

  RedisSessionDAO代码如下,主要是集成shiro的抽象sessiondao,重写里面的方法,调用redismanager

package org.creazycake.shiro;

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

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;

/**
 * Created by Rephilo on 2016/12/6 0006.
 */
public class RedisSessionDAO extends AbstractSessionDAO {

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

    private RedisManager redisManager;

    /**
     * shiro-redis的session对象前缀
     */
    private String keyPrefix = "shiro_redis_session:";

    /**
     * 更新session
     * @param session
     * @throws UnknownSessionException
     */
    @Override
    public void update(Session session) throws UnknownSessionException {
        this.saveSession(session);
    }

    /**
     * 保存Session
     * @param session
     * @throws UnknownSessionException
     */
    private void saveSession(Session session) throws UnknownSessionException{
        if(session == null || session.getId() == null){
            logger.error("session or session id is null");
            return;
        }

        byte[] key = getByteKey(session.getId());
        byte[] value = SerializeUtils.serialize(session);
        session.setTimeout(redisManager.getExpire()*1000);
        this.redisManager.set(key, value, redisManager.getExpire());
    }

    /**
     * 删除session
     * @param session
     */
    @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()));

    }

    /**
     * 获取所有的Session
     * @return
     */
    @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)SerializeUtils.deserialize(redisManager.get(key));
                sessions.add(s);
            }
        }

        return sessions;
    }

    /**
     * 创建Session
     * @param session
     * @return sessionid
     */
    @Override
    protected Serializable doCreate(Session session) {
        Serializable sessionId = this.generateSessionId(session);
        this.assignSessionId(session, sessionId);
        this.saveSession(session);
        return sessionId;
    }

    /**
     * 读取redis里的session
     * @param sessionId
     * @return
     */
    @Override
    protected Session doReadSession(Serializable sessionId) {
        if(sessionId == null){
            logger.error("session id is null");
            return null;
        }

        Session s = (Session)SerializeUtils.deserialize(redisManager.get(this.getByteKey(sessionId)));
        return s;
    }

    /**
     * 获得byte[]型的key
     * @param key
     * @return
     */
    private byte[] getByteKey(Serializable sessionId){
        String preKey = this.keyPrefix + sessionId;
        return preKey.getBytes();
    }

    // 后面是getter和setter
    public RedisManager getRedisManager() {
        return redisManager;
    }

    /**
     * 设置redismanager之后会初始化
     * @param redisManager
     */
    public void setRedisManager(RedisManager redisManager) {
        this.redisManager = redisManager;
        //初始化redisManager
        this.redisManager.init();
    }

    public String getKeyPrefix() {
        return keyPrefix;
    }

    public void setKeyPrefix(String keyPrefix) {
        this.keyPrefix = keyPrefix;
    }
}
  RedisManager代码如下,主要是初始化jedis的连接池,其他方法都是调用jedis的方法

package org.creazycake.shiro;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

import java.util.Set;

/**
 * Created by Rephilo on 2016/12/6 0006.
 */
public class RedisManager {

    //redis地址
    private String host = "127.0.0.1";

    //redis端口
    private int port = 6379;

    //过期时间,0-永不过期,其他-过期秒数
    private int expire = 0;

    //jedis连接超时时间,表示多少毫秒没有响应即超时
    private int timeout = 0;

    //redis连接密码
    private String password = "";

    //Jedis连接池
    private static JedisPool jedisPool = null;

    //构造方法,有需要的自己改吧
    public RedisManager() {

    }

    /**
     * 初始化jedis连接池方法
     */
    public void init() {
        if (jedisPool == null) {
            if (password != null && !"".equals(password)) {
                jedisPool = new JedisPool(new JedisPoolConfig(), host, port, timeout, password);
            } else if (timeout != 0) {
                jedisPool = new JedisPool(new JedisPoolConfig(), host, port, timeout);
            } else {
                jedisPool = new JedisPool(new JedisPoolConfig(), host, port);
            }

        }
    }

    /**
     * 从redis取得value
     *
     * @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;
    }

    /**
     * 设置一个kv到redis
     *
     * @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;
    }

    /**
     * 设置一个kv到redis,有过期时间
     *
     * @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;
    }

    /**
     * 根据key值删除value
     *
     * @param key
     */
    public void del(byte[] key) {
        Jedis jedis = jedisPool.getResource();
        try {
            jedis.del(key);
        } finally {
            jedisPool.returnResource(jedis);
        }
    }

    /**
     * 清空redis库
     */
    public void flushDB() {
        Jedis jedis = jedisPool.getResource();
        try {
            jedis.flushDB();
        } finally {
            jedisPool.returnResource(jedis);
        }
    }

    /**
     * 返回当前选择的数据库中key值的数量.
     */
    public Long dbSize() {
        Long dbSize = 0L;
        Jedis jedis = jedisPool.getResource();
        try {
            dbSize = jedis.dbSize();
        } finally {
            jedisPool.returnResource(jedis);
        }
        return dbSize;
    }

    /**
     * 搜索与key的匹配结果;模糊搜索key值
     *
     * @param regex
     * @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 int getTimeout() {
        return timeout;
    }

    public void setTimeout(int timeout) {
        this.timeout = timeout;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

}
  RedisCacheManager代码如下,主要是实现了shiro的CacheManager接口,其实已经属于缓存管理的内容了。这里我并不是很懂,缓存管理这边没有仔细的研究过。
public class RedisCacheManager implements CacheManager {

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

    //jvm本地保存一份redisCache缓存实例,key为查询的key值,value为redisCache实例
    private final ConcurrentMap<String, Cache> caches = new ConcurrentHashMap<String, Cache>();

    //redis的管理类
    private RedisManager redisManager;

    //session前缀
    private String keyPrefix = "shiro_redis_cache:";

    /**
     * 根据key值取出value
     * @param name
     * @param <K>
     * @param <V>
     * @return
     * @throws CacheException
     */
    @Override
    public <K, V> Cache<K, V> getCache(String name) throws CacheException {
        logger.debug("获取名称为: " + name + " 的RedisCache实例");

        //先从jvm缓存查询
        Cache c = caches.get(name);

        //查询为空,连接redis查询
        if (c == null) {

            // 初始化redis管理类
            redisManager.init();

            // 创建一份redisCache
            c = new RedisCache<K, V>(redisManager, keyPrefix);

            // 把redisCache实例加入jvm缓存
            caches.put(name, c);
        }
        return c;
    }


    public RedisManager getRedisManager() {
        return redisManager;
    }

    public void setRedisManager(RedisManager redisManager) {
        this.redisManager = redisManager;
    }

    public String getKeyPrefix() {
        return keyPrefix;
    }

    public void setKeyPrefix(String keyPrefix) {
        this.keyPrefix = keyPrefix;
    }
}
  RedisCache代码如下,其实和sessiondao连差不多,我觉得sessiondao应该调用cachemanager的类的,不应该直接写入redis

package org.creazycake.shiro;

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 java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * Created by Rephilo on 2016/12/6 0006.
 */
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_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(RedisManager cache){
        if (cache == null) {
            throw new IllegalArgumentException("Cache argument cannot be null.");
        }
        this.cache = cache;
    }

    /**
     * 另一个构造函数,可以使用自定义前缀
     * @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{
            return SerializeUtils.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)SerializeUtils.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 {
            cache.set(getByteKey(key), SerializeUtils.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);
            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);
        }
    }

}
  以上就是使用shiro实现session共享的基本原理了,至于有没有必要联系作者更新一下sessionDAO里面的东西。。。git上最新的是3年之前了。大伙凑合着用吧,那天有心情我更一下试试- -

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值