2023-03-11
原文作者:柒's Blog 原文地址:https://blog.52itstyle.vip/archives/1304/

202303111618024541.png

前言

前段时间做了一个图床的小项目,安全框架使用的是Shiro。为了使用户7x24小时访问,决定把项目由单机升级为集群部署架构。但是安全框架shiro只有单机存储的SessionDao,尽管Shrio有基于Ehcache-rmi的组播/广播实现,然而集群的分布往往是跨网段的,甚至是跨地域的,所以寻求新的方案。

架构

202303111618029692.png

方案

使用 redis 集中存储,实现分布式集群共享用户信息,这里我们采用第三方开源插件crazycake来实现,pom.xml 引入:

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    <dependency>
        <groupId>org.crazycake</groupId>
        <artifactId>shiro-redis</artifactId>
        <version>3.2.3</version>
    </dependency>

配置 application.properties

    # Redis
    # 数据库索引(默认为0)
    redis.database=0
    # 服务器地址 变更为自己的
    redis.host=127.0.0.1
    # 服务器连接端口
    redis.port=6379
    # 服务器连接密码,如果不设置密码注释掉即可
    # redis.password=
    # 连接超时时间(毫秒)
    redis.timeout=30000

本来crazycake插件已经实现了RedisManager,但是参数不可配,这里我们需要自己重写一下:

    public class RedisManager extends WorkAloneRedisManager implements IRedisManager {
    
        private RedisProperties redis;
    
        private JedisPool jedisPool;
    
        public RedisManager(RedisProperties redis) {
            this.redis = redis;
        }
    
        private void init() {
            synchronized(this) {
                if (this.jedisPool == null) {
                    this.jedisPool = new JedisPool(this.getJedisPoolConfig(), redis.getHost(), redis.getPort(),
                            redis.getTimeout(), redis.getPassword(), redis.getDatabase());
                }
            }
        }
    
        @Override
        protected Jedis getJedis() {
            if (this.jedisPool == null) {
                this.init();
            }
            return this.jedisPool.getResource();
        }
    }

参数配置 RedisProperties

    @Data
    @ConfigurationProperties(prefix = "redis")
    public class RedisProperties {
    
        private String host;
        private int port;
        private int timeout;
        private String password;
        private int database;
    }

配置 ShiroConfig

    /**
     * Shiro权限配置
     * 一定要配置 @Configuration 和 @EnableConfigurationProperties 注解
     */
    @Configuration
    @EnableConfigurationProperties({RedisProperties.class})
    public class ShiroConfig {
    
        private RedisProperties redis;
    
        public ShiroConfig(RedisProperties redis) {
            this.redis = redis;
        }
    
        @Bean
        public UserRealm userRealm() {
            return new UserRealm();
        }
    
        @Bean
        public ShiroFilterFactoryBean shiroFilterFactoryBean (SecurityManager securityManager) {
            ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
            shiroFilterFactoryBean.setSecurityManager(securityManager);
            shiroFilterFactoryBean.setLoginUrl("/index.html");
            shiroFilterFactoryBean.setUnauthorizedUrl("/403");
            // 拦截器
            Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
            /**
             * 静态文件
             */
            filterChainDefinitionMap.put("/file/**","anon");
            /**
             * 登录注册
             */
            filterChainDefinitionMap.put("/register.shtml","anon");
            filterChainDefinitionMap.put("/login.shtml","anon");
            /**
             * 管理后台
             */
            filterChainDefinitionMap.put("/sys/**", "roles[admin]");
            filterChainDefinitionMap.put("/**", "authc");
            shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
            return shiroFilterFactoryBean;
        }
    
        @Bean
        public SessionsSecurityManager securityManager() {
            DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
            securityManager.setRealm(userRealm());
            securityManager.setCacheManager(cacheManager());
            securityManager.setSessionManager(sessionManager());
            return securityManager;
        }
        @Bean
        public DefaultWebSessionManager sessionManager() {
            DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
            sessionManager.setSessionIdUrlRewritingEnabled(false);
            sessionManager.setSessionDAO(redisSessionDAO());
            return sessionManager;
        }
        @Bean
        public ShiroDialect shiroDialect(){
            return new ShiroDialect();
        }
    
        /**
         * cacheManager 缓存 redis实现
         * @return
         */
        public RedisCacheManager cacheManager() {
            RedisCacheManager redisCacheManager = new RedisCacheManager();
            redisCacheManager.setRedisManager(redisManager());
            return redisCacheManager;
        }
    
        /**
         * 配置shiro redisManager
         * @return
         */
        public RedisManager redisManager() {
            RedisManager redisManager = new RedisManager(redis);
            return redisManager;
        }
    
        /**
         * RedisSessionDAO shiro sessionDao层的实现
         * 原理就是重写 AbstractSessionDAO
         * 有兴趣的小伙伴自行阅读源码
         */
        @Bean
        public RedisSessionDAO redisSessionDAO() {
            RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
            redisSessionDAO.setRedisManager(redisManager());
            return redisSessionDAO;
        }
    }

小结

是不是很爽,以后重启应用再也不用担心用户投诉了?

阅读全文