• SpringBoot搭建基于Apache Shiro+Redis的分布式Session共享功能


    我们在上一遍文档中已经完成了Shiro验证功能。(http://www.cnblogs.com/nbfujx/p/7773789.html),在此基础上我们将完成分布式Session共享功能。

    Redis的使用

    Maven Plugin添加Redis相关jar包

    1   <dependency>
    2             <groupId>org.springframework.boot</groupId>
    3             <artifactId>spring-boot-starter-data-redis</artifactId>
    4         </dependency>
    View Code

    添加Redis配置文件

     1 package com.goku.webapi.config.redis;
     2 
     3 import com.fasterxml.jackson.databind.DeserializationFeature;
     4 import org.springframework.beans.factory.annotation.Value;
     5 import org.springframework.cache.CacheManager;
     6 import org.springframework.cache.annotation.CachingConfigurerSupport;
     7 import org.springframework.cache.annotation.EnableCaching;
     8 import org.springframework.context.annotation.Bean;
     9 import org.springframework.context.annotation.Configuration;
    10 import org.springframework.data.redis.cache.RedisCacheManager;
    11 import org.springframework.data.redis.connection.RedisConnectionFactory;
    12 import org.springframework.data.redis.core.RedisTemplate;
    13 import org.springframework.data.redis.core.StringRedisTemplate;
    14 import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
    15 
    16 import com.fasterxml.jackson.annotation.JsonAutoDetect;
    17 import com.fasterxml.jackson.annotation.PropertyAccessor;
    18 import com.fasterxml.jackson.databind.ObjectMapper;
    19 import org.springframework.data.redis.serializer.StringRedisSerializer;
    20 
    21 /**
    22  * Created by nbfujx on 2017/10/19.
    23  */
    24 @Configuration
    25 @EnableCaching
    26 public class RedisCacheConfig  extends CachingConfigurerSupport {
    27 
    28     @Value("${spring.redis.host}")
    29     private String host;
    30     @Value("${spring.redis.port}")
    31     private int port;
    32     @Value("${spring.redis.timeout}")
    33     private int timeout;
    34 
    35     @Bean
    36     public CacheManager cacheManager(RedisTemplate<Object, Object> redisTemplate) {
    37         RedisCacheManager cacheManager = new RedisCacheManager(redisTemplate);
    38         cacheManager.setDefaultExpiration(1800);
    39         return cacheManager;
    40     }
    41 
    42     @Bean
    43     public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory factory) {
    44         RedisTemplate<Object, Object> template = new RedisTemplate<>();
    45         template.setConnectionFactory(factory);
    46         template.setKeySerializer(new StringRedisSerializer());
    47         template.setValueSerializer(new RedisObjectSerializer());
    48         return template;
    49     }
    50 }
    View Code

    添加RedisSessionDAO配置文件

     1 package com.goku.webapi.config.Shiro;
     2 
     3 import org.apache.shiro.session.Session;
     4 import org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO;
     5 import org.slf4j.Logger;
     6 import org.slf4j.LoggerFactory;
     7 import org.springframework.data.redis.core.RedisTemplate;
     8 
     9 import javax.annotation.Resource;
    10 import java.io.Serializable;
    11 import java.util.concurrent.TimeUnit;
    12 
    13 /**
    14  * Created by nbfujx on 2017-11-08.
    15  */
    16 public class RedisSessionDAO  extends EnterpriseCacheSessionDAO {
    17 
    18     // session 在redis过期时间是30分钟30*60
    19     private static final int EXPIRE_TIME = 1800;
    20     private static Logger logger = LoggerFactory.getLogger(RedisSessionDAO.class);
    21     private static String prefix = "shiro-session:";
    22 
    23     @Resource
    24     private RedisTemplate<String, Object> redisTemplate;
    25 
    26     // 创建session,保存到数据库
    27     @Override
    28     protected Serializable doCreate(Session session) {
    29         Serializable sessionId = super.doCreate(session);
    30         this.logger.info("创建session:{}", session.getId());
    31         redisTemplate.opsForValue().set(prefix + sessionId.toString(), session);
    32         return sessionId;
    33     }
    34 
    35     // 获取session
    36     @Override
    37     protected Session doReadSession(Serializable sessionId) {
    38         this.logger.info("获取session:{}", sessionId);
    39         // 先从缓存中获取session,如果没有再去数据库中获取
    40         Session session = super.doReadSession(sessionId);
    41         if (session == null) {
    42             session = (Session) redisTemplate.opsForValue().get(prefix + sessionId.toString());
    43         }
    44         return session;
    45     }
    46 
    47     // 更新session的最后一次访问时间
    48     @Override
    49     protected void doUpdate(Session session) {
    50         super.doUpdate(session);
    51         this.logger.info("获取session:{}", session.getId());
    52         String key = prefix + session.getId().toString();
    53         if (!redisTemplate.hasKey(key)) {
    54             redisTemplate.opsForValue().set(key, session);
    55         }
    56         redisTemplate.expire(key, EXPIRE_TIME, TimeUnit.SECONDS);
    57     }
    58 
    59     // 删除session
    60     @Override
    61     protected void doDelete(Session session) {
    62         this.logger.info("删除session:{}", session.getId());
    63         super.doDelete(session);
    64         redisTemplate.delete(prefix + session.getId().toString());
    65     }
    66 }
    View Code

    添加ShiroCache配置文件

     1 package com.goku.webapi.config.Shiro;
     2 
     3 import org.apache.shiro.cache.Cache;
     4 import org.apache.shiro.cache.CacheException;
     5 import org.springframework.data.redis.core.RedisTemplate;
     6 
     7 import java.util.ArrayList;
     8 import java.util.Collection;
     9 import java.util.List;
    10 import java.util.Set;
    11 import java.util.concurrent.TimeUnit;
    12 
    13 /**
    14  * Created by nbfujx on 2017/11/8.
    15  */
    16 public class  ShiroCache<K, V> implements Cache<K, V> {
    17 
    18     private static final String REDIS_SHIRO_CACHE = "shiro-cache:";
    19     private static final long GLOB_EXPIRE = 30;
    20     private String cacheKey;
    21     private RedisTemplate<K, V> redisTemplate;
    22 
    23     public ShiroCache(RedisTemplate<K, V> client, String name) {
    24         this.cacheKey = REDIS_SHIRO_CACHE + name + ":";
    25         this.redisTemplate = client;
    26     }
    27 
    28     @Override
    29     public V get(K key) throws CacheException {
    30         redisTemplate.boundValueOps(getCacheKey(key)).expire(GLOB_EXPIRE, TimeUnit.MINUTES);
    31         return redisTemplate.boundValueOps(getCacheKey(key)).get();
    32     }
    33 
    34     @Override
    35     public V put(K key, V value) throws CacheException {
    36         V old = get(key);
    37         redisTemplate.boundValueOps(getCacheKey(key)).set(value);
    38         return old;
    39     }
    40 
    41     @Override
    42     public V remove(K key) throws CacheException {
    43         V old = get(key);
    44         redisTemplate.delete(getCacheKey(key));
    45         return old;
    46     }
    47 
    48     @Override
    49     public void clear() throws CacheException {
    50         redisTemplate.delete(keys());
    51     }
    52 
    53     @Override
    54     public int size() {
    55         return keys().size();
    56     }
    57 
    58     @Override
    59     public Set<K> keys() {
    60         return redisTemplate.keys(getCacheKey("*"));
    61     }
    62 
    63     @Override
    64     public Collection<V> values() {
    65         Set<K> set = keys();
    66         List<V> list = new ArrayList<>();
    67         for (K s : set) {
    68             list.add(get(s));
    69         }
    70         return list;
    71     }
    72 
    73     private K getCacheKey(Object k) {
    74         return (K) (this.cacheKey + k);
    75     }
    76 }
    View Code

    添加RedisCacheManager配置文件

     1 package com.goku.webapi.config.Shiro;
     2 
     3 import javax.annotation.Resource;
     4 
     5 import com.goku.webapi.config.Shiro.ShiroCache;
     6 import org.apache.shiro.cache.AbstractCacheManager;
     7 import org.apache.shiro.cache.Cache;
     8 import org.apache.shiro.cache.CacheException;
     9 import org.springframework.data.redis.core.RedisTemplate;
    10 /**
    11  * Created by nbfujx on 2017-11-08.
    12  */
    13 public class RedisCacheManager extends AbstractCacheManager {
    14 
    15     @Resource
    16     private RedisTemplate<String, Object> redisTemplate;
    17 
    18     @Override
    19     protected Cache<String, Object> createCache(String name) throws CacheException {
    20         return new ShiroCache<>(redisTemplate, name);
    21     }
    22 }
    View Code

    调整ShiroConfi配置文件

      1 package com.goku.webapi.config.Shiro;
      2 
      3 import org.apache.shiro.session.mgt.SessionManager;
      4 import org.apache.shiro.spring.LifecycleBeanPostProcessor;
      5 import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
      6 import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
      7 import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
      8 import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
      9 import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
     10 import org.springframework.context.annotation.Bean;
     11 import org.springframework.context.annotation.Configuration;
     12 import org.springframework.context.annotation.DependsOn;
     13 
     14 import javax.servlet.Filter;
     15 import java.util.LinkedHashMap;
     16 import java.util.Map;
     17 
     18 
     19 /**
     20  * shiro配置类
     21  * Created by nbfujx on 2017/11/7.
     22  */
     23 @Configuration
     24 public class ShiroConfig {
     25 
     26     /**
     27      * LifecycleBeanPostProcessor,这是个DestructionAwareBeanPostProcessor的子类,
     28      * 负责org.apache.shiro.util.Initializable类型bean的生命周期的,初始化和销毁。
     29      * 主要是AuthorizingRealm类的子类,以及EhCacheManager类。
     30      */
     31     @Bean(name = "lifecycleBeanPostProcessor")
     32     public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
     33         return new LifecycleBeanPostProcessor();
     34     }
     35 
     36 
     37     /**
     38      * ShiroRealm,这是个自定义的认证类,继承自AuthorizingRealm,
     39      * 负责用户的认证和权限的处理,可以参考JdbcRealm的实现。
     40      */
     41     @Bean(name = "shiroRealm")
     42     @DependsOn("lifecycleBeanPostProcessor")
     43     public ShiroRealm shiroRealm() {
     44         ShiroRealm realm = new ShiroRealm();
     45         realm.setCacheManager(redisCacheManager());
     46         return realm;
     47     }
     48 
     49     @Bean
     50     public RedisCacheManager redisCacheManager() {
     51         return new RedisCacheManager();
     52     }
     53 
     54     @Bean(name = "redisSessionDAO")
     55     public RedisSessionDAO sessionDAO() {
     56         RedisSessionDAO sessionDAO = new RedisSessionDAO();
     57         return sessionDAO;
     58     }
     59 
     60     /**
     61      * SessionManager session管理
     62      */
     63     @Bean
     64     public SessionManager sessionManager() {
     65         DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
     66         sessionManager.setSessionDAO(sessionDAO());
     67         sessionManager.setGlobalSessionTimeout(1800);
     68         sessionManager.setCacheManager(redisCacheManager());
     69         return sessionManager;
     70     }
     71 
     72     /**
     73      * SecurityManager,权限管理,这个类组合了登陆,登出,权限,session的处理,是个比较重要的类。
     74      */
     75     @Bean(name = "securityManager")
     76     public DefaultWebSecurityManager securityManager() {
     77         DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
     78         securityManager.setRealm(shiroRealm());
     79         securityManager.setCacheManager(redisCacheManager());
     80         securityManager.setSessionManager(sessionManager());
     81         return securityManager;
     82     }
     83 
     84 
     85     /**
     86      * ShiroFilterFactoryBean,是个factorybean,为了生成ShiroFilter。
     87      * 它主要保持了三项数据,securityManager,filters,filterChainDefinitionManager。
     88      */
     89     @Bean(name = "shiroFilter")
     90     public ShiroFilterFactoryBean shiroFilterFactoryBean() {
     91         ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
     92         shiroFilterFactoryBean.setSecurityManager(securityManager());
     93 
     94         Map<String, Filter> filters = new LinkedHashMap<String, Filter>();
     95         shiroFilterFactoryBean.setFilters(filters);
     96 
     97 
     98         Map<String, String> filterChainDefinitionManager = new LinkedHashMap<String, String>();
     99         filterChainDefinitionManager.put("/login", "anon");
    100         filterChainDefinitionManager.put("/logout", "anon");
    101         filterChainDefinitionManager.put("/sysUser/*", "authc,perms");//"authc,perms[sysUser:*]");
    102         filterChainDefinitionManager.put("/sysMenu/*", "authc,perms");//"authc,perms[sysUser:*]");
    103         filterChainDefinitionManager.put("/*", "anon");
    104         shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionManager);
    105 
    106         shiroFilterFactoryBean.setLoginUrl("/notAuthc");
    107         shiroFilterFactoryBean.setSuccessUrl("/");
    108         shiroFilterFactoryBean.setUnauthorizedUrl("/notAuthz");
    109         return shiroFilterFactoryBean;
    110     }
    111 
    112     /**
    113      * DefaultAdvisorAutoProxyCreator,Spring的一个bean,由Advisor决定对哪些类的方法进行AOP代理。
    114      */
    115     @Bean
    116     @DependsOn("lifecycleBeanPostProcessor")
    117     public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
    118         DefaultAdvisorAutoProxyCreator defaultAAP = new DefaultAdvisorAutoProxyCreator();
    119         defaultAAP.setProxyTargetClass(true);
    120         return defaultAAP;
    121     }
    122 
    123     /**
    124      * AuthorizationAttributeSourceAdvisor,shiro里实现的Advisor类,
    125      * 内部使用AopAllianceAnnotationsAuthorizingMethodInterceptor来拦截用以下注解的方法。
    126      */
    127     @Bean
    128     public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor() {
    129         AuthorizationAttributeSourceAdvisor aASA = new AuthorizationAttributeSourceAdvisor();
    130         aASA.setSecurityManager(securityManager());
    131         return aASA;
    132     }
    133 
    134 
    135 
    136 
    137 }
    View Code

    新增RedisCacheManager,RedisSessionDAO,sessionManager相关bean,

    securityManager 增加 securityManager.setSessionManager(sessionManager());

    Shiro-Redis的使用验证

    先进行登录验证操作

    查看redis存储数据

    进行数据查询

    查看日志是否从redis获取

     

    GITHUB

    github :  https://github.com/nbfujx/learn-java-demo/tree/master/Goku.WebService.Simple.Redis.Shiro

  • 相关阅读:
    什么是多线程中的上下文切换?
    java 中有几种方法可以实现一个线程?
    什么叫线程安全?servlet 是线程安全吗?
    什么是线程调度器(Thread Scheduler)和时间分片(Time Slicing )?
    一个线程运行时发生异常会怎样?
    我的样式
    springboot+dynamic多数据源配置
    mybatis plus条件拼接
    springboot配置虚拟路径
    springboot+mybatis+Druid配置多数据源(mysql+postgre)
  • 原文地址:https://www.cnblogs.com/nbfujx/p/7773833.html
Copyright © 2020-2023  润新知