当前位置:首页 > 问答 > 正文

用Shiro怎么能不靠Redis强度验证,想去掉Redis的那些限制和校验

在Apache Shiro框架的实际应用中,很多开发者会遇到一个问题:官方文档或常见的社区实践常常强烈推荐甚至默认使用Redis作为会话(Session)和缓存(如认证缓存、授权缓存)的存储后端,这么做的初衷是为了在集群环境下实现多应用实例间的状态共享,保证用户登录状态的高可用和一致性,这确实引入了一些“限制和校验”,

  1. 对Redis服务器的强依赖:应用启动和运行时必须保证Redis可用,否则整个认证授权功能可能瘫痪,增加了运维复杂度和单点故障风险。
  2. 网络开销与性能瓶颈:每次会话验证、权限检查都需要与Redis进行网络通信,虽然Redis本身很快,但网络延迟在高压下可能成为瓶颈。
  3. 序列化问题:存储在Redis中的对象需要序列化和反序列化,可能带来兼容性问题和性能损耗。
  4. 复杂性:需要配置Redis连接池、序列化方式等,对于小型项目或非集群应用来说显得过于沉重。

如何去掉Redis,回归到更简单、更轻量的方式呢?

核心思路是:摒弃默认的、依赖外部存储的SessionDAOCacheManager,转而使用Shiro内置的、基于内存的实现。

下面分步骤说明如何实现:

用Shiro怎么能不靠Redis强度验证,想去掉Redis的那些限制和校验

理解Shiro的核心组件与替换点

根据Shiro官方文档的架构说明,其安全性操作主要依赖于几个核心组件,我们要“去掉Redis”,主要替换的是其中负责数据持久化的两个部分:

  • SessionDAO:负责Session的创建、读取、更新和删除(CRUD)操作,默认的EnterpriseCacheSessionDAO会与配置的CacheManager(如RedisCacheManager)交互,将Session存储到Redis。
  • CacheManager:负责为Shiro的其他组件(如Realm)提供缓存实现,例如缓存认证信息和授权信息,以避免频繁查询数据库,使用Redis时,通常会配置RedisCacheManager

我们的目标就是用纯内存的实现替换掉这两个组件。

具体配置步骤(以经典的Shiro INI配置或Spring Boot配置为例)

使用内存存储Session(替换SessionDAO)

Shiro自带了一个非常简单的内存SessionDAO实现,叫做MemorySessionDAO,它会将Session存储在应用所在JVM的内存中。

用Shiro怎么能不靠Redis强度验证,想去掉Redis的那些限制和校验

  • 在shiro.ini中的配置示例:

    [main]
    # 配置Session管理器,并设置全局会话超时时间(毫秒)
    sessionManager = org.apache.shiro.web.session.mgt.DefaultWebSessionManager
    sessionManager.globalSessionTimeout = 1800000 # 30分钟
    # 关键步骤:创建并设置MemorySessionDAO
    sessionDAO = org.apache.shiro.session.mgt.eis.MemorySessionDAO
    sessionManager.sessionDAO = $sessionDAO
    # 将配置好的sessionManager设置给SecurityManager
    securityManager.sessionManager = $sessionManager
  • 在Spring Boot的Java Config中的配置示例:

    @Bean
    public SessionManager sessionManager() {
        DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
        sessionManager.setGlobalSessionTimeout(1800000); // 30分钟
        // 关键步骤:设置MemorySessionDAO
        sessionManager.setSessionDAO(new MemorySessionDAO());
        return sessionManager;
    }
    @Bean
    public DefaultWebSessionManager defaultWebSessionManager(SessionManager sessionManager) {
        // 确保SecurityManager使用我们自定义的SessionManager
        DefaultWebSessionManager manager = new DefaultWebSessionManager();
        manager.setSessionManager(sessionManager);
        return manager;
    }
    // 注意:需要将这个sessionManager Bean注入到SecurityManager中

使用内存缓存(替换CacheManager)

对于缓存,Shiro提供了MemoryConstrainedCacheManager,这是一个简单的内存缓存管理器,它不依赖于任何外部存储,完全在JVM堆内存中管理缓存。

用Shiro怎么能不靠Redis强度验证,想去掉Redis的那些限制和校验

  • 在shiro.ini中的配置示例:

    [main]
    # 关键步骤:配置内存缓存管理器
    cacheManager = org.apache.shiro.cache.MemoryConstrainedCacheManager
    # 将cacheManager设置给SecurityManager
    securityManager.cacheManager = $cacheManager
    # 也需要为你自定义的Realm启用缓存
    myRealm = com.yourcompany.shiro.MyCustomRealm
    myRealm.cachingEnabled = true
    myRealm.authenticationCachingEnabled = true
    myRealm.authorizationCachingEnabled = true
  • 在Spring Boot的Java Config中的配置示例:

    @Bean
    public CacheManager cacheManager() {
        // 返回一个纯内存的缓存管理器
        return new MemoryConstrainedCacheManager();
    }
    @Bean
    public DefaultWebSecurityManager securityManager(MyCustomRealm myRealm, CacheManager cacheManager) {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(myRealm);
        // 关键步骤:设置内存CacheManager
        securityManager.setCacheManager(cacheManager);
        return securityManager;
    }

去掉Redis后的影响与注意事项

成功进行上述配置后,你的Shiro应用将不再需要连接Redis,Session和权限信息都会存储在单个JVM实例的内存中,但这带来了新的特性和限制,你必须清楚:

  1. 会话失效:应用重启或停止后,内存中的所有Session都会丢失,所有用户需要重新登录。
  2. 无法集群化:这是最重要的限制,因为Session存储在单个服务器的内存里,如果你有多个应用实例(比如通过负载均衡器分发请求),用户第一次请求打到服务器A并登录,Session存在A上;第二次请求被负载均衡器发到服务器B,服务器B的内存中没有这个Session,会认为用户未登录。这种方案绝对不适用于多实例的集群环境。
  3. 内存溢出风险:如果应用用户量非常大且在线时间很长,大量的Session对象可能会占用可观的堆内存,有引发OutOfMemoryError的风险,需要合理设置globalSessionTimeout,确保Session能及时过期被垃圾回收。
  4. 缓存一致性:内存缓存同样存在一致性问题,如果修改了用户的权限,需要等到该用户对应的缓存项过期(或者手动清空缓存)后,新的权限才会生效。

如果你正在开发的是一个单实例部署的中小型应用,比如内部管理系统、个人项目,或者对短暂的服务重启可以接受,那么去掉Redis,回归到Shiro内置的内存存储,是一个非常明智的选择,它能极大地简化架构,降低运维成本,并可能因为减少了网络IO而提升性能。

具体做法就是如上述所示,将SessionDAO的实现指定为MemorySessionDAO,将CacheManager的实现指定为MemoryConstrainedCacheManager,但务必牢记其带来的单点问题和内存限制,确保它符合你的应用场景。