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

资讯详情

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

Sa-Token 动态路由鉴权:把路由拦截鉴权动态化,实现登录与权限规则的运行时配置

Sa-Token 动态路由鉴权:把路由拦截鉴权动态化,实现登录与权限规则的运行时配置 Sa-Token 动态路由鉴权把路由拦截鉴权动态化实现登录与权限规则的运行时配置【免费下载链接】Sa-Token✨ 开源、免费、一站式 Java 权限认证框架让鉴权变得简单、优雅—— 登录认证、权限认证、分布式 Session 会话、微服务网关鉴权、SSO 单点登录、OAuth2.0 统一认证、jwt 集成、API Key 秘钥授权、API 参数签名项目地址: https://gitcode.com/GitHub_Trending/sa/Sa-Token本篇技术指南聚焦 Sa-Token 的一个进阶用法如何把路由拦截鉴权SaRouter从启动时写死的硬编码规则改造为运行时动态加载的规则从而支持从数据库、配置中心等来源读取排除路径与权限校验规则。读完本文你将掌握动态登录校验、动态权限规则两种落地写法的完整代码并理解为什么把动态查询放在 lambda 之外会导致失效——这一行为可以直接从SaInterceptor的源码调用链中得到印证。问题背景硬编码路由鉴权的局限Sa-Token 官方文档给出的路由拦截鉴权示例通常形如sa-token-demo-case示例工程中的 SaTokenConfigure.java// 指定一条 match 规则 SaRouter .match(/user/**) // 拦截的 path 列表可以写多个 .notMatch(/user/doLogin, /user/doLogin2) // 排除掉的 path 列表可以写多个 .check(r - StpUtil.checkLogin()); // 要执行的校验动作可以写完整的 lambda 表达式 // 权限校验 -- 不同模块认证不同权限 SaRouter.match(/admin/**, r - StpUtil.checkPermission(admin)); SaRouter.match(/goods/**, r - StpUtil.checkPermission(goods));这种写法简单直接但拦截哪些路径、放行哪些路径、每个模块校验什么权限全部在代码里写死。实际项目中这些规则往往需要由运营后台维护、存放在数据库里并且希望修改后无需重新部署即可生效。框架的示例是硬代码写死的不过稍微做一下更改你就可以让它动态化——这正是本文要解决的核心问题。方案一动态登录校验放行路径动态化核心思路拦截器仍拦截/**但哪些 path 可以忽略鉴权改为每次请求时动态查询。参考如下Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new SaInterceptor(handle - { SaRouter .match(/**) .notMatch(excludePaths()) .check(r - StpUtil.checkLogin()); })).addPathPatterns(/**); } // 动态获取哪些 path 可以忽略鉴权 public ListString excludePaths() { // 此处仅为示例实际项目你可以写任意代码来查询这些path return Arrays.asList(/path1, /path2, /path3); }关键细节在于excludePaths()的调用发生在SaInterceptor构造时传入的 lambda即认证函数auth内部。从 SaInterceptor 源码可以看到preHandle在每个请求处理前都会执行auth.run(handler)// sa-token-spring-boot-starter 中的 SaInterceptor#preHandle节选 public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { try { // 前置函数在注解鉴权之前执行 beforeAuth.run(handler); // 这里必须确保 handler 是 HandlerMethod 类型时才能进行注解鉴权 if(isAnnotation handler instanceof HandlerMethod) { Method method ((HandlerMethod) handler).getMethod(); SaAnnotationStrategy.instance.checkMethodAnnotation.accept(method); } // Auth 路由拦截鉴权校验 auth.run(handler); } catch (StopMatchException e) { // StopMatchException 异常代表停止匹配进入Controller } catch (BackResultException e) { // BackResultException 异常代表停止匹配向前端输出结果 // ... } // 通过验证 return true; }也就是说new SaInterceptor(handle - {...})中 lambda 的构造时机只在注册拦截器应用启动时发生一次但 lambda体的执行时机是每个请求都会执行。因此把excludePaths()放进 lambda 内部就等于每次请求都会重新执行一次动态查询实现动态化放行。方案二动态权限规则路径-权限映射表动态化如果不仅仅是登录校验还需要鉴权那也很简单。把路径 → 所需权限做成一张运行时可变的映射表在认证函数中遍历执行Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new SaInterceptor(handle - { // 遍历校验规则依次鉴权 MapString, String rules getAuthRules(); for (String path : rules.keySet()) { SaRouter.match(path, () - StpUtil.checkPermission(rules.get(path))); } })).addPathPatterns(/**); } // 动态获取鉴权规则 public MapString, String getAuthRules() { // key 代表要拦截的 pathvalue 代表需要校验的权限 MapString, String authMap new LinkedHashMap(); authMap.put(/user/**, user); authMap.put(/admin/**, admin); authMap.put(/article/**, article); // 更多规则 ... return authMap; }说明两点规则语义key 是 Ant 风格的路由匹配符如/user/**value 是对应的权限标识。命中某条路径规则时执行StpUtil.checkPermission(value)完成权限校验不命中任何规则的路径则不做校验。LinkedHashMap的用意保证规则按插入顺序依次匹配行为可预期。实际项目中getAuthRules()可替换为任意动态数据源查询数据库、配置中心、本地缓存等代码结构不变。这种一个循环 多组SaRouter.match(pattern, fun)的写法与 SaRouter 提供的match(String pattern, SaFunction fun)重载内部等价于match(pattern).check(fun)完全兼容。错误写法剖析动态查询放在 lambda 之外一个高频踩坑写法如下Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new SaInterceptor(handle - { StpUtil.checkLogin(); })) .addPathPatterns(/**) .excludePathPatterns(excludePaths()); } // 动态获取哪些 path 可以忽略鉴权 public ListString excludePaths() { // 此处仅为示例实际项目你可以写任意代码来查询这些path return Arrays.asList(/path1, /path2, /path3); }错误点因为 lambda 表达式之外的代码只会在启动时执行一次所以excludePaths()方法是无法做到动态化读取的。若要在项目运行时动态读写必须把调用excludePaths()的时机放在 lambda 里。原因对照上面SaInterceptor的源码一目了然registry.addInterceptor(...).addPathPatterns(/**).excludePathPatterns(excludePaths())这一整条链式调用发生在addInterceptors方法执行期间——即应用启动、拦截器注册阶段。excludePathPatterns(excludePaths())传入的是一个已经求值完毕的静态 List此后数据库里怎么改都不会影响它。只有 lambda 体内部的代码才会随着auth.run(handler)在每个请求到来时重新执行。因此判断标准很简单凡是希望运行时生效的查询逻辑必须写在SaInterceptor的认证函数 lambda 内部写在 lambda 外部的任何代码生命周期都只有一次——启动时。底层机制路由匹配是怎么执行的理解动态化为什么可行还需要看SaRouter底层的匹配机制。match / notMatch / check 的短路求值链式调用的每一步都会落到 SaRouterStaff 上其内部用isHit命中标记实现短路逻辑public SaRouterStaff match(String... patterns) { if(isHit) { isHit SaRouter.isMatchCurrURI(patterns); } return this; } public SaRouterStaff notMatch(String... patterns) { if(isHit) { isHit !SaRouter.isMatchCurrURI(patterns); } return this; } public SaRouterStaff check(SaFunction fun) { if(isHit) { fun.run(); } return this; }isMatchCurrURI(patterns)取的是当前请求的 URISaHolder.getRequest().getRequestPath()所以每次请求进来时匹配用的 path 都是实时值只要前一步isHit已经为 false后续步骤直接跳过——这就是命中规则才执行check的实现方式方案一中的.match(/**).notMatch(excludePaths()).check(...)其excludePaths()正是在notMatch被调用时即每次请求的认证函数执行中求值。路由匹配算法是可插拔策略SaRouter.isMatch(pattern, path)最终委托给 SaStrategy 的routeMatcher策略/** * 路由匹配策略 */ public SaRouteMatchFunction routeMatcher (pattern, path) - { throw new NotImplException(未实现具体路由匹配策略).setCode(SaErrorCode.CODE_12401); };核心框架默认没有实现该策略由各接入环境的 starter/plugin 在初始化时注入具体实现。以 Spring Boot 为例SaTokenContextRegister 在构造阶段完成注册public SaTokenContextRegister() { // 重写路由匹配算法 SaStrategy.instance.routeMatcher (pattern, path) - { return SaPathPatternParserUtil.match(pattern, path); }; // 重写 SaRequest / SaResponse / SaStorage 创建策略 ... }因此在 Spring Boot以及 Solon、JFinal、Reactor 等环境下SaRouter.match使用的匹配符语法由对应 starter 的routeMatcher决定Spring Boot 环境为 Ant 风格路径匹配。这也意味着动态规则里写入的路径格式如/user/**必须与当前环境routeMatcher支持的语法一致否则规则会静地匹配不上。匹配失败与提前退出的异常语义方案二的循环里每条规则独立成链SaRouter.match(path, () - ...)互不干扰。而SaRouterStaff还提供两种提前退出手段public SaRouterStaff stop() { if(isHit) { throw new StopMatchException(); } return this; }stop()抛出StopMatchException被SaInterceptor.preHandle捕获后停止匹配进入 Controller常用于登录即放行类场景back(result)抛出BackResultException被捕获后直接向前端输出结果并中断后续处理free(fun)则允许在自由匹配块中局部捕获StopMatchException仅跳出代码块而不跳出整个认证函数。这些机制在编写动态规则尤其是需要自定义响应时值得了解。落地建议与注意事项结合文档示例与源码行为实际接入动态规则时建议关注以下几点查询频率动态查询发生在每次请求的认证函数中若excludePaths()/getAuthRules()直接查数据库会引入每请求一次的查询开销。常见做法是在动态数据源与请求路径之间加一层本地缓存定时刷新或基于版本号失效本文档示例中的方法体即预留了这个扩展位置——实际项目你可以写任意代码来查询这些 path。规则格式校验动态规则来自外部存储时建议入库前校验路径格式合法性是否符合当前环境routeMatcher的匹配语法避免非法通配符导致规则失效或行为异常。动态化只改变规则来源不改变执行位置无论规则从哪来鉴权执行仍然发生在拦截器/过滤器阶段的每次请求处理中SaInterceptor对StopMatchException/BackResultException的处理逻辑见 SaInterceptor保持不变。与注解鉴权正交SaInterceptor默认isAnnotation true路由拦截鉴权与注解鉴权在同一preHandle中先后执行二者互不影响动态路由规则可与SaCheckLogin等注解共存。小结动态化的本质把规则查询从拦截器注册期启动时一次挪到认证函数 lambda 内部每次请求执行这是由SaInterceptor.preHandle中auth.run(handler)的调用时机决定的登录校验动态化SaRouter.match(/**).notMatch(excludePaths()).check(r - StpUtil.checkLogin())excludePaths()在 lambda 内动态求值权限规则动态化维护路径 → 权限标识映射表在 lambda 内遍历执行SaRouter.match(path, () - StpUtil.checkPermission(rule))典型错误在 lambda 之外调用.excludePathPatterns(excludePaths())因启动时只求值一次而永远无法动态底层支撑SaRouter/SaRouterStaff的isHit短路匹配、可插拔的SaStrategy.routeMatcher策略由 Spring Boot 等 starter 注入具体实现共同保证了每个请求、按当前实时规则完成路由鉴权。【免费下载链接】Sa-Token✨ 开源、免费、一站式 Java 权限认证框架让鉴权变得简单、优雅—— 登录认证、权限认证、分布式 Session 会话、微服务网关鉴权、SSO 单点登录、OAuth2.0 统一认证、jwt 集成、API Key 秘钥授权、API 参数签名项目地址: https://gitcode.com/GitHub_Trending/sa/Sa-Token创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表