Sa-Token 路由拦截鉴权实战:基于 SaInterceptor 与 SaRouter 实现全局登录校验与模块化权限控制

Sa-Token 路由拦截鉴权实战:基于 SaInterceptor 与 SaRouter 实现全局登录校验与模块化权限控制 Sa-Token 路由拦截鉴权实战基于 SaInterceptor 与 SaRouter 实现全局登录校验与模块化权限控制【免费下载链接】Sa-Token✨ 开源、免费、一站式 Java 权限认证框架让鉴权变得简单、优雅—— 登录认证、权限认证、分布式 Session 会话、微服务网关鉴权、SSO 单点登录、OAuth2.0 统一认证、jwt 集成、API Key 秘钥授权、API 参数签名项目地址: https://gitcode.com/GitHub_Trending/sa/Sa-Token当项目中的绝大多数接口都需要登录校验、只有极少数接口如登录接口本身对外开放时逐个为 Controller 添加注解鉴权既繁琐又容易遗漏。Sa-Token 提供了一套完整的路由拦截鉴权方案通过SaInterceptor拦截器 SaRouter路由匹配器只需一段集中配置即可对全站接口做统一的前置审核。本文将以 route-check.md 为主线结合 Sa-Token 核心源码SaRouter.java、SaRouterStaff.java、SaInterceptor.java与官方 DemoSaTokenConfigure.java讲解从注册拦截器到进阶的匹配、退出、作用域控制等完整用法。一、需求场景与整体思路假设业务上有这样一条需求项目中所有接口均需要登录校验只有“登录接口”本身对外开放。如果给每个接口都手动加上注解鉴权将是一件比较麻烦的事情。此时使用拦截器鉴权模式将大大降低代码量。其整体流程如下注册一个全局拦截器拦截除登录接口以外的所有请求每次请求到达 Controller 之前拦截器先执行一道前置审核登录校验、角色校验、权限校验等审核通过则放行进入 Controller审核失败则抛出对应异常并结束请求。二、注册 Sa-Token 路由拦截器以 SpringBoot 2.0 为例新建配置类SaTokenConfigure.java实现WebMvcConfigurer接口并重写addInterceptors方法Configuration public class SaTokenConfigure implements WebMvcConfigurer { // 注册拦截器 Override public void addInterceptors(InterceptorRegistry registry) { // 注册 Sa-Token 拦截器校验规则为 StpUtil.checkLogin() 登录校验。 registry.addInterceptor(new SaInterceptor(handle - StpUtil.checkLogin())) .addPathPatterns(/**) .excludePathPatterns(/user/doLogin); } }以上代码注册了一个基于StpUtil.checkLogin()的登录校验拦截器并且排除了/user/doLogin接口用来开放登录。除了/user/doLogin以外的所有接口都需要登录才能访问。这里需要注意两点addPathPatterns(/**)表示拦截所有路由excludePathPatterns(/user/doLogin)是 Spring MVC 层面的路径排除两者共同作用才完成了全站拦截、登录接口放行SaInterceptor是新版本提供的综合拦截器自 1.31.0 起它默认自带注解鉴权能力并支持传入自定义认证函数auth。旧版本代码可参考其迁移示例本文不再展开。从 SaInterceptor.java 源码可以看到SaInterceptor内部维护了三个关键成员成员类型作用beforeAuthSaParamFunctionObject认证前置函数在注解鉴权之前执行isAnnotationboolean是否开启注解鉴权默认trueauthSaParamFunctionObject认证函数每次请求执行即构造时传入的 lambda每次请求到达时preHandle按如下顺序执行见 SaInterceptor.java执行beforeAuth前置函数若isAnnotation true且handler是HandlerMethod类型则执行注解鉴权执行auth路由拦截鉴权若捕获到StopMatchException则停止匹配、直接放行进入 Controller若捕获到BackResultException则向前端输出结果并返回false结束请求。三、校验函数详解从登录校验到模块化鉴权new SaInterceptor(handle - StpUtil.checkLogin())是最简单的写法代表只进行登录校验功能。我们可以往构造函数塞一个完整的 lambda 函数来定义详细的校验规则例如Configuration public class SaTokenConfigure implements WebMvcConfigurer { Override public void addInterceptors(InterceptorRegistry registry) { // 注册 Sa-Token 拦截器定义详细认证规则 registry.addInterceptor(new SaInterceptor(handler - { // 指定一条 match 规则 SaRouter .match(/**) // 拦截的 path 列表可以写多个 .notMatch(/user/doLogin) // 排除掉的 path 列表可以写多个 .check(r - StpUtil.checkLogin()); // 要执行的校验动作可以写完整的 lambda 表达式 // 根据路由划分模块不同模块不同鉴权 SaRouter.match(/user/**, r - StpUtil.checkPermission(user)); SaRouter.match(/admin/**, r - StpUtil.checkPermission(admin)); SaRouter.match(/goods/**, r - StpUtil.checkPermission(goods)); SaRouter.match(/orders/**, r - StpUtil.checkPermission(orders)); SaRouter.match(/notice/**, r - StpUtil.checkPermission(notice)); SaRouter.match(/comment/**, r - StpUtil.checkPermission(comment)); })).addPathPatterns(/**); } }SaRouter.match()匹配函数有两个参数参数一要匹配的 path 路由参数二要执行的校验函数。在校验函数内不只可以使用StpUtil.checkPermission(xxx)进行权限校验你还可以写任意代码例如Configuration public class SaTokenConfigure implements WebMvcConfigurer { // 注册 Sa-Token 的拦截器 Override public void addInterceptors(InterceptorRegistry registry) { // 注册路由拦截器自定义认证规则 registry.addInterceptor(new SaInterceptor(handler - { // 登录校验 -- 拦截所有路由并排除 /user/doLogin 用于开放登录 SaRouter.match(/**, /user/doLogin, r - StpUtil.checkLogin()); // 角色校验 -- 拦截以 admin 开头的路由必须具备 admin 角色或者 super-admin 角色才可以通过认证 SaRouter.match(/admin/**, r - StpUtil.checkRoleOr(admin, super-admin)); // 权限校验 -- 不同模块校验不同权限 SaRouter.match(/user/**, r - StpUtil.checkPermission(user)); SaRouter.match(/admin/**, r - StpUtil.checkPermission(admin)); SaRouter.match(/goods/**, r - StpUtil.checkPermission(goods)); SaRouter.match(/orders/**, r - StpUtil.checkPermission(orders)); SaRouter.match(/notice/**, r - StpUtil.checkPermission(notice)); SaRouter.match(/comment/**, r - StpUtil.checkPermission(comment)); // 甚至你可以随意的写一个打印语句 SaRouter.match(/**, r - System.out.println(----啦啦啦----)); // 连缀写法 SaRouter.match(/**).check(r - System.out.println(----啦啦啦----)); })).addPathPatterns(/**); } }官方 Demo SaTokenConfigure.java 中的写法与本例几乎一致对应的一组测试路由定义在 RouterCheckController.java 中如/user/info、/admin/info、/goods/info、/router/print等可直接启动 Demo 验证拦截效果。四、匹配特征详解path、请求方式、布尔条件与无限连缀除了上述示例的 path 路由匹配SaRouter还可以根据很多其它特征进行匹配。以下是所有可匹配的特征// 基础写法样例匹配一个 path执行一个校验函数 SaRouter.match(/user/**).check(r - StpUtil.checkLogin()); // 根据 path 路由匹配 ——— 支持写多个 path支持写 restful 风格路由 // 功能说明: 使用 /user , /goods 或者 /art/get 开头的任意路由都将进入 check 方法 SaRouter.match(/user/**, /goods/**, /art/get/{id}).check( /* 要执行的校验函数 */ ); // 根据 path 路由排除匹配 // 功能说明: 使用 .html , .css 或者 .js 结尾的任意路由都将跳过, 不会进入 check 方法 SaRouter.match(/**).notMatch(*.html, *.css, *.js).check( /* 要执行的校验函数 */ ); // 根据请求类型匹配 SaRouter.match(SaHttpMethod.GET).check( /* 要执行的校验函数 */ ); // 根据一个 boolean 条件进行匹配 SaRouter.match( StpUtil.isLogin() ).check( /* 要执行的校验函数 */ ); // 根据一个返回 boolean 结果的 lambda 表达式匹配 SaRouter.match( r - StpUtil.isLogin() ).check( /* 要执行的校验函数 */ ); // 多个条件一起使用 // 功能说明: 必须是 Get 请求 并且 请求路径以 /user/ 开头 SaRouter.match(SaHttpMethod.GET).match(/user/**).check( /* 要执行的校验函数 */ ); // 可以无限连缀下去 // 功能说明: 同时满足 Get 方式请求, 且路由以 /admin 开头, 路由中间带有 /send/ 字符串, 路由结尾不能是 .js 和 .css SaRouter .match(SaHttpMethod.GET) .match(/admin/**) .match(/**/send/**) .notMatch(/**/*.js) .notMatch(/**/*.css) // .... .check( /* 只有上述所有条件都匹配成功才会执行最后的 check 校验函数 */ );从源码角度理解这套链式匹配机制SaRouter是一个纯静态工具类所有match/notMatch方法都会new SaRouterStaff()开启一条新的匹配链见 SaRouter.javaSaRouterStaff内部用一个isHit布尔值记录当前匹配链是否命中只有在isHit true时才会继续执行后续的匹配与check一旦某个条件不满足isHit变为false后续判断全部短路跳过见 SaRouterStaff.javaSaRouter.match(/**).check(fun)这种一步到位写法实际等价于match(/**)之后紧跟check(fun)两步连缀。此外SaRouter还提供了不依赖当前请求上下文的底层匹配能力isMatch(pattern, path)、isMatch(patterns, path)以及针对当前请求的isMatchCurrURI(...)、isMatchCurrMethod(...)见 SaRouter.java供在自定义策略或过滤器场景下复用。请求类型匹配使用的枚举SaHttpMethod定义了GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE, CONNECT以及代表全部方式的ALL见 SaHttpMethod.java。五、提前退出匹配链stop() 与 back()使用SaRouter.stop()可以提前退出匹配链例registry.addInterceptor(new SaInterceptor(handler - { SaRouter.match(/**).check(r - System.out.println(进入1)); SaRouter.match(/**).check(r - System.out.println(进入2)).stop(); SaRouter.match(/**).check(r - System.out.println(进入3)); SaRouter.match(/**).check(r - System.out.println(进入4)); SaRouter.match(/**).check(r - System.out.println(进入5)); })).addPathPatterns(/**);如上示例代码运行至第 2 条匹配链时会在stop()函数处提前退出整个匹配函数从而忽略掉剩余的所有 match 匹配最终只打印进入1和进入2。除了stop()函数SaRouter还提供了back()函数用于停止匹配、结束执行、直接向前端返回结果// 执行 back 函数后将停止匹配也不会进入 Controller而是直接将 back 参数作为返回值输出到前端 SaRouter.match(/user/back).back(要返回到前端的内容);stop()与back()函数的区别在于SaRouter.stop()会停止匹配进入 ControllerSaRouter.back()会停止匹配直接返回结果到前端。从源码看两者都通过抛出框架内部异常实现stop()抛出StopMatchException见 StopMatchException.javaback(result)抛出携带返回内容的BackResultException见 SaRouter.java。这两类异常在SaInterceptor.preHandle中被分别捕获处理见 SaInterceptor.java。需要注意的是back()默认输出Content-Type为text/plain; charsetutf-8如需返回 JSON需在back前自行设置响应头为application/json例如SaHolder.getResponse().setHeader(Content-Type, application/json;charsetUTF-8)。六、使用 free 打开一个独立的作用域// 进入 free 独立作用域 SaRouter.match(/**).free(r - { SaRouter.match(/a/**).check(/* --- */); SaRouter.match(/b/**).check(/* --- */).stop(); SaRouter.match(/c/**).check(/* --- */); }); // 执行 stop() 函数跳出 free 后继续执行下面的 match 匹配 SaRouter.match(/**).check(/* --- */);free()的作用是打开一个独立的作用域使内部的stop()不再一次性跳出整个 Auth 函数而是仅仅跳出当前 free 作用域。因此上面代码中无论free内部是否触发stop()跳出后都会继续执行最后一条SaRouter.match(/**).check(...)。源码层面对此的处理非常清晰free()在内部捕获StopMatchException并静默吞掉从而保证异常不会继续向外抛出见 SaRouterStaff.java。七、使用 SaIgnore 忽略掉路由拦截校验某些接口虽然命中了拦截规则但出于特殊原因如内网回调用、公开查询等需要跳过校验此时可以使用SaIgnore注解1、先配置好了拦截规则Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new SaInterceptor(handler - { // 根据路由划分模块不同模块不同鉴权 SaRouter.match(/user/**, r - StpUtil.checkPermission(user)); SaRouter.match(/admin/**, r - StpUtil.checkPermission(admin)); SaRouter.match(/goods/**, r - StpUtil.checkPermission(goods)); // ... })).addPathPatterns(/**); }2、然后在 Controller 里又添加了忽略校验的注解SaIgnore RequestMapping(/user/getList) public SaResult getList() { System.out.println(------------ 访问进来方法); return SaResult.ok(); }请求将会跳过拦截器的校验直接进入 Controller 的方法中。[!WARNING| label:注意点] 注解SaIgnore的忽略效果只针对SaInterceptor拦截器 和 AOP 注解鉴权生效对自定义拦截器与过滤器不生效。从源码看SaIgnore可以标注在方法或类上Target({ ElementType.METHOD, ElementType.TYPE })其 Javadoc 中同样明确了这一生效范围见 SaIgnore.java。该注解由SaIgnoreHandler处理属于注解鉴权体系的一部分因此只有走注解鉴权链路的SaInterceptor与 AOP 注解鉴权才认识它。八、关闭注解校验与注册认证前置函数8.1 关闭注解校验SaInterceptor只要注册到项目中默认就会打开注解校验isAnnotation默认为true见 SaInterceptor.java。如果项目只使用路由拦截、不希望 Controller 上的SaCheckLogin等注解生效可以指定isAnnotation为 falseOverride public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor( new SaInterceptor(handle - { SaRouter.match(/**).check(r - StpUtil.checkLogin()); }).isAnnotation(false) // 指定关闭掉注解鉴权能力这样框架就只会做路由拦截校验了 ).addPathPatterns(/**); }8.2 注册认证前置函数 setBeforeAuth你也可以使用setBeforeAuth注册认证前置函数Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new SaInterceptor(handle - { System.out.println(1); }) .setBeforeAuth(handle - { System.out.println(2); }) ).addPathPatterns(/**); }如上代码先执行 2beforeAuth前置函数再执行注解鉴权再执行 1auth认证函数。如果beforeAuth里包含SaRouter.stop()将跳过后续的注解鉴权和 auth 认证环节。这一执行顺序同样可以在 SaInterceptor.java 的preHandle源码中得到印证。九、路由拦截鉴权 vs 全局过滤器除拦截器外Sa-Token 还提供了基于SaServletFilter的全局过滤器方案。两者的定位差异在于拦截器SaInterceptor校验失败后抛出异常由全局异常处理器ExceptionHandler或GlobalException统一接管便于与其它业务异常共用一套返回格式过滤器SaServletFilter校验失败不会进入全局异常组件而是进入setError指定的异常处理函数直接返回结果。官方 Demo SaTokenConfigure.java 中同时演示了两种注册方式读者可以对比学习。二者内部的路由匹配代码可以完全复用都是SaRouter链式写法差别仅在于结果处理出口。十、结语路由拦截鉴权是 Sa-Token 面向全局统一鉴权场景的核心解决方案。本文覆盖了从最简的登录校验拦截器到模块化权限划分、多特征匹配、提前退出stop/back、独立作用域free、注解忽略SaIgnore以及注解鉴权开关与前置函数等全部能力。实际落地时建议遵循以下组合策略用SaRouter.match(/**).notMatch(...).check(...)完成全站登录校验与开放路由放行按业务模块/user/**、/admin/**等继续追加权限校验链少数需要特例放行的接口用SaIgnore做细粒度豁免需要直接拦截返回如黑名单、风控提示时使用back()需要在free局部作用域内控制流程时使用stop()。相关源码与 Demo 路径核心实现见 SaRouter.java 与 SaRouterStaff.java拦截器实现见 SaInterceptor.java完整可运行示例见 SaTokenConfigure.java 与 RouterCheckController.java。【免费下载链接】Sa-Token✨ 开源、免费、一站式 Java 权限认证框架让鉴权变得简单、优雅—— 登录认证、权限认证、分布式 Session 会话、微服务网关鉴权、SSO 单点登录、OAuth2.0 统一认证、jwt 集成、API Key 秘钥授权、API 参数签名项目地址: https://gitcode.com/GitHub_Trending/sa/Sa-Token创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考