首页 / 资讯中心 / 文章详情

SpringBoot整合Shiro+Redis实现用户认证授权与分布式会话方案

SpringBoot整合Shiro+Redis实现用户认证授权与分布式会话方案 ★ FEATURED ARTICLE
SpringBootShiroRedis 整合实现用户认证授权这套组合在中小型项目里实在太常见了。我自己接过好几个这类需求真要说起来踩过的坑不比写过的代码少。如果你正准备给项目加一套登录鉴权又不想直接上Spring Security那一大坨配置那Shiro配Redis确实是个非常务实的选择。这篇文章就把我实际整合过程中的思路、配置、代码和问题排查全部整理出来照着做可以少走很多弯路。1. 整体设计与思路拆解1.1 为什么选 Shiro 而不是 Spring Security很多刚开始做认证授权的同学会纠结这个问题。Spring Security背靠Spring家族功能确实强大社区资料也多但它的学习曲线偏陡峭过滤链的配置逻辑也比较绕新手往往花了两三天连登录流程还没跑通。Shiro的设计哲学则相反它把认证、授权、会话管理、加密这几个核心能力全部抽象成简单的API概念少、上手快一个简单的登录校验只需要理解Subject、SecurityManager、Realm这三个核心组件就能跑起来。从实际项目角度讲如果你的系统是标准的单体应用用户量在百万以下对安全框架的要求主要是“能快速实现登录、记住我、角色权限控制”那Shiro是完全够用的。它自带SessionManager结合Redis之后还能解决分布式场景下的会话共享问题已经覆盖了绝大多数业务系统的需求。我见过不少公司内部的管理后台、运营系统、中小型电商后端用的都是这套方案。当然Shiro也不是没有缺点。相比Spring Security它的过滤链自定义能力弱一些文档也不够细致遇到一些偏门问题往往要靠翻源码解决。而且Shiro的社区活跃度近年有所下降新功能迭代比较慢。选型的时候你要评估自己的团队情况如果大家都没用过Shiro但熟悉Spring生态那直接上Spring Security可能反而更顺如果项目工期紧、需求简单或者团队成员已经有Shiro经验那Shiro这套组合性价比极高。1.2 引入 Redis 解决了什么问题单机部署的时候Shiro自带的Session管理完全够用Session数据存在JVM内存里登录状态直接保存在服务器本地。可一旦系统需要多节点部署或者服务重启、发布问题就来了用户在A节点登录下次请求被负载均衡转发到B节点B节点的内存里没有这个Session用户就被判定为未登录体验非常割裂。传统的解决方案是Session黏滞让同一个用户的请求始终转发到同一台机器但这只能算规避节点故障或重启时问题依然存在。引入Redis之后Session数据被集中存储在外部缓存中所有节点共享同一份会话数据。用户无论请求到哪个节点都能从Redis正确读取登录状态。同时Redis的高读写性能让Session的存取几乎不影响接口响应时间。除了存SessionRedis还承担了权限缓存的角色。Shiro默认每次请求都会执行Realm的授权方法如果你的授权逻辑里有数据库查询在高并发场景下这个开销会被放大。把角色和权限列表缓存到Redis后同一用户的权限校验直接命中缓存大幅减少了数据库的压力。这套设计在真正的生产环境里效果很明显我有一次压测加了Redis缓存之后QPS提升了将近30%。1.3 整体架构与数据流转过程这套方案的核心数据流转可以简单概括为请求进来Shiro过滤链判断是否有登录凭证有则从Redis中取出Session恢复用户的登录状态需要鉴权时从Redis缓存取出权限信息进行校验登录成功后用户信息和Session写入Redis退出登录时删除Redis中的会话数据。具体到一次登录请求的时序用户提交用户名密码Shiro把请求包装成一个UsernamePasswordToken交给SecurityManagerSecurityManager委托给配置好的Realm去做认证——Realm里我们写的是先查数据库用户信息再用密码加密规则比对密码认证成功后Shiro根据配置生成Session并存入Redis同时用户主体信息也被缓存起来。后续每次请求经过过滤链时Shiro的SessionDAO都会先去Redis里查这个Session是否还存在从而判断用户是否有效登录。这套流程把“认证”“会话”“缓存”三个环节通过Redis串起来结构清晰每个环节都可以独立做扩展。比如后续要加“记住我”功能只要调整Cookie和Session的有效期逻辑要加多终端登录限制也只需在Session管理上做文章。2. 核心细节解析与实操要点2.1 Shiro 三大核心组件的工作机制Shiro的工作模型围绕Subject、SecurityManager、Realm展开。用生活化的类比来解释这三者的关系Subject是“当前操作的人”SecurityManager是“门卫”Realm是“员工花名册”。Subject代表当前用户封装了用户的所有安全操作。在代码里你可以随时通过SecurityUtils.getSubject()拿到它然后用subject.login(token)做登录用subject.isAuthenticated()判断是否已登录用subject.hasRole(admin)判断角色。它本质上是Shiro与当前线程绑定的一个上下文对象简化了安全操作的调用方式。SecurityManager整个Shiro的心脏负责协调认证、授权、会话管理等所有内部组件。它自己不干活而是把任务分发给其他组件执行。在SpringBoot整合时我们需要在配置类里创建一个SecurityManager的Bean并把它绑定到Shiro的过滤链上。Realm数据访问层的桥接器Shiro通过它获取用户的认证信息和授权信息。默认的Realm什么都不做我们必须写一个自定义Realm继承AuthorizingRealm重写doGetAuthenticationInfo认证时用和doGetAuthorizationInfo授权时用这两个方法。这是整个整合方案里最核心的代码部分。理解这三个组件的职责边界排起错来会清晰很多。比如登录失败问题大概率出在Realm里的认证逻辑权限校验不通过就要检查doGetAuthorizationInfo里返回的权限集合是否正确。2.2 过滤链配置anon、authc、roles、perms 的含义Shiro的过滤链是通过配置URL和对应的过滤器来实现访问控制的这也是它最让人头疼但又最灵活的部分。常用的几个过滤器要搞清楚anon匿名访问不需要登录就可以访问。通常用来放行登录接口、注册接口、静态资源、验证码接口。authc必须登录后才能访问。配置了authc的路径如果用户未登录会被重定向到登录页面或返回未授权提示。roles必须拥有指定角色才能访问。它需要参数比如roles[admin]表示只有admin角色可以访问。perms必须拥有指定权限才能访问同样需要参数比如perms[user:add]。配置的顺序非常重要。Shiro的过滤链是“第一次匹配优先”的也就是说一条请求在前面匹配到了anon就会立刻放行后面的authc根本不会生效。我见过有同学把/**authc配置在前面结果所有请求都要登录后面配置的放行规则完全失效。正确的做法是放行规则写前面兜底的拦截规则写最后具体化的路径写前面通配的路径写后面。2.3 Redis 在 Shiro 整合中的两种核心用途Redis这套方案里扮演两个完全不同的角色很多人在配置时容易混淆导致出现一些很隐蔽的问题。第一个用途是作为Session的存储介质。Shiro把原本存在JVM的Session搬到Redis里需要我们自己实现一个RedisSessionDAO继承AbstractSessionDAO重写doCreate、doRead、doUpdate、doDelete这几个方法。Shiro每次创建、读取、更新、删除Session时都会调用这个DAO。写入Redis的key我们通常设计成shiro_session_xxx这样的格式value是序列化后的Session对象。第二个用途是权限数据的缓存。Shiro的Realm里有一个AuthorizationCache默认内存实现。我们把它替换成RedisCache实现后用户权限信息会以shiro_cache_xxx的key形式存入Redis缓存有效期可以单独设置。两种数据虽然都放在Redis里但生命周期和用途不一样建议在Redis的key前缀上做明确区分避免相互干扰。2.4 序列化方案Java 序列化 vs JSON 序列化把Session对象和权限对象存入Redis之前必须经过序列化。这里有个很多人踩过的坑RedisTemplate默认的序列化器是JdkSerializationRedisSerializer它要求存储的对象必须实现Serializable接口。如果你的用户实体类没实现Serializable运行时就会抛序列化异常。用Java原生序列化虽然简单但存储的内容可读性极差全是乱码和类路径信息占用空间也大。更推荐的做法是用FastJson或Jackson做序列化这样Redis里看到的是清晰的JSON结构排查问题时一眼就能看出存储的内容。不过要注意用JSON序列化时从Redis读出来的对象是JSON反序列化后的副本不是原来的对象引用在极端情况下可能存在数据一致性问题。考虑到Session和权限信息通常是只读使用这个权衡是可以接受的。我在生产环境里用的是改造过的RedisSessionDAO内部统一走JSON序列化这样Redis里的数据键值对一目了然出了问题的定位效率高很多。3. 实操过程与核心环节实现3.1 Maven 依赖引入首先在pom.xml里引入必要的依赖。我用的版本组合是经过实际验证的可以直接抄dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.apache.shiro/groupId artifactIdshiro-spring-boot-starter/artifactId version1.9.1/version /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-data-redis/artifactId /dependency dependency groupIdorg.apache.commons/groupId artifactIdcommons-pool2/artifactId /dependency dependency groupIdcom.alibaba/groupId artifactIdfastjson/artifactId version2.0.26/version /dependency需要注意shiro-spring-boot-starter这个依赖在不同版本下的兼容性差异挺明显。如果你用的是Spring Boot 2.x建议Shiro版本选1.8.x以上1.9.x更稳如果你是Spring Boot 3.x那要特别注意Shiro对Jakarta命名空间的支持问题最好找专门的适配版本。3.2 自定义Realm实现认证与授权自定义Realm是整套方案的核心代码。我习惯创建一个名为UserRealm的类继承AuthorizingRealm里面实现认证和授权两个方法。先看认证方法Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { UsernamePasswordToken usernamePasswordToken (UsernamePasswordToken) token; String username usernamePasswordToken.getUsername(); // 根据用户名查询数据库 User user userService.findByUsername(username); if (user null) { throw new UnknownAccountException(用户不存在); } if (user.getStatus() 0) { throw new LockedAccountException(账号已被锁定); } // 返回认证信息密码的比对由CredentialsMatcher完成 return new SimpleAuthenticationInfo(user, user.getPassword(), ByteSource.Util.bytes(user.getSalt()), getName()); }注意这里有个关键设计我没有在Realm里手动比对密码而是把数据库查出的加密密码返回给Shiro由配置的CredentialsMatcher去自动比对。这样做的好处是密码校验逻辑集中管理后续如果更换加密算法只需要替换Matcher的实现不影响Realm的其它逻辑。再看授权方法Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { User user (User) principals.getPrimaryPrincipal(); SimpleAuthorizationInfo info new SimpleAuthorizationInfo(); // 查询用户角色 SetString roles roleService.findRolesByUserId(user.getId()); // 查询用户权限 SetString perms permissionService.findPermsByUserId(user.getId()); info.setRoles(roles); info.setStringPermissions(perms); return info; }这个方法的调用时机很特殊不是每次请求都会执行而是首次访问需要角色或权限校验的接口时才触发而且结果会被缓存。所以把权限查询逻辑写在这里非常合适配合Redis缓存可以大幅减少数据库查询。3.3 Shiro 核心配置类编写配置类负责把Realm、SecurityManager、SessionDAO、过滤链全部串起来。我直接给出一个可以运行的配置骨架Configuration public class ShiroConfig { Bean public UserRealm userRealm() { return new UserRealm(); } Bean public SecurityManager securityManager(UserRealm userRealm, RedisSessionDAO sessionDAO) { DefaultWebSecurityManager manager new DefaultWebSecurityManager(); manager.setRealm(userRealm); manager.setSessionManager(sessionManager(sessionDAO)); // 开启Redis缓存权限数据 manager.setCacheManager(redisCacheManager()); return manager; } Bean public SessionManager sessionManager(RedisSessionDAO sessionDAO) { DefaultWebSessionManager sessionManager new DefaultWebSessionManager(); sessionManager.setSessionDAO(sessionDAO); // Session有效期单位毫秒这里设置为2小时 sessionManager.setGlobalSessionTimeout(7200000); return sessionManager; } Bean public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) { ShiroFilterFactoryBean factoryBean new ShiroFilterFactoryBean(); factoryBean.setSecurityManager(securityManager); // 登录接口 factoryBean.setLoginUrl(/api/login); // 未授权跳转地址 factoryBean.setUnauthorizedUrl(/api/unauthorized); // 过滤链配置 MapString, String filterChainDefinitionMap new LinkedHashMap(); // 放行登录、注册、验证码接口 filterChainDefinitionMap.put(/api/login, anon); filterChainDefinitionMap.put(/api/register, anon); filterChainDefinitionMap.put(/api/captcha, anon); // 静态资源放行 filterChainDefinitionMap.put(/static/**, anon); // 用户相关接口需要登录 filterChainDefinitionMap.put(/api/user/**, authc); // 管理员接口需要admin角色 filterChainDefinitionMap.put(/api/admin/**, roles[admin]); // 增加权限示例 filterChainDefinitionMap.put(/api/user/add, perms[user:add]); // 其余接口需要登录 filterChainDefinitionMap.put(/**, authc); factoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); return factoryBean; } }有一处细节要提醒filterChainDefinitionMap的类型一定是LinkedHashMap保证有序性。如果用了HashMap过滤链的匹配顺序会变得不可预测表面上看着配置没问题实际跑起来却会出现各种奇怪的访问异常。另外CacheManager的Redis实现需要我们自己写一个类继承AbstractCacheManager创建RedisCache。这个类的代码量不大核心是把Cache接口的get、put、remove方法映射到Redis的读写删除操作上。3.4 RedisSessionDAO 与 RedisCache 实现这部分是整合过程中的硬骨头直接给出可用的核心实现。RedisSessionDAO继承AbstractSessionDAO关键是序列化方式。我选择用FastJson处理先把Session转成JSON字符串存入Redis读取时再反序列化。还有一个细节是key的过期时间设置——Session本身有超时时间Redis的key也要设置一个对应的TTL防止Session永久残留在Redis里变成脏数据。Component public class RedisSessionDAO extends AbstractSessionDAO { Autowired private RedisTemplateString, String redisTemplate; private static final String SESSION_KEY_PREFIX shiro_session:; private static final long SESSION_TIMEOUT 7200L; Override protected Serializable doCreate(Session session) { Serializable sessionId generateSessionId(session); assignSessionId(session, sessionId); saveSession(session); return sessionId; } Override protected Session doReadSession(Serializable sessionId) { String key SESSION_KEY_PREFIX sessionId; String sessionStr redisTemplate.opsForValue().get(key); if (sessionStr null) { return null; } return JSON.parseObject(sessionStr, SimpleSession.class); } private void saveSession(Session session) { String key SESSION_KEY_PREFIX session.getId(); String sessionStr JSON.toJSONString(session); redisTemplate.opsForValue().set(key, sessionStr, SESSION_TIMEOUT, TimeUnit.SECONDS); } Override public void update(Session session) throws UnknownSessionException { saveSession(session); } Override public void delete(Session session) { String key SESSION_KEY_PREFIX session.getId(); redisTemplate.delete(key); } }RedisCache的实现稍微复杂主要是把缓存key映射到Redis key并统一管理过期时间。这里我直接用一个StringRedisTemplate操作避免序列化器不一致带来的问题。3.5 登录接口与退出登录的实现登录接口的逻辑很直观核心就两行代码创建token调用subject.login。PostMapping(/login) public Result login(RequestBody LoginRequest loginRequest) { Subject subject SecurityUtils.getSubject(); UsernamePasswordToken token new UsernamePasswordToken( loginRequest.getUsername(), loginRequest.getPassword() ); try { subject.login(token); // 登录成功后可以从subject里拿到用户信息 User user (User) subject.getPrincipal(); return Result.success(登录成功, user); } catch (UnknownAccountException e) { return Result.error(用户不存在); } catch (IncorrectCredentialsException e) { return Result.error(密码错误); } catch (LockedAccountException e) { return Result.error(账号被锁定); } catch (AuthenticationException e) { return Result.error(认证失败); } }退出登录更简单一行代码GetMapping(/logout) public Result logout() { Subject subject SecurityUtils.getSubject(); subject.logout(); return Result.success(退出成功); }调用subject.logout()之后Shiro会清掉当前Subject的身份信息并通过我们实现的RedisSessionDAO删除对应的Session数据。从Redis里检查一下key对应的数据是否被清掉就能确认退出逻辑是否生效。3.6 权限注解的使用方式除了过滤链配置Shiro还支持在接口方法上使用注解来控制权限粒度更细用起来也比URL配置更清晰。RestController RequestMapping(/api/user) public class UserController { // 需要登录才能访问 RequiresAuthentication GetMapping(/info) public Result getUserInfo() { User user (User) SecurityUtils.getSubject().getPrincipal(); return Result.success(user); } // 需要admin角色 RequiresRoles(admin) PostMapping(/delete) public Result deleteUser(RequestParam Long userId) { userService.deleteById(userId); return Result.success(删除成功); } // 需要user:add权限 RequiresPermissions(user:add) PostMapping(/add) public Result addUser(RequestBody User user) { userService.add(user); return Result.success(新增成功); } }使用注解方式有个先决条件必须在配置类里开启Shiro注解支持并且确保Shiro的拦截器配置了可代理的目标对象。Spring Boot整合Shiro后默认会通过AOP方式支持注解但需要额外加入一个Bean配置让Spring的切面能够拦截到这些注解。3.7 密码加密方案的选型与实现密码安全是整个认证系统里绝对不能含糊的部分。早期很多系统直接用MD5存密码这在如今的安全环境下是远远不够的。我推荐使用MD5加随机盐的方式甚至直接用BCrypt成本低效果好。以MD5加盐为例注册时生成一个随机salt密码加密的逻辑如下// 生成随机盐 String salt RandomStringUtils.randomAlphanumeric(8); // 加密迭代次数可以根据需要调整 String encryptedPassword new Md5Hash(rawPassword, salt, 2).toHex();数据库里存储的是加密后的密码和盐。登录时Shiro会通过CredentialsMatcher进行密码比对我们要在ShiroConfig的UserRealm里设置对应的MatcherBean public UserRealm userRealm() { UserRealm realm new UserRealm(); // 使用带盐的MD5迭代2次 HashedCredentialsMatcher matcher new HashedCredentialsMatcher(); matcher.setHashAlgorithmName(MD5); matcher.setHashIterations(2); realm.setCredentialsMatcher(matcher); return realm; }这里面有一处非常容易踩坑HashedCredentialsMatcher默认认为数据库里存储的密码是Hex字符串如果当时加密用的是Base64或者其他编码比对就会一直失败。所以注册时的编码方式和Matcher的预期必须严格一致改起来很费劲。如果要从根本上提高安全性可以直接用BCrypt。BCrypt的哈希自带盐不需要额外字段存salt校验时直接用BCrypt.matches(rawPassword, encryptedPassword)即可。Shiro也有对应的BCryptCredentialsMatcher可以配置。代价是BCrypt的计算开销大一些但对登录这种低频操作来说完全不是问题。4. 整合过程中的常见问题与排查实录4.1 Session 失效或每次请求都重新登录这个问题的典型表现是前端调用登录接口成功了但下一次请求就被过滤链拦截判定为未登录。排查思路先检查Redis里面有没有写入Session数据。如果Redis里根本没有shiro_session开头的key说明SessionDAO的写入逻辑没有被执行或执行失败。常见原因有几种RedisTemplate的序列化配置有问题Session对象写入Redis时报序列化异常被Shiro内部吞掉了。SessionDAO没有正确注入到SessionManagerShiro还是用的默认内存Session实现。检查配置里sessionManager的setSessionDAO是否真的生效。Cookie的名称或路径不对浏览器没有保存Shiro生成的SessionID。用浏览器的开发者工具看一下请求Cookie的情况。如果是前端部署在另外一个域名的场景还要考虑Cookie跨域的问题。Shiro默认的Cookie只在同域名下有效跨域时需要配置Cookie的domain属性或者改成前后端分离模式前端手动携带SessionID。4.2 密码一直匹配不上的问题密码比对失败的排查核心是注册时的加密逻辑和登录时的校验逻辑必须完全一致。最常见的坑是加盐方式不对。注册时用new Md5Hash(password, salt, 2)但登录时Matcher拿到的ByteSource如果不一致结果必然不一样。在Realm里返回AuthenticationInfo时盐的构造方式必须和注册时保持一致。比如注册时用的是随机字符串salt那么Realm返回时也要用ByteSource.Util.bytes(user.getSalt())。另外一个坑是迭代次数。我遇到过一个情况数据库表里有历史数据老数据的迭代次数是1次新数据是2次结果老用户全部登录失败。解决思路是渐进式迁移登录成功后检测当前用户的加密参数如果是老版本在登录成功的同时用新参数重新加密一遍密码并更新数据库。4.3 Redis 缓存导致权限变更不生效这是最容易让人困惑的问题之一。管理员在后台修改了某个用户的角色或者权限但该用户下次访问接口时还是旧权限甚至需要重启服务才生效。原因是权限信息被缓存到了Redis而且是按用户维度缓存的。只要不主动清理Redis中的缓存会一直沿用旧数据。解决思路有三种在权限变更的接口里主动清理对应用户权限的缓存。需要拿到权限缓存Manager调用clearCache方法。给权限缓存设置较短的过期时间比如30分钟牺牲一些性能换取准实时性。在权限变更后强制该用户重新登录通过登录流程重建权限缓存。实际项目里我比较推荐第一种思路在业务代码里预留缓存清理逻辑。虽然要多写一点代码但权限变更的响应是几乎实时的运营和管理同事的体验会好很多。4.4 RedisSessionDAO 反序列化报错这种情况在Session数据是旧版本写入、代码升级后重新读取时容易发生。比如之前用Java原生序列化写入的Session代码改成JSON序列化后旧的Redis数据还在新代码尝试用JSON解析直接抛异常。解决思路是平滑升级改代码之前先把Redis里的Session数据一次性清掉。发布时设置session的有效期短一些让旧Session尽快过期。更好的方案是代码里做兼容处理读数据失败时catch异常并返回null让Shiro对这个Session重新走创建流程。4.5 权限注解不生效的排查在Spring Boot项目里启用Shiro注解需要确认两个方面一是自定义Realm的Bean必须被Spring管理二是Shiro的注解扫描需要开启。常见的配置是引入一个AuthorizationAttributeSourceAdvisor的Bean它的作用是把Shiro的注解和Spring AOP关联起来。如果配置了还是不生效优先检查是不是类没有被Spring扫描到或者接口方法用了private修饰。Shiro的注解是通过AOP代理实现的private方法是不会走代理的所以注解必须放在public方法上。5. 这个方案的扩展与优化方向5.1 基于 Redis 的限流与防刷既然Redis已经集成进来了顺手加一个接口防刷的机制是很自然的扩展。在登录接口上做限制比如同一个IP一分钟内最多尝试5次登录超过则直接拒绝。实现也不复杂用Redis的INCR和EXPIRE命令就能搞定。String key login:limit: ipAddress; Long count redisTemplate.opsForValue().increment(key); if (count ! null count 1) { redisTemplate.expire(key, 1, TimeUnit.MINUTES); } if (count ! null count 5) { throw new RuntimeException(登录尝试次数过多请稍后再试); }这个逻辑放在登录接口的最前面拦截效果非常直接。它也是目前很多互联网产品在用的登录防暴力破解手段。5.2 记住我功能与退出策略Shiro自带RememberMe功能开启后用户关闭浏览器SessionId仍然保存在本地Cookie中下次访问自动恢复登录状态。Redis的引入让RememberMe的实现更有优势因为Session数据在Redis里即使用户换了节点只要Cookie中的SessionId还在依然能正确恢复登录状态。实现RememberMe需要在登录时设置token的rememberMe属性为true并在SecurityManager中配置一个rememberMeManager把Cookie的有效期调长。要注意的是RememberMe状态下subject.isAuthenticated()为false但subject.isRemembered()为true。如果你的代码里用isAuthenticated()做判断未真正登录就拒绝访问的接口需要特别注意这个细节。5.3 集群环境下的多节点会话一致性这套方案在集群环境下的表现是比较理想的。因为所有Session数据都存在Redis每个节点读到的都是同一份数据天然解决了会话一致性的问题。唯一要注意的是多个节点共享同一个Redis时序列化配置必须一致否则A节点写入的数据会被B节点反序列化失败。另一个要考虑的点是Redis的持久化策略要对齐公司的运维规范。如果Redis是纯缓存模式、允许数据丢失那么Redis重启后所有用户会集体掉线必须重新登录。如果希望高可用可以开启Redis的持久化或者用主从模式哨兵。这块属于运维范畴但在方案设计阶段就应该想清楚免得后面上线时出大问题。6. 实操心得总结这套SpringBootShiroRedis的方案我从第一次接触到今天前前后后在好几个项目里落地过。回过头来看最核心的收获是技术选型一定要结合团队的实际能力和项目的真实规模不要为了炫技去选一个用不上的框架。Shiro在中小型项目里依然是非常能打的方案搭配Redis之后的分布式会话能力完全够用。而且它最大的优势在于概念直观新同学上手速度快出现问题后定位路径非常短。相比Spring Security同样的功能在Shiro里的实现代码量大概能少30%到40%这在项目周期紧的时候是实打实的优势。有一点我觉得值得养成习惯正式上线前一定要对认证授权模块做一次专门的测试重点覆盖Session过期、权限变更、多节点切换、并发登录这几个场景。这些边角问题在单机开发和测试环境里很难暴露但一旦上了生产用户报障的往往就是这类问题。最后再分享一个排查小技巧如果你在整合过程中碰到了诡异的Session、权限问题第一步永远是去Redis里看看数据是什么样的。数据在问题就在读数据的环节数据不在问题就在写数据的环节。这比反复看代码猜原因高效得多。
阅读完成 · 觉得有帮助?
咨询建站