尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Sa-Token拦截器演进:从Servlet Filter到Spring MVC原生集成

Sa-Token拦截器演进:从Servlet Filter到Spring MVC原生集成 1. 从“过滤器”到“拦截器”为什么Sa-Token要引入SaInterceptor如果你和我一样是从Sa-Token的早期版本一路用过来的老用户看到v1.31.0这个更新心里可能会“咯噔”一下又来新东西了之前的SaServletFilter用得好好的怎么突然冒出来一个SaInterceptor是不是又要改代码了别慌这其实是一个“早有预谋”的优化目的不是给你添麻烦而是让权限控制的集成变得更优雅、更强大。简单来说SaInterceptor是Sa-Token为了拥抱更广泛的Web框架生态特别是对Spring MVC、Spring Boot WebFlux乃至未来可能出现的其他框架提供更原生、更一致的集成体验而设计的一套新机制。在它出现之前Sa-Token的核心登录认证与权限校验逻辑是打包在一个名为SaServletFilter的Servlet过滤器里的。过滤器Filter是Java Web的古老标准它在Servlet规范层面工作能拦截一切进入容器的请求功能强大且通用。但正是这种“通用性”在某些场景下成了短板。举个例子当你使用Spring MVC时一个请求的生命周期是Filter-DispatcherServlet-Interceptor-Controller。SaServletFilter工作在Filter层它执行得非常早早于Spring的DispatcherServlet也早于Spring MVC自己的拦截器Interceptor。这带来几个问题第一你无法直接享受到Spring MVC拦截器链的便利比如与其他拦截器的执行顺序控制第二在处理一些非标准Servlet API的框架如WebFlux时Filter这套机制就显得水土不服第三Filter的配置相对底层在纯注解驱动的Spring Boot环境中用起来总感觉不够“Spring范儿”。SaInterceptor的诞生就是为了解决这些痛点。它本质上是一套适配器模式将Sa-Token的核心校验逻辑抽象出来然后针对不同的Web框架提供具体的拦截器实现。在Spring MVC环境下它就是SaInterceptor在WebFlux环境下未来可能就是SaWebFilter。这样做的好处是Sa-Token的校验能力可以像乐高积木一样无缝嵌入到各个框架的原生拦截器链中执行时机、异常处理、依赖注入都能和框架本身完美融合。所以这次更新不是简单的功能叠加而是一次重要的架构演进。它意味着Sa-Token正在从一个“好用”的组件向一个“既好用又专业”的框架级解决方案迈进。对于开发者而言最直接的好处就是集成更简单控制更精细未来兼容性更好。接下来我们就深入看看这个新拦截器具体怎么用以及如何把你的旧项目平滑地迁移过来。2. SaInterceptor核心功能与配置详解理解了为什么需要SaInterceptor我们再来看看它具体能做什么以及怎么把它配置到项目里。它的核心职责和之前的SaServletFilter是一致的即负责HTTP请求的登录认证与权限校验但实现方式更贴合现代Web框架。2.1 核心校验逻辑与执行流程SaInterceptor内部封装了Sa-Token最核心的几项校验其执行流程可以概括为以下几步这个流程比单纯的Filter更清晰因为它融入了Spring MVC的拦截器生命周期前置处理preHandle这是拦截器的主要工作阶段。当一个请求匹配到拦截路径后SaInterceptor会依次执行以下校验登录校验调用StpUtil.checkLogin()。这是最基本的一关检查当前会话是否已登录。如果未登录将抛出NotLoginException。角色校验如果配置了角色认证如SaCheckRole(admin)拦截器会解析注解并调用StpUtil.checkRole(role)进行验证。权限校验如果配置了权限认证如SaCheckPermission(user:add)则调用StpUtil.checkPermission(permission)。二级认证校验如果接口要求二级认证SaCheckSafe则会检查当前会话是否已完成二次验证。Http Basic 认证如果接口配置了SaCheckBasic会进行基础的HTTP认证校验。后置处理与完成处理postHandle / afterCompletion在Spring MVC拦截器中postHandle在Controller方法执行之后、视图渲染之前调用afterCompletion在整个请求完成之后调用。SaInterceptor目前的核心逻辑集中在preHandle但这样的架构为未来扩展留下了空间例如可以在afterCompletion中统一记录审计日志或清理线程上下文数据。与SaServletFilter最大的不同在于异常处理。Filter中校验失败抛出异常后需要依靠你自定义的全局异常处理器如ControllerAdvice来捕获并转换。而SaInterceptor作为Spring MVC拦截器其抛出的异常会立即进入Spring MVC的异常解析流程与你项目中已有的全局异常处理机制结合得更加天衣无缝。2.2 在Spring Boot中的两种配置方式在Spring Boot项目中启用SaInterceptor非常简单。官方推荐使用注册WebMvcConfigurerBean的方式这也是最“Spring”的方式。方式一通过配置类注册推荐这是最清晰、最常用的方式。创建一个配置类实现WebMvcConfigurer接口然后重写addInterceptors方法。import cn.dev33.satoken.interceptor.SaInterceptor; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; Configuration public class SaTokenConfigure implements WebMvcConfigurer { Override public void addInterceptors(InterceptorRegistry registry) { // 注册 Sa-Token 拦截器并指定拦截路径和排除路径 registry.addInterceptor(new SaInterceptor()) .addPathPatterns(/**) // 拦截所有路径 .excludePathPatterns(/auth/login, /auth/logout, /doc.html, /swagger-resources/**, /webjars/**, /v2/**, /swagger-ui.html/**); // 排除登录、登出、Swagger等接口 } }关键点解析new SaInterceptor()这里创建的是基础的拦截器实例。你可能会想能不能像其他Spring Bean一样用Autowired注入目前SaInterceptor的设计是无状态的直接new一个即可简单高效。未来如果它需要依赖其他Spring管理的Bean官方可能会提供Bean方式的配置。addPathPatterns(/**)这是拦截规则。/**表示拦截所有请求。我强烈建议你不要一上来就全局拦截除非你的项目所有接口都需要认证。更好的做法是只拦截需要保护的API路径比如/api/**而对登录、公开文档等接口进行排除。excludePathPatterns(...)排除规则至关重要。你必须把登录接口如/auth/login放行否则用户永远无法登录。同样像Swagger、Knife4j这类API文档的静态资源路径也必须排除否则文档页面都无法打开。这是一个非常容易踩的坑。方式二通过注解属性配置更简洁从Sa-Token v1.31.0开始你还可以使用更简洁的方式。在任意一个Configuration配置类上使用SaTokenConfigure注解。import cn.dev33.satoken.annotation.SaTokenConfigure; import org.springframework.context.annotation.Configuration; Configuration SaTokenConfigure(interceptors SaTokenConfigure.Interceptor( pathPatterns /**, excludePathPatterns {/auth/login, /doc.html/**} )) public class MyConfiguration { // ... 其他配置 }这种方式更加声明式代码量少。但它的灵活性略低于第一种方式例如无法动态地、根据条件注册拦截器。对于大多数标准项目这种方式完全够用。注意无论采用哪种方式SaInterceptor和旧的SaServletFilter是互斥的。如果你按照下面的步骤迁移并正确配置了SaInterceptor就一定要确保旧项目中关于SaServletFilter的配置无论是通过Bean还是web.xml已经被移除或禁用否则会导致重复校验甚至引发意想不到的冲突。3. 从SaServletFilter迁移到SaInterceptor完整步骤与避坑指南理论讲完了现在进入实战环节。假设你有一个正在使用SaServletFilter的Spring Boot项目现在要升级到v1.31.0并使用SaInterceptor。请跟随以下步骤一步一坑我们把它填平。3.1 第一步依赖升级与旧配置清理首先修改你的pom.xml或build.gradle将Sa-Token的版本更新到至少1.31.0。!-- Maven 示例 -- dependency groupIdcn.dev33/groupId artifactIdsa-token-spring-boot-starter/artifactId version1.31.0/version !-- 确保版本号 1.31.0 -- /dependency升级依赖后最重要的一步是找到并移除旧的Filter配置。根据你项目的配置方式清理点不同如果你通过Bean方式注册了SaServletFilter找到对应的配置类通常叫SaTokenConfigure或FilterConfig直接删除或注释掉整个Bean方法。例如// 旧的配置需要删除或注释 // Bean // public SaServletFilter getSaServletFilter() { // return new SaServletFilter() // .addInclude(/**) // .addExclude(/auth/login) // .setAuth(obj - StpUtil.checkLogin()); // }如果你通过web.xml配置了Filter在Spring Boot项目中这种情况较少如果存在请从web.xml中移除相关filter和filter-mapping配置。检查application.yml或application.properties旧版本可能有一些针对Filter的配置项如sa-token.filter.exclude。这些配置对SaInterceptor是无效的。你需要将它们删除或者意识到它们不再起作用后续的拦截路径将在Java代码中配置。3.2 第二步新增SaInterceptor配置按照上一节讲解的“方式一”或“方式二”在你的项目中新增SaInterceptor的配置。这里我以最推荐的配置类方式为例创建一个新的SaTokenConfigure类。迁移时的路径匹配对照这是迁移中最容易出错的地方。旧Filter的addInclude和addExclude对应到拦截器就是addPathPatterns和excludePathPatterns。你需要仔细核对旧项目放行了哪些路径并在新的排除规则中一一还原。登录/登出接口必须排除。验证码、注册等公开接口必须排除。静态资源如/static/**,/public/**,/resources/**。API文档如Swagger的/v2/api-docs,/swagger-ui/**,/doc.htmlKnife4j的/doc.html及相关资源路径。健康检查如Spring Boot Actuator的/actuator/health。WebSocket端点如果用了WebSocket如/ws/**通常也需要排除因为握手请求可能被拦截。错误页面如/error。一个相对完整的迁移示例如下Configuration public class SaTokenConfigure implements WebMvcConfigurer { Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new SaInterceptor()) .addPathPatterns(/**) .excludePathPatterns( // 认证相关 /auth/login, /auth/logout, /auth/register, /captcha/**, // 静态资源 /static/**, /public/**, /favicon.ico, // API文档 (Swagger Knife4j) /doc.html, /webjars/**, /swagger-resources/**, /v2/api-docs, /swagger-ui.html/**, // 健康检查 /actuator/health, // 错误页面 /error ); } }3.3 第三步处理注解鉴权的变化在SaServletFilter时代注解鉴权如SaCheckRole通常需要配合SaTokenCheck注解或在Filter配置中开启注解校验才能生效。而SaInterceptor默认就集成了注解鉴权能力。这是一个积极的改进意味着你Controller层上的SaCheckRole、SaCheckPermission等注解在注册了SaInterceptor后会自动生效。但是这里有一个至关重要的细节注解的生效范围。SaInterceptor是一个Spring MVC拦截器它只对通过DispatcherServlet路由的请求生效也就是你的Controller和RestController中的方法。这意味着静态资源处理器如果你通过WebMvcConfigurer的addResourceHandlers自定义了静态资源映射这些路径虽然被拦截器规则排除了但本身也不会走注解校验流程没问题。直接定义的BeanServlet或Filter如果你的项目里还有一些老式的、直接以Bean方式声明的Servlet或Filter例如某些监控端点这些请求可能不会经过SaInterceptor因此其上的Sa-Token注解会失效。Spring Security的过滤器链如果你同时集成了Spring Security请求会先经过Security的过滤器链。SaInterceptor在更后面的MVC层。你需要确保两者的安全规则不冲突通常Sa-Token负责业务权限Spring Security负责HTTP安全如CSRF、CORS和更底层的认证。迁移检查清单[ ] 升级Sa-Token依赖至1.31.0。[ ] 移除或禁用所有旧的SaServletFilter配置Bean或web.xml。[ ] 删除application.yml中关于sa-token.filter的无效配置。[ ] 新增SaInterceptor配置类并仔细核对拦截与排除路径。[ ] 启动项目首先测试被排除的路径如登录、Swagger是否能正常访问。[ ] 登录后测试需要权限的接口是否正常。[ ] 测试未登录访问需权限接口是否正确返回401状态码和配置的提示信息。[ ] 检查所有带有SaCheckXXX注解的接口确保鉴权逻辑与迁移前一致。4. 进阶自定义拦截逻辑与多拦截器编排基础的迁移完成后SaInterceptor还能玩出更多花样。它不是一个黑盒允许你进行一定程度的自定义并且能很好地融入Spring MVC的拦截器生态。4.1 自定义认证函数虽然SaInterceptor默认集成了完整的校验逻辑但它也提供了构造函数允许你传入一个自定义的认证函数。这在你需要实现一些非标准的、全局性的校验逻辑时非常有用。Configuration public class SaTokenConfigure implements WebMvcConfigurer { Override public void addInterceptors(InterceptorRegistry registry) { // 创建自定义认证逻辑的拦截器 SaInterceptor interceptor new SaInterceptor(handler - { // 1. 先执行Sa-Token标准登录校验 StpUtil.checkLogin(); // 2. 自定义逻辑例如检查请求头中的特定令牌或IP黑白名单 String customFlag SaHolder.getRequest().getHeader(X-Custom-Flag); if (!ALLOWED.equals(customFlag)) { throw new SaTokenException(自定义校验失败); } // 3. 注解鉴权依然会生效因为这是SaInterceptor的内置行为 // 除非你在这里完全重写逻辑并跳过默认的注解处理不推荐。 }); registry.addInterceptor(interceptor) .addPathPatterns(/api/**) .excludePathPatterns(/api/auth/**); } }使用场景比如你的应用需要同时验证Sa-Token的会话和另一个来自网关的JWT令牌或者需要对所有管理端接口进行操作日志的预处理就可以在这个函数里实现。需要注意的是一旦提供了自定义函数你就必须手动调用StpUtil.checkLogin()等基础方法否则登录校验会失效。但注解鉴权SaCheckRole默认仍会处理除非你的逻辑完全覆盖了它。4.2 与其它拦截器的执行顺序在真实的项目中SaInterceptor很少是唯一的拦截器。你可能有日志拦截器、接口耗时统计拦截器、数据脱敏拦截器等。控制它们的执行顺序至关重要。在Spring MVC中通过InterceptorRegistry注册的拦截器其执行顺序默认就是注册的顺序。你可以利用WebMvcConfigurer的addInterceptors方法多次调用registry.addInterceptor来控制。Configuration public class WebConfig implements WebMvcConfigurer { Override public void addInterceptors(InterceptorRegistry registry) { // 1. 日志拦截器最先执行记录请求入参 registry.addInterceptor(new LogInterceptor()).addPathPatterns(/**).order(1); // 2. Sa-Token权限拦截器在日志之后在业务逻辑之前进行认证鉴权 registry.addInterceptor(new SaInterceptor()).addPathPatterns(/**).order(10); // 3. 数据脱敏拦截器在业务处理之后响应返回之前执行 registry.addInterceptor(new DataMaskInterceptor()).addPathPatterns(/**).order(100); } }最佳实践建议SaInterceptor的顺序应放在业务逻辑之前。通常在日志记录之后在其他需要已认证用户信息的拦截器如用户信息注入拦截器之前。一个常见的顺序是日志(1) - 权限(10) - 用户上下文注入(20) - 业务。使用order()方法明确指定顺序值数值越小优先级越高越先执行。不指定order时默认顺序为0。记住preHandle方法按order正序执行postHandle和afterCompletion按order倒序执行。SaInterceptor目前主要工作在preHandle阶段。4.3 在WebFlux与非Servlet环境下的思考SaInterceptor目前主要针对Spring MVCServlet栈。如果你的项目使用的是Spring WebFlux响应式栈那么原生的SaInterceptor是无法直接使用的因为WebFlux没有HandlerInterceptor这个概念。对于WebFluxSa-Token提供了SaReactorFilter或未来可能统一的适配器。其配置方式类似但需要注册为WebFlux的WebFilter。迁移思路是类似的移除旧的全局Filter换用框架原生的方式集成。这体现了Sa-Token新架构的优势为不同框架提供最合适的集成方式而不是一个Filter走天下。即使你现在用的是MVC了解这一点也有助于规划未来。如果你的架构有向响应式迁移的可能那么现在采用这种“框架原生集成”的思路未来的迁移成本会更低。5. 常见问题排查与性能优化建议迁移完成功能正常是不是就高枕无忧了别急在实际运行中你可能会遇到一些“怪现象”。下面是我总结的几个常见问题及排查思路。5.1 拦截器不生效路径匹配可能是元凶问题描述配置了SaInterceptor但访问需要登录的接口却没有被拦截或者访问排除的路径反而被拦截了。排查步骤检查配置类是否被加载确保你的配置类上有Configuration注解并且位于Spring Boot组件扫描的包路径下。最简单的验证方法是启动时查看日志或者在里面加个PostConstruct打印日志。仔细核对路径模式这是最高频的错误点。excludePathPatterns(/auth/login)只能精确匹配/auth/login。如果你想排除/auth/login和/auth/login/需要写成/auth/login, /auth/login/或者使用通配符/auth/login/**。特别注意Swagger/Knife4j它们的资源路径很多需要排除一组常见的包括/doc.html,/webjars/**,/swagger-resources/**,/v2/api-docs,/swagger-ui.html/**。少一个都可能让文档页面加载异常。检查是否有其他配置覆盖你是否在别的地方也配置了WebMvcConfigurer并设置了addInterceptors或者是否有通过EnableWebMvc完全接管了MVC配置确保你的SaInterceptor被正确添加到了最终的拦截器链中。确认旧Filter已彻底移除如果旧的SaServletFilter的Bean没有注释或删除它仍然在工作可能会和拦截器产生冲突或重复校验。5.2 注解鉴权失效注意作用域与AOP顺序问题描述Controller方法上的SaCheckPermission注解没有起作用用户即使没有权限也能访问。排查步骤确认拦截器已注册并拦截了该路径参考上一条。检查注解位置SaCheckPermission等注解必须放在Controller类的方法上或类上。放在Service层是无效的因为SaInterceptor只拦截Controller。注意AOP代理的坑如果你的Controller方法被Spring AOP代理了比如使用了Transactional,Async或者自定义的AOP并且AOP切面的执行顺序在拦截器之前可能会影响注解的解析。虽然不常见但如果遇到可以尝试调整AOP切面的Order值确保权限拦截器在业务AOP之前执行。尝试最简单的注解写一个最简单的测试接口只加SaCheckLogin看登录校验是否生效。如果基础登录校验生效而权限校验不生效问题可能出在权限数据本身角色权限关系未正确配置。5.3 性能考量与最佳实践引入任何全局拦截器都会带来微小的性能开销。对于SaInterceptor我们可以通过一些配置让它更高效缩小拦截范围这是最有效的优化。不要无脑使用/**。根据项目结构只拦截API路径例如/api/**、/admin/**。将静态资源、公开接口明确排除。合理设计排除列表将确切的、高频的公开路径如/login,/health放在排除列表前列。虽然影响微乎其微但良好的习惯很重要。避免在拦截器中进行复杂IO操作自定义认证函数里不要进行数据库查询、远程调用等重型操作。权限信息应在登录时加载到缓存如Redis拦截器中只进行内存或缓存检查。关注Session存储模式Sa-Token支持多种Session存储模式内存、Redis等。在分布式环境下务必使用Redis等集中存储避免每个实例内存存储带来的数据不一致和登录态失效问题。SaInterceptor本身不关心存储但存储模式直接影响认证校验的速度和可靠性。迁移到SaInterceptor表面上看只是换了一种配置方式但其背后是Sa-Token对更优雅、更强大集成方式的追求。这个过程可能会遇到一些小麻烦但一旦完成你会获得更清晰的配置、更好的框架兼容性以及更可控的执行流程。我的建议是在新项目中直接使用SaInterceptor对于老项目可以规划一个低峰期进行升级和测试。毕竟跟上主流框架的演进步伐是保持项目健康度的关键一环。
返回列表