springboot+shiro+redis缓存+redis会话管理(自定义)

首先要想通过Redis进行会话管理和缓存的话   就要实现这些各类 Cache、CacheManager、CachingSessionDao都是shiro里面的类。

讲一下在写代码中遇到的坑

1.序列化和反序列化的问题

序列化的问题主要是体现在对session的id进行序列化的时候会出现一个问题,可以利用Apache的common的lang3组件里面有对序列化操作的工具类。但是我在处理的过程中出现了一个小bug,还不知道原因是什么,这边说一下,经过RedisSessionDao操作后,在Redis里面存储了一个session,但是其key值很奇怪,在我写的key值前面还加了一些内容,当时不知道怎么回事,后来通过对key值进行处理,才解决了这个问题。

处理代码:

private byte[] getByteKey(Object k){
    if(k instanceof String){
        String key = PREFIX+k;
        return key.getBytes();
    }else {
        return SerializationUtils.serialize((Serializable) k);
    }
}

2.会话管理和缓存执行的顺序的问题

这里要说一下,Redis的会话管理主要是为了能够做到服务器集群的,所以其 主要作用是对Redis的时间进行设置的,在进行RedisSessionDao操作之后shiro还会进行缓存设置,也就是再在Redis上面设置session,所以2个key值要一样,不然会出现2个session,会导致一些小问题。这里要讲一下授权,授权会通过Redis缓存,把数据缓存到Redis上面,他的key值一般是对象。

3.会话管理的一些小问题

如果你要用Redis实现会话管理,就必须要自己实现Redis的缓存,这样才可以使用Redis的会话管理;当你只要缓存的时候可以不去实现会话管理,用shiro提供的默认会话管理也是可以的。

下面就献上我的代码:

pom.xml文件


    org.springframework.boot
    spring-boot-starter-parent
    1.5.1.RELEASE


    1.8


    
        org.springframework.boot
        spring-boot-starter-data-jpa
    
    
        org.springframework.data
        spring-data-jpa
        1.11.3.RELEASE
    
    
        org.hibernate.javax.persistence
        hibernate-jpa-2.1-api
        1.0.0.Final
    
    
        org.springframework.boot
        spring-boot-starter-thymeleaf
    
    
        net.sourceforge.nekohtml
        nekohtml
        1.9.22
    
    
        org.springframework.boot
        spring-boot-starter-web
    
    
        org.apache.shiro
        shiro-spring
        1.4.0
    
    
        mysql
        mysql-connector-java
        runtime
    
    
        com.alibaba
        druid
        1.0.11
    
    
    
        com.github.penggle
        kaptcha
        2.3.2
    
    
        javax.servlet
        javax.servlet-api
    
    
        redis.clients
        jedis
    
    
        org.apache.commons
        commons-lang3
        3.4
    
    
        commons-lang
        commons-lang
        2.6
    


    
        
            org.springframework.boot
            spring-boot-maven-plugin
        
    

shiro配置

import com.google.code.kaptcha.servlet.KaptchaServlet;
import com.youxiong.dao.UserReposisty;
import com.youxiong.domain.Permission;
import com.youxiong.domain.Role;
import com.youxiong.domain.UserInfo;
import com.youxiong.filter.FormValid;
import com.youxiong.redis.JedisCacheManager;
import com.youxiong.redis.RedisSessionDao;
import com.youxiong.redis.RedisSessionListener;
import com.youxiong.redis.RediseSessionFactory;
import com.youxiong.shiro.MyShiroRealm;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.SessionListener;
import org.apache.shiro.session.mgt.SessionFactory;
import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO;
import org.apache.shiro.session.mgt.eis.SessionDAO;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.handler.SimpleMappingExceptionResolver;

import javax.servlet.Filter;
import java.util.*;

@Configuration
public class ShiroConfig {

    @Autowired
    private UserReposisty userReposisty;

    @Bean
    public ShiroFilterFactoryBean createShiroFilter(SecurityManager securityManager) {
        System.out.println("--------ShiroFilterFactoryBean-------");
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        Map, Filter> filterMap = new HashMap<>();
        //map里面key值要为authc才能使用自定义的过滤器
        filterMap.put("authc", formValid());

        // can go to login
        shiroFilterFactoryBean.setLoginUrl("/login.html");
        //doLogin success go to page
        shiroFilterFactoryBean.setSuccessUrl("/success.html");
        //do not Unauthorized page
        shiroFilterFactoryBean.setUnauthorizedUrl("/403.html");
        Map, String> map = new LinkedHashMap, String>();
        //验证码的路径   不要跟下面需要认证的写在一个路径里  会被拦截的
        map.put("/servlet/**", "anon");
        //需要把要授权的URL  全部装到filterChain中去过滤
        UserInfo userInfo = userReposisty.findByUid(1);
        for (Role role : userInfo.getRoles()) {
            for (Permission permission : role.getPermissions()) {
                if (permission.getUrl() != "") {
                    String permissions = "perms[" + permission.getPermission() + "]";
                    map.put(permission.getUrl(), permissions);
                }
            }
        }
        map.put("/user*/*", "authc");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
        shiroFilterFactoryBean.setFilters(filterMap);

        return shiroFilterFactoryBean;
    }

    //自己定义realm
    @Bean
    public MyShiroRealm myShiroRealm() {
        MyShiroRealm myShiroRealm = new MyShiroRealm();
        return myShiroRealm;
    }

    @Bean
    public SecurityManager securityManager() {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(myShiroRealm());
        //缓存管理  
        securityManager.setCacheManager(jedisCacheManager());
        //会话管理  
        securityManager.setSessionManager(sessionManager());
        return securityManager;
    }

    //密码盐   可以不必实现    因为一般密码可以自己定义自己的密码加密规则
/*    @Bean
    public HashedCredentialsMatcher hashedCredentialsMatcher(){
        HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
        hashedCredentialsMatcher.setHashAlgorithmName("md5");
        hashedCredentialsMatcher.setHashIterations(2);
        return hashedCredentialsMatcher;
    }*/

   //开启aop注解
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }

    @Bean(name = "simpleMappingExceptionResolver")
    public SimpleMappingExceptionResolver
    createSimpleMappingExceptionResolver() {
        SimpleMappingExceptionResolver r = new SimpleMappingExceptionResolver();
        Properties mappings = new Properties();
        mappings.setProperty("DatabaseException", "databaseError");//数据库异常处理
        mappings.setProperty("UnauthorizedException", "403");
        r.setExceptionMappings(mappings);  // None by default
        r.setDefaultErrorView("error");    // No default
        r.setExceptionAttribute("ex");     // Default is "exception"
        //r.setWarnLogCategory("example.MvcLogger");     // No default
        return r;
    }

    //servlet注册器   -----》验证码的路径
    @Bean
    public ServletRegistrationBean servletRegistrationBean() {
        System.out.println("----验证码---");
        return new ServletRegistrationBean(new KaptchaServlet(), "/servlet/kaptcha.jpg");
    }


    //自定义过滤器 ---》里面实现了对验证码校验
    @Bean("myFilter")
    public FormValid formValid() {
        return new FormValid();
    }


    //jedis缓存
    @Bean
    public JedisCacheManager jedisCacheManager() {
        return new JedisCacheManager();
    }

    @Bean
    public SessionManager sessionManager() {
        DefaultWebSessionManager defaultWebSessionManager = new DefaultWebSessionManager();
        defaultWebSessionManager.setSessionIdCookie(simpleCookie());
        defaultWebSessionManager.setSessionDAO(sessionDAO());
        //可以设置shiro提供的会话管理机制
        //defaultWebSessionManager.setSessionDAO(new EnterpriseCacheSessionDAO());
        return defaultWebSessionManager;
    }



    //这里就是会话管理的操作类  
    @Bean
    public SessionDAO sessionDAO() {
        return new RedisSessionDao();
    }

    //这里需要设置一个cookie的名称  原因就是会跟原来的session的id值重复的
    @Bean
    public SimpleCookie simpleCookie() {
        SimpleCookie simpleCookie = new SimpleCookie("REDISSESSION");
        return simpleCookie;
    }
}
JedisUtil工具类  ---》对Redis进行操作的类

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

public class JedisUtil {

    private static JedisPool jedisPool;

    static {
        JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
        jedisPoolConfig.setMaxTotal(100);
        jedisPoolConfig.setMaxIdle(10);
        jedisPoolConfig.setMaxWaitMillis(100);
        jedisPool = new JedisPool(jedisPoolConfig,"127.0.0.1",6379);
    }

    public static Jedis getJedis(){
        return jedisPool.getResource();
    }

    public static void closeJedis(Jedis jedis){
        jedis.close();
    }

}

JedisCache  ----》这个类就是做缓存用的

import org.apache.commons.lang3.SerializationUtils;
import org.apache.shiro.cache.Cache;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import redis.clients.jedis.Jedis;


import java.io.Serializable;
import java.util.*;

public class JedisCache<K,V> implements Cache<K, V> ,Serializable{
    private static final Logger LOGGER = LoggerFactory.getLogger(JedisCache.class);

    private static final String PREFIX = "SHIRO_SESSION_ID";

    private byte[] getByteKey(K k){
        if(k instanceof String){
            String key = PREFIX+k;
            return key.getBytes();
        }else {
            return SerializationUtils.serialize((Serializable) k);
        }
    }

    @Override
    public int size() {
        Jedis jedis = JedisUtil.getJedis();
        Long size = jedis.dbSize();
        return size.intValue();
    }

    @Override
    public Set<K> keys() {
        Jedis jedis = JedisUtil.getJedis();

        Set<byte[]> bytes = jedis.keys( (PREFIX + new String("*")).getBytes());
        Set<K>  keys = new HashSet<>();
        if(bytes!=null){
            for (byte[] b: bytes) {
                keys.add(SerializationUtils.deserialize(b));
            }
        }
        JedisUtil.closeJedis(jedis);
        return  keys;
    }

    @Override
    public Collection<V> values() {
        Set<K> keys = this.keys();
        Jedis jedis = JedisUtil.getJedis();
        List<V> lists = new ArrayList<>();
        for (K k:keys) {
            byte[] bytes = jedis.get(getByteKey(k));
            lists.add(SerializationUtils.deserialize(bytes));
        }
        JedisUtil.closeJedis(jedis);
        return lists;
    }

    @Override
    public void clear() {
        JedisUtil.getJedis().flushDB();
    }

    @Override
    public V put(K k, V v) {
        LOGGER.info("key---->"+k+"value---->"+v);
        Jedis jedis = JedisUtil.getJedis();
        jedis.set(getByteKey(k), SerializationUtils.serialize((Serializable) v));
        jedis.expire(getByteKey(k),10000);
        byte[] bytes = jedis.get(SerializationUtils.serialize(getByteKey(k)));
        JedisUtil.closeJedis(jedis);
        if(bytes==null){
            return null;
        }
        return SerializationUtils.deserialize(bytes);
    }

    @Override
    public V get(K k) {
        LOGGER.info("get------>key="+k);
        if(k==null){
            return null;
        }
        //System.out.println(k);
        Jedis jedis = JedisUtil.getJedis();
        byte[] bytes = jedis.get(getByteKey(k));
        JedisUtil.closeJedis(jedis);
        if(bytes==null){
            return null;
        }
        return SerializationUtils.deserialize(bytes);
    }

    @Override
    public V remove(K k) {
        Jedis jedis = JedisUtil.getJedis();
        byte[] bytes = jedis.get(getByteKey(k));
        jedis.del(getByteKey(k));
        JedisUtil.closeJedis(jedis);
        if(bytes==null){
            return null;
        }
        return SerializationUtils.deserialize(bytes);
    }




}

JedisCacheManager  ----》这个类主要是创建JedisCache的

import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheException;
import org.apache.shiro.cache.CacheManager;

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

public class JedisCacheManager implements CacheManager {

    private final ConcurrentMap, Cache> caches = new ConcurrentHashMap, Cache>();
    //cache
    @Override
    public <K, V> Cache<K, V> getCache(String s) throws CacheException {
        Cache cache = caches.get(s);
        if(cache == null){
            cache = new JedisCache();
            caches.put(s,cache);
        }

        return cache;
    }
}

RedisSessionDao       ----》这个类就是对会话就行操作的,也就管理会话的,有创建、更新、移除等操作,都可以自己实现

import org.apache.commons.lang3.SerializationUtils;
import org.apache.shiro.session.Session;
import org.apache.shiro.session.UnknownSessionException;
import org.apache.shiro.session.mgt.SimpleSession;
import org.apache.shiro.session.mgt.ValidatingSession;
import org.apache.shiro.session.mgt.eis.CachingSessionDAO;
import org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO;
import org.apache.shiro.session.mgt.eis.SessionDAO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Transaction;

import java.io.Serializable;
import java.util.Base64;
import java.util.Collection;

public class RedisSessionDao extends CachingSessionDAO {

    private static final String PREFIX = "SHIRO_SESSION_ID";

    private static final int EXPRIE = 10000;

    private static final Logger LOGGER = LoggerFactory.getLogger(RedisSessionDao.class);


    @Override
    protected Serializable doCreate(Session session) {
        LOGGER.info("--------doCreate-----");
        Serializable serializable = this.generateSessionId(session);
        assignSessionId(session, serializable);
        Jedis jedis = JedisUtil.getJedis();
        session.setTimeout(EXPRIE*1000);
        /*jedis.set(getByteKey(serializable),SerializationUtils.serialize((Serializable)session));
        jedis.expire(SerializationUtils.serialize(getByteKey(serializable)),EXPRIE);*/
        jedis.setex(getByteKey(serializable),EXPRIE,SerializationUtils.serialize((Serializable)session) );
        JedisUtil.closeJedis(jedis);
        return serializable;
    }


    @Override
    protected Session doReadSession(Serializable serializable) {
        LOGGER.info("--------doReadSession-----");
        Jedis jedis = JedisUtil.getJedis();
        Session session = null;
        byte[] s = jedis.get(getByteKey(serializable));
        if (s != null) {
            session = SerializationUtils.deserialize(s);
            jedis.expire((PREFIX+serializable).getBytes(),EXPRIE);
        }
        //判断是否有会话  没有返回NULL
        if(session==null){
            return null;
        }
        JedisUtil.closeJedis(jedis);
        return session;
    }

    private byte[] getByteKey(Object k){
        if(k instanceof String){
            String key = PREFIX+k;
            return key.getBytes();
        }else {
            return SerializationUtils.serialize((Serializable) k);
        }
    }
    @Override
    protected void doUpdate(Session session) {
        LOGGER.info("--------doUpdate-----");
       if(session==null){
           return ;
       }
        Jedis jedis = JedisUtil.getJedis();
       session.setTimeout(EXPRIE*1000);
       /*jedis.set(getByteKey(session.getId()),SerializationUtils.serialize((Serializable)session));
       jedis.expire(SerializationUtils.serialize((PREFIX+session.getId())),EXPRIE);*/
        jedis.setex(getByteKey(session.getId()),EXPRIE,SerializationUtils.serialize((Serializable)session) );


    }


    @Override
    protected void doDelete(Session session) {
        LOGGER.info("--------doDelete-----");
        Jedis jedis = JedisUtil.getJedis();
        jedis.del(getByteKey(session.getId()));
        JedisUtil.closeJedis(jedis);

    }


}

你可能感兴趣的:(shiro,springboot)