SpringBoot整合Shiro权限框架实战:从认证授权到常见问题排查

发布时间:2026/10/14 1:15:39

SpringBoot整合Shiro权限框架实战:从认证授权到常见问题排查 1. 为什么在SpringBoot里折腾Shiro1.1 从一次权限失控的事故说起前两年我接手过一个后台管理系统功能不复杂就是给内部运营团队用的数据看板。当时项目赶得急登录校验直接用了个拦截器加Session的方案权限控制基本靠前端按钮隐藏。上线第三周就出事了——一个离职员工的账号还能登录而且通过直接拼URL的方式访问到了管理员才该看到的财务数据页面。事后复盘问题就出在权限校验只做在了前端后端接口几乎是裸奔状态。那次之后我开始认真找一套能快速集成、配置直观、又足够灵活的权限框架。试过自己手写过滤器链也看过Spring Security最后落到Shiro上。原因很实在Spring Security功能确实强大但学习曲线陡配置复杂对于中小型项目来说有点杀鸡用牛刀的感觉。Shiro的API设计非常直白Subject、SecurityManager、Realm三个核心概念搞清楚基本就能跑起来而且和SpringBoot的整合成本极低。这篇内容就是把我从零搭建Shiro的完整过程拆开来讲包括依赖选型、配置思路、认证授权流程、常见坑点。适合正在做后台系统、需要快速加上登录和权限控制的开发者也适合之前只用过拦截器方案、想升级到专业权限框架的朋友。1.2 Shiro到底解决了什么问题用一句话概括Shiro帮你把谁在操作和他能操作什么这两件事从业务代码里剥离出来。没有权限框架的时候你可能会在每个Controller方法里写一堆if-else判断当前用户角色代码又臭又长改一个权限规则要翻遍整个项目。Shiro的做法是通过过滤器链在请求进入Controller之前就完成校验业务代码只需要关注业务逻辑本身。它还提供了Session管理、密码加密、记住我等功能这些都是后台系统的刚需。和Spring Security对比Shiro的优势在于概念少、上手快。Spring Security的过滤器链和配置体系对新手来说确实不太友好而Shiro的核心就三个东西Subject代表当前用户SecurityManager是幕后管理者Realm负责从数据源拿用户和权限信息。理解这三个剩下的就是配置的事。注意Shiro本身不依赖Spring它是一个独立的安全框架和SpringBoot整合只是让它更容易融入现有项目。这一点和Spring Security有本质区别。2. 环境准备与依赖选型2.1 版本选择的门道搭Shiro第一步就是选版本。这里有个很多人踩过的坑Shiro的版本和SpringBoot的版本之间存在兼容性问题。我实测下来比较稳的组合是SpringBoot 2.7.x配Shiro 1.11.0以上或者SpringBoot 3.x配Shiro 2.0.x。如果你用的是SpringBoot 3一定要注意Shiro 1.x是不支持的因为SpringBoot 3把javax.servlet换成了jakarta.servletShiro 1.x还在用旧的包名直接整合会报ClassNotFoundException。我自己的项目用的是SpringBoot 2.7.18加Shiro 1.13.0这个组合经过多个项目验证稳定性没问题。如果你是新项目建议直接上SpringBoot 3加Shiro 2.0虽然Shiro 2.0的资料相对少一些但长远来看是趋势。依赖方面核心就两个dependency groupIdorg.apache.shiro/groupId artifactIdshiro-spring-boot-web-starter/artifactId version1.13.0/version /dependency用starter的好处是它自动帮你装配了ShiroFilterFactoryBean和SecurityManager省去了大量手动配置。如果你不用starter就得自己写Bean来创建这些对象代码量会多不少。2.2 项目结构规划在动手写代码之前我习惯先把包结构规划好。Shiro相关的类不要散落在各个包里面集中管理后期维护会轻松很多。我的做法是建一个config包放Shiro的配置类一个shiro包放自定义Realm和过滤器一个entity包放用户和角色实体。数据库表设计也得提前想清楚。最基础的是三张表用户表、角色表、权限表再加上两张关联表用户角色关联和角色权限关联。如果你觉得五张表太重也可以简化成用户表里直接存角色字段权限用角色来映射。但我不建议这么做后期扩展会很痛苦。权限控制这件事表结构设计得越规范后面加需求越轻松。3. Shiro核心概念拆解3.1 Subject站在用户视角看一切Subject是Shiro对外暴露的门面。你在代码里调用SecurityUtils.getSubject()拿到的就是当前请求对应的Subject对象。它封装了当前用户的所有信息包括是否已认证、拥有哪些角色、具备哪些权限。打个比方Subject就像是你去公司前台办事时拿到的那张访客卡。卡片上记录了你是谁、你能去哪些楼层、你能进哪些会议室。每次你要去某个区域保安看的就是这张卡。Shiro里的Subject也是这个角色它不关心你背后是怎么登录的、数据从哪来的它只负责回答当前这个人能不能做这件事。Subject的几个核心方法必须记住login()用于登录认证logout()退出isAuthenticated()判断是否已登录hasRole()和isPermitted()用于权限判断。日常开发中你用得最多的就是这几个。3.2 SecurityManager幕后的总调度SecurityManager是Shiro的心脏所有认证、授权、Session管理都要经过它。但在实际开发中你几乎不会直接和它打交道因为Subject已经帮你把大部分操作封装好了。你只需要在配置类里把它创建出来注入Realm就行。可以把它理解成公司安保系统的总控室。Subject是前台那张卡但卡能不能刷开某扇门最终是总控室在背后做判断。总控室需要知道每个员工的权限信息这些信息就是从Realm来的。3.3 Realm连接数据源的桥梁Realm是Shiro和你的数据库之间的桥梁。Shiro本身不知道你的用户存在哪、密码是什么、权限怎么分配这些都需要你通过Realm告诉它。你需要继承AuthorizingRealm类实现两个核心方法doGetAuthenticationInfo()负责认证doGetAuthorizationInfo()负责授权。我见过很多新手在这里犯迷糊把认证和授权混在一起写。记住一个原则认证解决的是你是谁的问题授权解决的是你能干什么的问题。登录的时候走认证访问受保护资源的时候走授权两条线是分开的。4. 完整搭建流程实操4.1 配置文件与基础参数先在application.yml里把Shiro的基础配置写好shiro: enabled: true web: enabled: true loginUrl: /login successUrl: /index unauthorizedUrl: /unauthorizedloginUrl是未登录时跳转的地址unauthorizedUrl是已登录但权限不足时跳转的地址。这两个配置很关键不配的话Shiro会用默认值可能导致跳转行为不符合预期。还有一个容易忽略的点Shiro默认使用自己的Session管理如果你项目里已经用了Spring Session或者Redis做分布式Session需要额外配置SessionManager否则会出现Session不一致的问题。这个坑我在分布式部署的时候踩过本地测试一切正常上了集群就频繁掉登录。4.2 自定义Realm实现认证逻辑下面是我项目中实际使用的Realm实现做了简化处理public class CustomRealm extends AuthorizingRealm { Autowired private UserService userService; Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { String username (String) principals.getPrimaryPrincipal(); SimpleAuthorizationInfo info new SimpleAuthorizationInfo(); SetString roles userService.getRolesByUsername(username); SetString permissions userService.getPermissionsByUsername(username); info.setRoles(roles); info.setStringPermissions(permissions); return info; } Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { UsernamePasswordToken upToken (UsernamePasswordToken) token; String username upToken.getUsername(); User user userService.findByUsername(username); if (user null) { throw new UnknownAccountException(账号不存在); } if (user.getStatus() 0) { throw new LockedAccountException(账号已被锁定); } return new SimpleAuthenticationInfo( username, user.getPassword(), ByteSource.Util.bytes(user.getSalt()), getName() ); } }这里有几个细节值得展开说。第一密码不要明文存储我用的是加盐哈希的方式salt存在用户表里每次认证时传给Shiro做比对。第二认证失败要抛具体的异常类型UnknownAccountException和LockedAccountException会被Shiro捕获并转换成对应的错误信息前端可以根据不同异常给出不同提示。第三doGetAuthorizationInfo里的权限查询要做好缓存不然每次请求都查数据库性能扛不住。4.3 ShiroConfig配置类编写配置类是整个整合的核心我把它拆成几个Bean来写Configuration public class ShiroConfig { Bean public CustomRealm customRealm() { CustomRealm realm new CustomRealm(); realm.setCredentialsMatcher(hashedCredentialsMatcher()); return realm; } Bean public HashedCredentialsMatcher hashedCredentialsMatcher() { HashedCredentialsMatcher matcher new HashedCredentialsMatcher(); matcher.setHashAlgorithmName(MD5); matcher.setHashIterations(1024); return matcher; } Bean public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) { ShiroFilterFactoryBean factoryBean new ShiroFilterFactoryBean(); factoryBean.setSecurityManager(securityManager); factoryBean.setLoginUrl(/login); factoryBean.setUnauthorizedUrl(/unauthorized); MapString, String filterChain new LinkedHashMap(); filterChain.put(/login, anon); filterChain.put(/register, anon); filterChain.put(/static/**, anon); filterChain.put(/admin/**, roles[admin]); filterChain.put(/**, authc); factoryBean.setFilterChainDefinitionMap(filterChain); return factoryBean; } }filterChain的顺序非常重要必须用LinkedHashMap保证有序因为Shiro是按顺序匹配的第一个匹配上的规则就生效。anon表示匿名访问authc表示需要认证roles[admin]表示需要admin角色。我一般把静态资源和登录注册放最前面然后是特定角色的路径最后用/**兜底。注意filterChain.put(/**, authc)这行必须放在最后否则后面的规则永远不会被匹配到。这个坑我见过太多人踩了。4.4 登录接口与密码加密登录接口的写法很直接PostMapping(/login) public Result login(String username, String password) { Subject subject SecurityUtils.getSubject(); UsernamePasswordToken token new UsernamePasswordToken(username, password); try { subject.login(token); return Result.success(登录成功); } catch (UnknownAccountException e) { return Result.error(账号不存在); } catch (IncorrectCredentialsException e) { return Result.error(密码错误); } catch (LockedAccountException e) { return Result.error(账号已锁定); } }密码加密这块我建议在用户注册的时候就做好。用Shiro的SimpleHash工具类public static String encryptPassword(String password, String salt) { return new SimpleHash(MD5, password, ByteSource.Util.bytes(salt), 1024).toHex(); }salt建议用随机生成的字符串每个用户不一样。这样即使两个用户密码相同存到数据库里的哈希值也不同能有效防止彩虹表攻击。哈希迭代次数设1024次是常见做法次数越高越安全但计算越慢1024是一个平衡点。5. 权限控制实战技巧5.1 注解式权限控制除了在filterChain里配置URL级别的权限Shiro还支持注解级别的细粒度控制。在Controller方法上加RequiresPermissions或RequiresRolesRequiresPermissions(user:delete) DeleteMapping(/user/{id}) public Result deleteUser(PathVariable Long id) { userService.deleteById(id); return Result.success(); }要让注解生效必须在ShiroConfig里开启AOP支持加上这两个BeanBean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) { AuthorizationAttributeSourceAdvisor advisor new AuthorizationAttributeSourceAdvisor(); advisor.setSecurityManager(securityManager); return advisor; } Bean public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() { DefaultAdvisorAutoProxyCreator creator new DefaultAdvisorAutoProxyCreator(); creator.setProxyTargetClass(true); return creator; }不加这两个Bean注解就是摆设一点效果都没有。这是新手最容易忽略的一步。5.2 权限缓存优化每次请求都去数据库查权限QPS一上来数据库就扛不住了。我的做法是用Redis做权限缓存在doGetAuthorizationInfo里先查缓存缓存没有再查数据库并写入缓存。缓存的key用用户名value是角色和权限的集合设置一个合理的过期时间比如30分钟。用户权限变更的时候要记得清缓存不然会出现改了权限但不生效的情况。我一般是在角色分配、权限修改的接口里主动删除对应的缓存key。这个逻辑虽然简单但漏掉的话排查起来很费时间因为现象是有时候生效有时候不生效很容易误判成偶发bug。5.3 前后端分离下的Session处理现在很多项目是前后端分离的前端用Vue或React后端只提供接口。这种情况下传统的Session-Cookie模式就不太适用了因为跨域请求默认不带Cookie。解决方案有两种一种是用Token替代Session另一种是配置CORS允许携带Cookie。我倾向于用Token方案具体做法是登录成功后生成一个JWT返回给前端前端每次请求在Header里带上这个Token后端写一个自定义过滤器解析Token并构建Subject。这样Shiro的Session管理就不参与了整个认证流程更加轻量。但要注意用Token方案的话Shiro的logout和Session相关功能需要自己实现不能直接用框架提供的。6. 常见问题与排查实录6.1 登录后仍然跳转登录页这是最高频的问题。现象是明明登录成功了访问其他页面还是被重定向到loginUrl。排查思路如下排查项可能原因解决方法filterChain配置/**规则放在了具体规则前面调整顺序具体规则在前Session共享分布式环境下Session不一致配置RedisSessionManager跨域问题前后端分离Cookie未携带配置CORS或改用Token缓存问题权限缓存未更新清除缓存或缩短过期时间我遇到最多的是filterChain顺序问题。很多人写配置的时候习惯把通配符放前面觉得这样覆盖面广结果导致后面的精细规则全部失效。6.2 密码校验一直失败密码校验失败但确认密码没输错大概率是加密方式不匹配。检查三个地方注册时用的哈希算法和Realm里配置的是否一致salt是否传对了迭代次数是否相同。这三个参数任何一个不一致校验都会失败。还有一种情况是数据库里存的密码是明文但Realm配置了HashedCredentialsMatcher这样Shiro会拿密文去比对明文自然对不上。要么把数据库里的密码改成加密后的要么把CredentialsMatcher换成SimpleCredentialsMatcher。我建议前者安全性更高。6.3 注解权限不生效前面提到过注解不生效九成是因为没有配置AuthorizationAttributeSourceAdvisor和DefaultAdvisorAutoProxyCreator。另外还要确认EnableAspectJAutoProxy是否开启SpringBoot默认是开启的但如果你手动关掉了就要加回来。还有一个隐蔽的问题如果Controller方法是被内部调用的比如A方法调用B方法B方法上的注解不会生效因为AOP代理只对外部调用起作用。这个属于Spring AOP的通用限制不只是Shiro的问题。6.4 退出登录后Session未清除调用subject.logout()之后Shiro会清除Session并重定向到配置的地址。但如果你用的是自定义SessionManager或者Token方案logout可能不会按预期工作。我的做法是在logout接口里手动清除Token或Session不依赖Shiro的默认行为这样更可控。7. 一些实操心得与扩展思路7.1 关于Realm的拆分项目初期我把认证和授权都写在一个Realm里后来用户体系变复杂了出现了多种登录方式账号密码、手机验证码、第三方授权一个Realm就扛不住了。这时候可以拆成多个Realm每个Realm负责一种认证方式通过ModularRealmAuthenticator来协调。Shiro支持配置多个Realm认证时会依次尝试直到有一个成功。7.2 关于权限粒度的把握权限设计不要太细也不要太粗。太细的话一个页面要配十几个权限码维护成本极高太粗的话又起不到控制作用。我的经验是按模块:操作的格式来定义比如user:view、user:edit、user:delete一个模块三到五个权限码就够了。角色和权限的映射关系在后台管理界面里配置不要硬编码在代码里。7.3 关于Shiro的替代方案如果你的项目权限逻辑特别复杂比如需要支持数据级别的权限控制不同用户看到不同的数据行Shiro可能就不太够用了需要考虑更专业的权限系统或者自研。但对于大多数中小型后台系统来说Shiro的URL级别加注解级别控制已经能覆盖90%的场景。我在实际使用中发现Shiro最大的价值不是它提供了多少功能而是它把权限控制这件事标准化了。团队里任何人接手项目看到Shiro的配置就知道权限是怎么控制的不需要去翻业务代码找拦截器。这种标准化带来的维护效率提升比功能本身更重要。最后分享一个小技巧在开发阶段可以把Shiro的日志级别调到DEBUG这样每次请求的认证授权过程都会打印出来排查问题非常方便。上线前记得调回INFO不然日志量会很大。
延伸阅读

更多相关文章

2026/10/14 1:10:39

组合数学计数法则:加法与乘法原理及排列组合实战指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/14 1:10:39

Spring Cloud Config 配置中心实战:从搭建到动态刷新

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/14 2:30:44

量化软件推荐 参数搜索和独立验证三款工具怎样选

量化软件推荐,准备反复修改参数的人可以比较BigQuant、牛股王股票和青柠量化。愿意自己记录表达式及模型改动,可评估BigQuant;希望先限定现有因子与交易模型的少量调整,可检查牛股王股票;需要参数实验室及步进样本外验…

2026/10/14 2:30:44

【单片机课设毕设项目】基于单片机的智能婴儿床心率体温监测与图像远程监护装置设计 基于WIFI的婴儿监护生理环境监测与哄睡联动系统设计(032709)

博主介绍:✌️码农一枚 ,专注于大学生项目实战开发、讲解和毕业🚢文撰写修改等。全栈领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于嵌入式单片机,Java、小程序技术领域和毕业项目实战 ✌️…

2026/10/14 2:30:44

个人量化软件对比 三款工具如何解释少数交易拉高收益

个人量化软件对比,面对一条收益不错、却可能由少数交易贡献的曲线,可以比较米筐研究工具、牛股王股票和掘金量化。需要自行阅读头寸、流水并设计收益归因,可了解米筐RQAlpha Plus;希望从单次收益、盈亏比和历史明细开始复核&#…

2026/10/14 2:30:44

【单片机毕设案例分享】基于STM32的智能婴儿床多参数监测与远程联动控制装置设计 基于物联网的婴儿床生理环境监测与摄像头监护系统设计(032709)

博主介绍:✌️码农一枚 ,专注于大学生项目实战开发、讲解和毕业🚢文撰写修改等。全栈领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于单片机,STM32单片机,51单片机,J…

2026/10/14 2:30:44

机械工程读研值不值?从产业周期到导师选择的全链条决策指南

每年到了保研、考研的时间点,总有一批机械工程专业的本科生在纠结同一个问题:到底要不要读硕士。问这个问题的学生,往往不是成绩最拔尖的那批,也不是完全不想读书的那批,而是处于中间状态的大多数——能找工作&#xf…

2026/10/13 5:19:27

Python调用Gemini Structured Outputs实现工单路由门禁

客服工单最怕的不是模型“答错一句话”,而是它给出一段看起来合理的说明,程序却从中猜错优先级。通俗做法是:要求模型只交 JSON(JavaScript Object Notation,轻量数据格式),再让代码验证它。Gem…

2026/10/13 17:09:18

Spring Boot超市进销存系统毕设实战:从需求拆解到答辩通关

最近带的一个学生项目组里,有A同学跑来问我:选什么毕设题目最稳妥,既能让评审老师觉得工作量够,又不会在答辩时被问到语无伦次。我第一反应就是推荐基于Spring Boot的超市仓库管理系统——也就是超市进销存系统。这个题目乍一看平…

2026/10/14 2:32:55

Flutter StatefulWidget 生命周期核心解析

很多刚开始接触 Flutter 的朋友,在看完一堆“Hello World”和基础组件之后,大概率都会撞上同一堵墙:StatefulWidget 里那堆 initState、build、dispose 方法,到底什么时候被调用?为什么顺序是那样?在里面到…

2026/10/14 0:00:33

DFS算法解决飞机降落调度问题实战

1. 项目背景与核心思路飞机降落问题是一个经典的调度优化场景,也是算法竞赛和面试中的高频题目。这个题目要求我们合理安排多架飞机的降落顺序和时间,确保所有飞机都能安全着陆而不发生冲突。在实际航空管制中,这类问题直接关系到飞行安全和机…

2026/10/14 0:00:33

macOS/iOS高精度计时:mach_absolute_time原理与实战

1. 项目概述:为什么mach_absolute_time是 macOS/iOS 时间测量的“黄金标准”在 macOS 和 iOS 开发中,我见过太多人用CFAbsoluteTimeGetCurrent()或NSDate.timeIntervalSinceReferenceDate做性能打点,结果在高精度场景下被系统时钟跳变、NTP 同…

2026/10/14 0:00:33

SCORM课件演示与测试全解析:从API通信到自动化验证

简介:这是一套遵循SCORM标准的交互式课件演示与测试资源,面向E-Learning课程开发者、LMS平台测试人员及在线教学从业者,用于验证课件在不同学习管理系统中的识别、传递与跟踪能力。压缩包共28个文件,约12.07MB,以swf动…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑