首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >还在用 @PreAuthorize?聊聊我切换到 Sa-Token 路由拦截后的真实体感

还在用 @PreAuthorize?聊聊我切换到 Sa-Token 路由拦截后的真实体感

作者头像
程序员NEO
发布2026-04-29 19:36:28
发布2026-04-29 19:36:28
1140
举报
你有没有经历过这种场景:

项目初期,接口不多,每个 Controller 方法上加个 @SaCheckLogin 注解,清清爽爽。

但随着业务膨胀,接口从 20 个变成 200 个,你发现自己在做一件极其机械的事情 —— 每个方法上都要贴注解,漏一个就是线上事故。

我自己就干过这种蠢事。有一次新同事加了个查询接口,忘了贴注解,结果裸奔了两周才被发现。😅

后来我把整套鉴权逻辑切换成了 Sa-Token 的路由拦截模式,代码量直接砍了一大截,而且再也不用担心"漏注解"这种低级问题了。

这篇文章,咱们就把这套路由拦截鉴权从头到尾拆清楚。


先搞清楚:路由拦截鉴权到底在干啥

思路其实很简单 —— 把鉴权逻辑从每个接口上抽出来,统一放到拦截器里做前置校验

就像小区门口的保安,不管你去几号楼,先在大门口查一遍你的门禁卡。

这样做的好处非常明显:

  • 不用每个接口都写鉴权逻辑,新接口默认被拦截
  • 漏鉴权的概率降到最低,因为默认就是"全拦"
  • 权限规则集中管理,改一处就够了

1、最基础的写法:注册路由拦截器

SpringBoot2.0 为例,新建一个配置类就行:

代码语言:javascript
复制
@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"); 
    }
}

这段代码做了三件事:

  • addPathPatterns("/**") —— 拦截所有路由
  • excludePathPatterns("/user/doLogin") —— 放行登录接口
  • StpUtil.checkLogin() —— 未登录直接打回去

说白了就是:除了登录接口,其他全部需要先登录

⚠️ 这里有个细节容易踩坑:excludePathPatterns 的路径必须和你 Controller 里的 @RequestMapping 完全一致。我之前就因为多写了一个斜杠,排除没生效,debug 了好一会儿。

SaInterceptor 是新版本提供的拦截器,旧版本需要迁移,参考 代码迁移示例[1]。


2、进阶玩法:自定义校验规则

实际项目中,只做登录校验肯定是不够的

比如我手上的项目,后台管理系统有这么几个模块:用户管理、商品管理、订单管理、公告管理……不同角色能访问的模块完全不同。

这时候就需要在拦截器里写更细粒度的校验逻辑:

代码语言:javascript
复制
@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 路由
  • 参数二:匹配成功后要执行的校验函数

这里有个我当时纠结过的地方 —— 校验函数里到底能写什么?

答案是:随便写,它就是个普通的 lambda。权限校验、角色校验、甚至打印日志都行:

代码语言:javascript
复制
@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("/**");
    }
}

说实话,这种"一个配置类管所有鉴权"的感觉,比在每个 Controller 上贴注解舒服太多了。


3、匹配特征详解:不止能匹配路径

很多人以为 SaRouter 只能按路径匹配,其实它支持的匹配维度比你想象的多:

代码语言:javascript
复制
// 基础写法样例:匹配一个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校验函数 */ );

我在项目里用得最多的是 路径匹配 + 排除静态资源 这个组合。因为前后端没有完全分离的老项目,静态资源请求也会走拦截器,不排除的话页面直接白屏。

👇 这个写法基本是我每个项目的标配:

代码语言:javascript
复制
SaRouter.match("/**").notMatch("*.html", "*.css", "*.js").check(r -> StpUtil.checkLogin());

4、提前退出匹配链:stop() 和 back()

当你写了很多条 match 规则的时候,有时候想 在某个节点提前结束整个匹配流程

SaRouter.stop() 就是干这个的:

代码语言:javascript
复制
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()直接跳出整个匹配函数,3、4、5 全部被忽略。但请求还是会正常进入 Controller。

还有一个 back() 函数,更狠 —— 连 Controller 都不进了,直接返回结果给前端

代码语言:javascript
复制
// 执行back函数后将停止匹配,也不会进入Controller,而是直接将 back参数 作为返回值输出到前端
SaRouter.match("/user/back").back("要返回到前端的内容");

💡 划重点,这两个的区别:

  • stop() —— 停止匹配,但 继续进入 Controller
  • back() —— 停止匹配,直接把结果甩给前端,Controller 都不走

我在项目里用 back() 做过一个简单的维护模式开关:系统维护时直接在拦截器里 back("系统维护中"),连 Controller 都不用改。虽然不算优雅,但胜在简单粗暴,上线快。


5、free 独立作用域:解决 stop() 误伤问题

stop() 有个问题 —— 它会跳出整个认证函数

如果你只想跳出一组规则,而不影响后面的规则怎么办?

free() 就是干这个的,它会开辟一个 独立作用域,内部的 stop() 只在这个作用域里生效:

代码语言:javascript
复制
// 进入 free 独立作用域 
SaRouter.match("/**").free(r -> {
    SaRouter.match("/a/**").check(/* --- */);
    SaRouter.match("/b/**").check(/* --- */).stop();
    SaRouter.match("/c/**").check(/* --- */);
});
// 执行 stop() 函数跳出 free 后继续执行下面的 match 匹配 
SaRouter.match("/**").check(/* --- */);

上面代码里,/b/**stop() 只会跳出 free 作用域,外面的 match 不受影响,照常执行

说实话这个功能我用得不多,但在规则特别复杂的大型项目里,确实能避免 stop() 误伤的问题。


6、@SaIgnore:给个别接口开后门

有时候你配好了全局拦截,但个别接口就是需要放行。比如健康检查接口、第三方回调接口。

这时候在 excludePathPatterns 里加太多路径也不现实,@SaIgnore 注解更干净

先确保你有全局拦截配置:

代码语言:javascript
复制
@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("/**");
}

然后在需要放行的接口上贴注解:

代码语言:javascript
复制
@SaIgnore
@RequestMapping("/user/getList")
public SaResult getList() {
    System.out.println("------------ 访问进来方法"); 
    return SaResult.ok(); 
}

贴了 @SaIgnore,这个接口就会 跳过拦截器校验,直接进入方法体

⚠️ 但这里有个大坑要注意:

@SaIgnore 只对 SaInterceptor 拦截器AOP 注解鉴权 生效。如果你项目里还有自定义拦截器或者 Filter,@SaIgnore 管不到它们。

我之前就因为这个问题排查了半天 —— 以为加了 @SaIgnore 就万事大吉了,结果请求还是被另一个自定义 Filter 拦住了。


7、关闭注解校验 & 认证前置函数

SaInterceptor 注册后,默认会自动开启注解校验(就是 @SaCheckLogin@SaCheckRole 这些注解会自动生效)。

如果你只想用路由拦截,不想要注解校验,可以手动关掉:

代码语言:javascript
复制
@Override
public void addInterceptors(InterceptorRegistry registry) {
    registry.addInterceptor(
        new SaInterceptor(handle -> {
            SaRouter.match("/**").check(r -> StpUtil.checkLogin());
        }).isAnnotation(false)  // 指定关闭掉注解鉴权能力,这样框架就只会做路由拦截校验了 
    ).addPathPatterns("/**");
}

另外还有一个 setBeforeAuth,可以注册 认证前置函数

代码语言:javascript
复制
@Override
public void addInterceptors(InterceptorRegistry registry) {
    registry.addInterceptor(new SaInterceptor(handle -> {
        System.out.println(1);
    })
    .setBeforeAuth(handle -> {
        System.out.println(2);
    })
    ).addPathPatterns("/**");
}

执行顺序是:先 beforeAuth → 再注解鉴权 → 最后 auth 函数

如果 beforeAuth 里调了 SaRouter.stop(),后面的注解鉴权和 auth 认证 全部跳过

这个功能在做灰度发布或者临时绕过鉴权调试的时候挺好用的。


写在最后

鉴权这件事,能用机制保障的,就别靠人肉记忆。

路由拦截的核心价值不是"少写几行代码",而是 把鉴权从"每个开发者都要记住"变成"框架默认就有"

一句话总结我的实践经验:

全局拦截兜底 + @SaIgnore 开白名单 + SaRouter 做细粒度控制,这三板斧基本能覆盖 90% 的鉴权场景。


本章源码(GitHub):Sa-Token 路由拦截示例 —— sa-token-demo-interceptor[2]

相关文章推荐

引用链接

[1] 代码迁移示例: https://blog.csdn.net/shengzhang_/article/details/126458949 [2] 本章源码(GitHub):Sa-Token 路由拦截示例 —— sa-token-demo-interceptor: https://github.com/BNTang/Sa-Token-Demo/tree/main/sa-token-demo-interceptor

如果这篇文章帮到了你,不妨点个分享给同样需要的朋友吧! 你的每一次支持,都是我持续创作的动力!💪

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2026-02-09,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 程序员NEO 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 先搞清楚:路由拦截鉴权到底在干啥
  • 1、最基础的写法:注册路由拦截器
  • 2、进阶玩法:自定义校验规则
  • 3、匹配特征详解:不止能匹配路径
  • 4、提前退出匹配链:stop() 和 back()
  • 5、free 独立作用域:解决 stop() 误伤问题
  • 6、@SaIgnore:给个别接口开后门
  • 7、关闭注解校验 & 认证前置函数
  • 写在最后
    • 引用链接
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档