Spring Boot 3.2集成Shiro 2.0.1:从javax到jakarta的迁移实战
2026/9/20 15:08:56 网站建设 项目流程

如果你今天打开项目的pom.xml,看到 Spring Boot 版本停在3.2.x,然后顺手把 Apache Shiro 从1.13.0升到2.0.1,恭喜你,你即将进入一个从javax.servletjakarta.servlet的全新世界。我第一次升这个版本的时候,项目启动第一秒就抛了ClassNotFoundException: javax.servlet.Filter,当时第一反应是“这包我明明导了”,结果发现根本不是包缺失的问题,而是整个 Servlet 命名空间都换了血。那两天我几乎把公司项目的依赖树翻了个底朝天,才把整套过滤器链重新捋顺。

这篇文章就围绕 Spring Boot 3.2 集成 Shiro 2.0.1 这条主线,讲清楚javax.servletjakarta.servlet迁移过程中会遇到哪些问题、为什么会发生这些问题、每一步怎么排查和解决。内容不绕弯子,直接以我实际踩过的坑为主线,适合正在做 Spring Boot 3 迁移、或者打算升级 Shiro 的团队参考。如果你只是把 Spring Boot 从 2.x 升到 3.x,但 Shiro 还停在 1.x,那这篇文章更要看完,因为你大概率会在上线前夜被NoClassDefFoundError折腾到怀疑人生。

1. 根源在名字:一场从 javax.servlet 到 jakarta.servlet 的改名运动

1.1 Java EE 更名史:为什么代码里会同时存在 javax 和 jakarta

很多人第一次看到jakarta.servlet这个包名时会愣住,心想“是不是写错了”。没写错,这是真实存在的命名空间,背后是一段 Java 企业级技术栈的改名历史。

早期 Java 企业级开发基于 Java EE,命名空间统一是javax.*。Oracle 后来把 Java EE 捐给了 Eclipse 基金会,基金会不能继续使用 Java EE 这个商标,于是项目更名为 Jakarta EE。改名不可怕,可怕的是 Jakarta EE 9 开始规定:所有原本在javax.*下的 API 包,必须迁移到jakarta.*下。这就是为什么今天你看到的javax.servlet.http.HttpServletRequest变成了jakarta.servlet.http.HttpServletRequest

这里面有一个关键区别:javax.servletjakarta.servlet是两套完全不同的 API,底层字节码层面就不兼容。哪怕你只是把一个HttpServletRequest类型搞混,都会直接抛ClassCastException或者NoClassDefFoundError

我经常打一个比方:这就好比老小区改了个新名字,身份证、户口本、水电煤气账单全部要跟着换。你本人还是那个人,但所有和外部系统对接的凭证都得换一版。Spring Boot 3.x 就是那个换完户口本的新小区,而 Shiro 1.x 还拿着老身份证,进不了门。

1.2 Spring Boot 3.2 为什么必须切换 Servlet API 版本

Spring Boot 3.0 开始,官方将基线升级到 Jakarta EE 9+,这意味着它内嵌的 Tomcat 版本已经是 10.x,而 Tomcat 10 之后已经彻底移除javax.servlet支持,只提供jakarta.servlet实现。

Spring Boot 3.2 则是在这个基础上进一步稳定的版本,要求 JDK 17+。也就是说,不管你想不想,只要你的项目跑在 Spring Boot 3.2 上,Servlet 容器层面的 API 就已经是jakarta.*了。

这里有个很容易踩的隐性坑:IDE 自动补全。当你输入HttpServletRequest时,IDE 可能会同时提示javax.servlet.http.HttpServletRequestjakarta.servlet.http.HttpServletRequest。如果在 Spring Boot 3.2 项目里误选了javax版本,编译期大概率不会报错,因为项目里可能还有别的依赖间接引用了javax.servlet-api,但运行时就会出现各种诡异问题。这个问题在 Shiro 集成时会尤其明显,因为 Shiro 1.x 的过滤器全部基于javax.servlet编译,放进 Tomcat 10 里根本没法用。

1.3 Shiro 2.0.1:一个不得不出现的大版本

Apache Shiro 1.x 是很成熟的身份认证与授权框架,在国内企业项目里使用率很高,尤其是那些基于 Spring Boot 2.x 搭建的后台管理系统、企业项目进度跟踪系统、工时管理系统,几乎人手一个 Shiro。

问题在于,Shiro 1.x 是从javax.servlet时代走过来的,所有 servlet 相关的过滤器、Filter 链、会话管理等都是基于旧 API。Spring Boot 3 已经切到jakarta了,Shiro 要么跟着切,要么就别想兼容新容器。

于是 Shiro 2.0.0 出现了,这是 Apache Shiro 全面支持 Jakarta EE 9+ 的首个大版本,2.0.1 则是 2.0 系列趋于稳定的维护版本。我们项目用的就是 Shiro 2.0.1,配合 Spring Boot 3.2,整体跑下来已经比较顺了。

另外,Shiro 1.x 时代的安全漏洞问题也很让人头疼。安全扫描工具一跑,shiro-core的 CVE 列表能列出好几条,尤其是在 rememberMe 相关的 Java 序列化问题上。升级到 2.0.1 之后,虽然不能说绝对安全,但至少能清掉一批 1.x 时代遗留的高风险告警。从安全合规角度来说,这也是一个重要的升级理由。

2. 依赖层面的清扫:先让 Maven 告诉我们谁还抱着 javax 不放

2.1 一份能直接在 Spring Boot 3.2 里跑的 Shiro 2.0.1 依赖清单

在准备开始写配置类之前,先把pom.xml理清楚。我推荐的最小依赖清单是这样的:

<properties> <java.version>17</java.version> <spring-boot.version>3.2.5</spring-boot.version> <shiro.version>2.0.1</shiro.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- Shiro 核心 + Spring Boot Web 集成 --> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring-boot-web-starter</artifactId> <version>${shiro.version}</version> </dependency> <!-- Shiro 与 Spring AOP 集成,用于注解权限 --> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>${shiro.version}</version> </dependency> </dependencies>

这里有几个细节值得注意:

第一,shiro-spring-boot-web-starter是 Web 项目专用的 starter,它会把 Shiro 的自动配置带进来。如果你的项目是纯后端 API,建议直接用这个;如果只是普通业务项目,没有 Web 层,那才考虑shiro-spring-boot-starter

第二,shiro-spring提供了 Spring 集成相关的类和 AOP 支持。虽然 starter 内部可能已经传递依赖了shiro-spring,但我习惯在pom.xml里显式声明,避免后续版本升级时丢依赖。

第三,java.version必须 17+,这是 Spring Boot 3.x 的硬性要求,跟 Shiro 无关。

2.2 用 mvn dependency:tree 揪出所有 javax.servlet 依赖

引入 Shiro 2.0.1 之后,项目如果还能启动,说明你的依赖树已经基本健康。但很多人的项目里还有别的老依赖,比如老版本的 Ehcache、Hibernate Validator、某些第三方 starter,它们可能会传递引入javax.servlet-api

这种依赖通常不会直接编译报错,因为你的代码用的是jakarta,但一旦某个框架内部用反射加载了javax.servlet.Filter,就会在运行时炸出来。而且这种错误往往特别诡异,报错点可能在 Spring 容器刷新阶段,也可能在你访问某个接口时。

排查方法很简单,直接在项目根目录执行:

mvn dependency:tree -Dincludes=javax.servlet

这条命令会列出所有依赖树中带javax.servlet的坐标。我实际遇到的情况是一个老版本的shiro-redis-spring-boot-starter间接引入了javax.servlet-api,而它的代码又是基于javax.servlet.http.HttpSession编译的,导致启动时直接进入错误分支。

处理方式一般有两种:升级这个第三方依赖到支持jakarta的版本;或者如果短期内升不了,就在自己的pom.xml里对它做排除:

<dependency> <groupId>org.crazycake</groupId> <artifactId>shiro-redis-spring-boot-starter</artifactId> <version>3.3.1</version> <exclusions> <exclusion> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> </exclusion> </exclusions> </dependency>

但这里要提醒你:排除依赖只是治标,如果那个库内部真的用了javax.servlet的类,排除之后会变成NoClassDefFoundError。所以最好的方案还是找一个已经适配jakarta的版本。

2.3 不只是 servlet:其他 javax 包也一起迁移了

这次迁移不只是javax.servlet改成jakarta.servlet,其他 Java EE API 也一样:

  • javax.validationjakarta.validation
  • javax.annotationjakarta.annotation
  • javax.persistencejakarta.persistence
  • javax.transactionjakarta.transaction

其中最容易让老项目翻车的,是javax.annotation.PostConstruct。很多人在自定义 Filter、监听器、工具类里用过@PostConstruct,升级之后如果没注意 import,启动时 Spring 容器可能根本不会调用这个初始化方法,导致一些静态数据没加载。

另外,JDK 11 之后,Java EE 模块被从 JDK 中逐步移出,比如javax.xml.bind(JAXB)。如果你项目里还有 XML 解析相关的代码,可能还会遇到ClassNotFoundException: javax.xml.bind.JAXBException。这种情况需要在pom.xml里单独引入jakarta.xml.bind-api,或者干脆检查一下是否还有旧代码在使用这些库。

我在迁移时做了个笨但有效的事:在 IDEA 里全局搜索import javax.这个前缀,把结果列出来,一个一个看。很多代码只是 import 语句需要替换,没有其他逻辑变化。

2.4 渐进式接入:先让空项目跑起来再谈 Shiro

我推荐的做法是:先不要一口气把 Shiro 配置全部搬过去,而是在一个干净的 Spring Boot 3.2 项目里只加 Shiro 依赖,不加任何 ShiroConfig,不加过滤链,先启动一次看基础环境是否正常。

这一步的意义在于:把“依赖迁移”和“配置迁移”这两个问题隔离开来。如果连依赖都有问题,你后边加配置时看到的报错会把原因混在一起,排查成本翻倍。

我当时是先建了一个 demo 项目,把 Spring Boot 3.2 跑起来,确认 Tomcat 正常启动、端口正常显示,然后再开始写 Shiro 的配置类。事实证明这个决策非常明智,后面遇到启动异常时,我基本可以断定问题出在 Shiro 配置上,而不是底层环境。

3. 配置类翻新:从 ShiroFilterFactoryBean 到链式定义的完整改写

3.1 旧的 ShiroFilterFactoryBean 配置为什么在新版失去意义

如果你以前用 Spring Boot 2.x + Shiro 1.x,大概率见过这种配置:

@Bean public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) { ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean(); factoryBean.setSecurityManager(securityManager); factoryBean.setLoginUrl("/login"); factoryBean.setUnauthorizedUrl("/403"); Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>(); filterChainDefinitionMap.put("/logout", "logout"); filterChainDefinitionMap.put("/**", "authc"); factoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); return factoryBean; }

这套配置在 Shiro 1.x 时代完全没问题,但在 Shiro 2.0.1 里,ShiroFilterFactoryBean已经被移除了。我第一次编译时看到“程序包 org.apache.shiro.spring.web.config 不存在”还以为是版本拉错了,后来翻官方文档才确认:Shiro 2.0 的 Web 集成方式改成“链式定义”了。

这背后还有一个更底层的逻辑:Shiro 2.0 不再自己去创建 Filter 实例并强行注入 Servlet 容器,而是把过滤器的构建和注册交给 Spring Boot 的自动配置机制。你只需要告诉 Shiro “我要拦截哪些路径”,剩下的由它自动完成。这个变更大大减少了对javax.servletAPI 的直接依赖,也是它能迁移到jakarta的基础之一。

3.2 新配置样板:SecurityManager 和 ShiroFilterChainDefinition

下面是 Shiro 2.0.1 在 Spring Boot 3.2 下能直接用的配置类骨架:

import jakarta.servlet.Filter; import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.realm.Realm; import org.apache.shiro.web.mgt.DefaultWebSecurityManager; import org.apache.shiro.web.filter.authc.FormAuthenticationFilter; import org.apache.shiro.web.servlet.ShiroFilter; import org.apache.shiro.web.spring.config.ShiroFilterChainDefinition; import org.apache.shiro.web.spring.config.DefaultShiroFilterChainDefinition; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.util.LinkedHashMap; import java.util.Map; @Configuration public class ShiroConfig { @Bean(name = "securityManager") public DefaultWebSecurityManager securityManager(Realm realm) { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); securityManager.setRealm(realm); return securityManager; } @Bean public ShiroFilterChainDefinition shiroFilterChainDefinition() { DefaultShiroFilterChainDefinition chainDefinition = new DefaultShiroFilterChainDefinition(); Map<String, String> filterChainMap = new LinkedHashMap<>(); filterChainMap.put("/login", "anon"); filterChainMap.put("/logout", "logout"); filterChainMap.put("/**", "authc"); chainDefinition.addPathDefinitions(filterChainMap); return chainDefinition; } }

这里有几个关键点:

第一,SecurityManager的 Bean 名字必须是securityManager。Shiro 自动配置会以这个名字去容器里找DefaultWebSecurityManager,如果名字不对,运行时会提示找不到 SecurityManager 相关的 Bean。

第二,ShiroFilterChainDefinition替代了以前的filterChainDefinitionMap。路径匹配规则和以前一致,anon表示匿名访问,authc表示需要认证,logout表示退出。

第三,如果你需要自定义登录地址,可以用:

FormAuthenticationFilter filter = new FormAuthenticationFilter(); filter.setLoginUrl("/login");

但要注意,如果项目是纯前后端分离的 REST 风格,FormAuthenticationFilter默认的 302 跳转行为会让前端很痛苦。这个坑我后面详细说。

3.3 自定义 Realm:登录和权限的核心

Realm 是 Shiro 的身份数据源,相当于用自己的账号体系对接 Shiro。一个典型的自定义 Realm 长这样:

import org.apache.shiro.authc.*; import org.apache.shiro.authz.AuthorizationInfo; import org.apache.shiro.authz.SimpleAuthorizationInfo; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.subject.PrincipalCollection; import java.util.HashSet; import java.util.Set; public class JwtRealm extends AuthorizingRealm { @Override public boolean supports(AuthenticationToken token) { return token instanceof UsernamePasswordToken; } @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { // 这里模拟从数据库查用户 String username = (String) token.getPrincipal(); if (!"admin".equals(username)) { throw new UnknownAccountException("用户不存在"); } return new SimpleAuthenticationInfo(username, "123456", getName()); } @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { String username = (String) principals.getPrimaryPrincipal(); Set<String> roles = new HashSet<>(); Set<String> permissions = new HashSet<>(); if ("admin".equals(username)) { roles.add("admin"); permissions.add("user:add"); permissions.add("project:create"); } SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo(); authorizationInfo.setRoles(roles); authorizationInfo.setStringPermissions(permissions); return authorizationInfo; } }

这段代码和 1.x 时代几乎没什么区别,说明 Shiro 2.0 在核心认证授权模型上是向后兼容的,主要变化集中在 Web 层和 Servlet 容器集成层。

3.4 注解权限的补丁:别让 @RequiresPermissions 无故失效

使用@RequiresPermissions这类注解时,需要 Spring AOP 和 Shiro 的 Advisor 配合。在我的项目里,光有shiro-spring-boot-web-starter还不够,还要手动声明两个 Bean,否则注解权限会静默失效:

import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor; import org.apache.shiro.mgt.SecurityManager; import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class ShiroAnnotationConfig { @Bean public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() { DefaultAdvisorAutoProxyCreator creator = new DefaultAdvisorAutoProxyCreator(); creator.setProxyTargetClass(true); return creator; } @Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) { AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor(); advisor.setSecurityManager(securityManager); return advisor; } }

很多人在 Spring Boot 3.2 下升级 Shiro 2.0.1 后,发现接口不鉴权了,以为是自己权限配置写错,其实只是 AOP 代理没生效。这个 Bug 不会报任何错误,只会让你所有权限注解变成摆设。

4. 踩坑实录:两天内遇到的四个致命异常和完整排查链

4.1 ClassNotFoundException: javax.servlet.Filter——开门第一坑

这是最典型、最令人崩溃的错误。我当时运行mvn spring-boot:run,控制台前几行还正常,然后突然抛出一大段异常日志,最上面写着:

java.lang.ClassNotFoundException: javax.servlet.Filter

第一反应是依赖里少了javax.servlet-api,于是往pom.xml里加了这个依赖,结果启动后变成了另一个错误:java.lang.NoClassDefFoundError: javax/servlet/Filter。这是因为 Tomcat 10 里根本没有javax.servlet.Filter这个类。

排查链路是这样走的:

  1. 先确认内嵌 Tomcat 版本。Spring Boot 3.2 默认 Tomcat 10.1.x,它只实现jakarta.servlet
  2. 再看 Shiro 版本。shiro-core1.x 的所有 Web 模块都是基于javax.servlet编译,所以只要 Shiro 还是 1.x,这个错误就是必然。
  3. 最后用mvn dependency:tree -Dincludes=javax.servlet看依赖来源,把不需要的旧依赖排除掉。

最终的修复就是升级到 Shiro 2.0.1,并确保没有别的库把javax.servlet-api拉进来。如果你是降级回来或者非要兼容老版本,那就得用 Spring Boot 2.7 以下的版本,别在 Boot 3.2 上折腾。

4.2 启动日志不显示端口号:不是端口冲突,是 Context 初始化直接失败

有一个现象很误导人:项目启动后,控制台看不到Tomcat started on port 8080这行日志,看起来像端口被占用,或者启动被卡住了。我之前就盲目地执行了lsof -i:8080查看端口,结果什么都没查到。

后来才明白,这种“静默失败”往往是因为 Spring 容器在刷新阶段就已经抛了异常,只是异常堆栈被吞掉了一部分,日志只显示到 Spring Boot 的 banner 和启动信息就断了。

排查方法:

mvn spring-boot:run -Dspring-boot.run.arguments=--debug

开启 debug 模式后,真正的Caused by才会显示出来。在我的场景里,根因是 Shiro 的自动配置尝试创建一个ShiroFilter,但这个 Filter 类型是org.apache.shiro.web.servlet.ShiroFilter,它同样需要jakarta.servlet.Filter接口。因为这个接口找不到实现,整个 WebApplicationContext 初始化失败,自然也就轮不到 Tomcat 打印端口号了。

如果你在 IDEA 里启动 Spring Boot 项目,看到控制台只输出了 banner、没有端口号,第一步别去查端口占用,直接往下翻异常堆栈,或者开 debug。大部分情况下,这是容器初始化失败,不是端口问题。

4.3 登录接口 302 到 /login:FormAuthenticationFilter 的传统路径逻辑

当 Shiro 的过滤链配置了/ **authc后,所有未认证请求都会被拦截。如果在配置里设置了loginUrl,Shiro 会对未认证请求发起重定向。

对于传统服务端渲染的页面项目,这种方式没问题;但现代项目大多是前后端分离,前端用fetchaxios调用接口,遇到 302 时浏览器会自动跟随重定向到/login,返回一个 HTML 页面,前端拿到的不是 JSON,解析直接失败。

当时我们项目的登录接口是/api/login,前端请求/api/project/list时收到的重定向是/login,导致前端控制台报了一堆 404 或者 HTML parser 错误。

解决方式有两种:

一种是自定义过滤器,拦截未认证请求时直接返回 401 JSON 响应,不重定向。这个方案更符合前后端分离架构。

另一种更简单:把/login等认证接口配置为anon,同时把loginUrl指向一个不存在的路径,让未认证请求进入认证逻辑而不是重定向。但这种方法不够优雅,我后来还是花时间写了一个无状态的认证过滤器。

4.4 rememberMe 和 Session 的未授权异常,被安全扫描逼出来的重构

Shiro 1.x 的 rememberMe 机制默认使用 Java 序列化,配合硬编码密钥,这是安全扫描重点关注的对象。升级到 Shiro 2.0.1 后,我特意检查了 rememberMe 的配置,发现如果还沿用旧代码的硬编码方式,安全扫描依然会报高风险。

建议至少做到两点:

第一,把 rememberMe 的密钥放到配置文件中,并通过环境变量注入,不要写死在代码里。

第二,如果项目本身是接口型服务,建议尽量用 JWT 替代 Session 机制。这样可以让 Shiro 的 Subject 状态不依赖服务端 Session,减少跨域和重启失效的问题。当然,这会涉及更大的重构,不是简单改配置就行。

Shiro 2.0.1 本身已经做了很多安全增强,但底层设计上 rememberMe 仍然是一把双刃剑。我的原则是:非必要不开启,必要则必须使用随机加密密钥,并且定期轮换。

5. 上线前对照清单:怎么验证这次迁移没有埋雷

5.1 代码检查清单

如果你也是从 Shiro 1.x 升级到 2.0.1,上线前先过一遍这个清单:

  • 全局搜索import javax.servlet.,确认所有 Web 相关 import 都已切换为jakarta.servlet.
  • 检查pom.xml中是否还有javax.servlet-api依赖,能用exclusion排除就排除,不要留着。
  • 检查web.xmlServletRegistrationBeanFilterRegistrationBean中是否有直接引用javax.servlet类型的地方。
  • 确认SecurityManager的 Bean 名称为securityManager,避免自动配置找不到。
  • 确认ShiroFilterChainDefinition中路径匹配顺序正确,anon路径一定要放在/**之前,否则会被authc拦截。

5.2 功能回归用例模板

我在迁移完成后会执行一套固定的回归用例,你可以直接参考:

测试项预期结果说明
未登录访问受保护接口返回 401 或重定向到登录页取决于你选择的认证方案
登录成功后访问/api/project/list返回 200 和业务数据验证认证过滤器正常放行
使用@RequiresPermissions("user:add")的接口,未授权用户访问返回 403验证注解权限和 AOP Advisor 生效
退出登录后再次访问受保护接口返回 401验证 Session 清理正常
正常登录后等待 Session 过期再次请求被拦截验证会话管理正常工作
rememberMe 开启后重启服务部分场景下登录态可持续不要在纯 API 场景默认开启

这里特别提醒,路径匹配规则的顺序非常重要。Shiro 的过滤器链遵循“先匹配先生效”的原则,如果/loginanon写在/ **authc后面,那anon实际上永远不会生效。

5.3 回滚与灰度策略

迁移这种底层的认证授权框架,哪怕测试再充分,也建议预留回滚方案。我的做法是:

第一,在 Git 里保留迁移前的分支,并打一个 tag,比如release/shiro-1.13-boot-2.7。这样如果线上出现无法容忍的问题,可以快速切回旧分支重新发版。

第二,不要直接在生产环境做“一次性切换”。先在预发环境跑一个完整的回归周期,重点观察 Session、权限、登录接口的响应时间变化。Shiro 2.0 的 Session 管理和 1.x 在底层实现上有差异,某些极端情况下旧 Session 无法复用,用户会被强制下线,需要提前通知相关方。

第三,如果必须灰度,可以先让少量用户走新版本网关,通过流量比例控制逐步放量。不过在实际操作中,认证授权框架通常不适合细粒度灰度,因为它影响所有接口。所以我的建议是“预发充分回归 + 生产快速回滚预案”的策略,比“少量灰度”更实在。

最后说点个人体会

这次从 Spring Boot 2.7 + Shiro 1.13 迁移到 Spring Boot 3.2 + Shiro 2.0.1,整个过程最折磨人的不是配置类的改写,而是那些藏在第三方依赖里的javax引用。它们平时安安静静躺在依赖树里,一旦你切换到 Jakarta 容器环境,就会在运行时的某个角落突然跳出来捣乱。

我的一个经验是:遇到这种底层基础框架升级,先别急着改业务代码,一定要先把依赖树和容器版本确认清楚。用mvn dependency:tree花半小时扫一遍,比上线后花了几个小时排查异常堆栈要划算得多。

另外,建议完整浏览一遍 Shiro 2.0 的官方文档,重点看 Spring Boot 集成部分的“兼容性说明”。按照我的实际体验,Shiro 2.0 的核心 API 变化不大,但 Web 层集成方式确实换了思路,只要把 SecurityManager、ShiroFilterChainDefinition 和 AOP 支持这三件事理顺,整个迁移就成功了大半。

如果你接下来也要做类似升级,希望这篇踩坑实录能帮你少走一些弯路。我最后再推荐一个小习惯:在所有 import 路径上都用jakarta.*,并且尽量不要在项目里保留任何javax.*的 Servlet API 依赖,干净清爽才能睡得安稳。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询