Shiro integrates redis for distributed session processing

aforementioned

This article modifies the code that shiro has to use in a single application , so that shiro uses Redis as session management to realize the distributed session function.

In a cluster environment, we need multiple servers in the cluster to be able to share caches and sessions. The current popular solution is to use the Redis database as the cache server. Shiro does not officially provide integrated support for Redis for caching. There is support for Redis in the official third-party extension library: Shiro-Redis

However, it has been 2 years since this third-party extension was updated recently. Many dependencies are too low and have not been upgraded. The following address is my forkcopy and modified. The current version has been upgraded shiro, jedisand other dependencies have been upgraded, and I will study it later. How to use it fastjson2as serialization. shiro-redis v3.7

The introduction is almost the same, and then we start to implement the use of Redis as a cache for shiro.

1. Modify POM dependencies

1. First remove all  shiro-ehcache relevant dependencies and codes in the project, including   the content in pom.xml, ehcache.xml, andShiroConfig

2. Introduce shiro-redisdependencies

<!-- shiro整合redis:这里直接引用了博主自己升级了有关依赖得jar包 -->
<dependency>
   <groupId>io.github.LiJunYi2</groupId>
   <artifactId>shiro-redis</artifactId>
   <version>3.7</version>
</dependency>
<!-- springboot整合redis -->
 <dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
   <groupId>com.alibaba.fastjson2</groupId>
   <artifactId>fastjson2</artifactId>
   <version>2.0.9</version>
</dependency>
<dependency>
   <groupId>com.alibaba.fastjson2</groupId>
   <artifactId>fastjson2-extension</artifactId>
   <version>2.0.9</version>
</dependency>

2. Yaml adds Redis configuration

spring:
  redis:
    database: 0
    host: 127.0.0.1
    port: 6379
    password:
    timeout: 6000ms
    lettuce:
      pool:
        max-active: 1000
        max-wait: -1ms
        max-idle: 10
        min-idle: 5

3. New Redis serialization configuration

used here isFastJson2

/**
 * @className: FastJson2JsonRedisSerializer
 * @description: Redis使用FastJson序列化
 */
public class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T>
{
    public static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;

    private Class<T> clazz;

    public FastJson2JsonRedisSerializer(Class<T> clazz)
    {
        super();
        this.clazz = clazz;
    }

    @Override
    public byte[] serialize(T t) throws SerializationException
    {
        if (t == null)
        {
            return new byte[0];
        }
        return JSON.toJSONString(t, JSONWriter.Feature.WriteClassName).getBytes(DEFAULT_CHARSET);
    }


    @Override
    public T deserialize(byte[] bytes) throws SerializationException
    {
        if (bytes == null || bytes.length <= 0)
        {
            return null;
        }
        String str = new String(bytes, DEFAULT_CHARSET);

        return JSON.parseObject(str, clazz, JSONReader.Feature.SupportAutoType);
    }
}

/**
 * @className: RedisConfig
 * @description: redis序列化配置
 */
@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport
{
    @Bean
    @SuppressWarnings(value = { "unchecked", "rawtypes" })
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory)
    {
        RedisTemplate<Object, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(connectionFactory);

        FastJson2JsonRedisSerializer serializer = new FastJson2JsonRedisSerializer(Object.class);

        // 使用StringRedisSerializer来序列化和反序列化redis的key值
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(serializer);

        // Hash的key也采用StringRedisSerializer的序列化方式
        template.setHashKeySerializer(new StringRedisSerializer());
        template.setHashValueSerializer(serializer);

        template.afterPropertiesSet();
        return template;
    }
}

Fourth, modify the configuration of shiro

@Configuration
public class ShiroConfig {

    /**
     * redis缓存地址
     */
    @Value("${spring.redis.port}")
    private String redisPort;

    /**
     * redis缓存端口
     */
    @Value("${spring.redis.host}")
    private String redisHost;

    /**
     * redis数据库索引
     */
    @Value("${spring.redis.database}")
    private int database;

    /**
     * redis密码
     */
    @Value("${spring.redis.password}")
    private String password;

    /**
     * 登录网址
     */
    @Value("${shiro.user.loginUrl}")
    private String loginUrl;

    /**
     * 成功的url
     */
    @Value("${shiro.user.successUrl}")
    private String successUrl;

    /**
     * 未经授权的url
     */
    @Value("${shiro.user.unauthorizedUrl}")
    private String unauthorizedUrl;


    /**
     * Cache Manager (shiro-redis)
     */
    @Bean
    public RedisCacheManager redisCacheManager()
    {
        RedisCacheManager redisCacheManager = new RedisCacheManager();
        redisCacheManager.setRedisManager(redisManager());
        redisCacheManager.setPrincipalIdFieldName("loginName");
        redisCacheManager.setValueSerializer(new FstSerializer());
        return redisCacheManager;
    }

    /**
     * RedisManager (shiro-redis)
     */
    @Bean
    public IRedisManager redisManager()
    {
        LettuceRedisManager redisManager = new LettuceRedisManager(redisHost, Convert.toInt(redisPort));
        redisManager.setDatabase(database);
        redisManager.setTimeout(30 * 60);
        return redisManager;
    }

    /**
     *  自定义Realm
     */
    @Bean
    public MyShiroRealm myShiroRealm() {
        MyShiroRealm shiroRealm = new MyShiroRealm();
        shiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());
        shiroRealm.setCacheManager(redisCacheManager());
        return shiroRealm;
    }

    /**
     * 凭证匹配器 (由于我们的密码校验交给Shiro的SimpleAuthenticationInfo进行处理了
     * 所以我们需要修改下doGetAuthenticationInfo中的代码; )
     */
    @Bean
    public HashedCredentialsMatcher hashedCredentialsMatcher() {
        HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
        // 散列算法:这里使用MD5算法;
        hashedCredentialsMatcher.setHashAlgorithmName("md5");
        hashedCredentialsMatcher.setHashIterations(1024);
        return hashedCredentialsMatcher;
    }

    /**
     * 退出过滤器
     */
    public LogoutFilter logoutFilter(){
        LogoutFilter logoutFilter = new LogoutFilter();
        logoutFilter.setLoginUrl(loginUrl);
        logoutFilter.setCacheManager(redisCacheManager());
        return logoutFilter;
    }

    /**
     * RedisSessionDAO (shiro-redis)
     */
    @Bean
    public RedisSessionDAO redisSessionDAO()
    {
        RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
        redisSessionDAO.setRedisManager(redisManager());
        // custom session key prefix
        //redisSessionDAO.setKeyPrefix("");
        // custom session value serializer, default is jdk serializer.
        redisSessionDAO.setValueSerializer(new FstSerializer());
        redisSessionDAO.setExpire(30 * 60);
        return redisSessionDAO;
    }

    /**
     * 会话管理器
     */
    @Bean
    public SessionManager sessionManager(){
        DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
        // 加入缓存管理器
        sessionManager.setCacheManager(redisCacheManager());
        // 去掉JSESSIONID
        sessionManager.setSessionIdUrlRewritingEnabled(false);
        sessionManager.setSessionIdCookie(simpleCookie());
        // 自定义SessionDao
        sessionManager.setSessionDAO(redisSessionDAO());
        return sessionManager;
    }

    /**
     *  安全管理器  securityManager
     */
    @Bean
    public SecurityManager securityManager() {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(myShiroRealm());
        // 注入缓存管理器
        securityManager.setCacheManager(redisCacheManager());
        //注入记住我管理器
        securityManager.setRememberMeManager(rememberMeManager());
        // 注入session管理
        securityManager.setSessionManager(sessionManager());
        return securityManager;
    }


    /**
     * Shiro过滤器配置
     * */
    @Bean
    public ShiroFilterFactoryBean shiroFilter(){
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        // 必须设置 SecurityManager
        shiroFilterFactoryBean.setSecurityManager(securityManager());
        // ......
        return shiroFilterFactoryBean;
    }

    private SimpleCookie simpleCookie()
    {
        SimpleCookie simpleCookie = new SimpleCookie("shiro.sesssion");
        simpleCookie.setPath("/");
        return simpleCookie;
    }

    /**
     * cookie 属性设置
     */
    private SimpleCookie rememberMeCookie(){
        //这个参数是cookie的名称,对应前端的checkbox的name = rememberMe
        SimpleCookie simpleCookie = new SimpleCookie("rememberMe");
        //如果httyOnly设置为true,则客户端不会暴露给客户端脚本代码
        simpleCookie.setHttpOnly(true);
        simpleCookie.setMaxAge(-1);
        simpleCookie.setPath("/");
        return simpleCookie;
    }

    /** rememberMeManager管理器
     * rememberMeManager()方法是生成rememberMe管理器,而且要将这个rememberMe管理器设置到securityManager中
     */
    private CookieRememberMeManager rememberMeManager(){
        CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
        cookieRememberMeManager.setCipherKey(Base64.decode("xxxxxx"));
        cookieRememberMeManager.setCookie(rememberMeCookie());
        return cookieRememberMeManager;
    }


    /**
     * 会话调度器
     */
    @Bean
    public ExecutorServiceSessionValidationScheduler scheduler(){
        ExecutorServiceSessionValidationScheduler scheduler = new ExecutorServiceSessionValidationScheduler();
        scheduler.setInterval(30 * 60 * 1000);
        return scheduler;
    }

    /**
     * 同一个用户多设备登录限制
     */
    public KickoutSessionControlFilter kickoutSessionControlFilter(){
        KickoutSessionControlFilter kickoutSessionControlFilter = new KickoutSessionControlFilter();
        kickoutSessionControlFilter.setCacheManager(redisCacheManager());
        kickoutSessionControlFilter.setSessionManager(sessionManager());
        // 同一个用户最大的会话数,默认-1无限制;比如2的意思是同一个用户允许最多同时两个人登录
        kickoutSessionControlFilter.setMaxSession(1);
        // 是否踢出后来登录的,默认是false;即后者登录的用户踢出前者登录的用户;踢出顺序
        kickoutSessionControlFilter.setKickoutAfter(false);
        kickoutSessionControlFilter.setKickoutUrl("/login?kickout=1");
        return kickoutSessionControlFilter;
    }

    /**
     * 在thymeleaf 使用shiro页面标签
     * */
    @Bean
    public ShiroDialect shiroDialect(){
        return new ShiroDialect();
    }

    /**
     * 开启Shiro注解通知器
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor() {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager());
        return authorizationAttributeSourceAdvisor;
    }

    /**
     * DefaultAdvisorAutoProxyCreator,Spring的一个bean,由Advisor决定对哪些类的方法进行AOP代理。
     */
    @Bean
    @ConditionalOnMissingBean
    public static DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator defaultApp = new DefaultAdvisorAutoProxyCreator();
        defaultApp.setProxyTargetClass(true);
        return defaultApp;
    }
}

test

After starting and logging in to the system, check the cache status of the redis console

微信截图_20220715154434.png

微信截图_20220715154523.png

微信截图_20220715154530.png

Other articles

shiro integrates JWT

shiro custom Realm

shiro get online users

Guess you like

Origin juejin.im/post/7120506532793417741