news 2026/9/20 1:25:25

Sa-Token 认证过了,Gateway 还拦请求?用 TaoToken 接 Codex 对着白名单查

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Sa-Token 认证过了,Gateway 还拦请求?用 TaoToken 接 Codex 对着白名单查

Sa-Token 认证过了,Gateway 还拦请求?用 TaoToken 接 Codex 对着白名单查

登录接口明明已经返回 token,Gateway 却继续对/order/**/user/**返回 401;或者网关看起来放行了,订单服务里的UserContextFilter还是读不到X-User-IdX-Tenant-Id,多租户查询直接查不到数据。这是企业 Java 网关排障里非常典型的一类问题。本文用 TaoToken 接 Codex,对着config.tomlapplication.ymlAuthGlobalFilter.javaUserContextFilter.java逐行查断点。TaoToken 只给 Codex 提供 Key 和 Base URL,不参与鉴权,也不替代 Gateway。动手前先打开官网 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 注册并创建 Key,再把 Key 和https://taotoken.net/api填进 Codex,让它按白名单、token 空分支、mutate 透传、下游上下文这几条线排查。

这里的核心目标不是重新设计认证体系,而是把“登录成功但请求仍被网关拦下”或“放行后下游拿不到身份”拆成可验证的断点。Sa-Token 负责登录态和权限,OAuth2 负责授权协议,SSO 负责多系统共享登录,多租户负责数据隔离,Gateway 负责统一入口和前置校验,自定义 Framework 负责把上下文统一封装。排障时要先盯住 Gateway 这一段,不要一上来就怀疑 Sa-Token 登录失败。

一、原问题与场景:登录成功,Gateway 仍拦或下游拿不到身份

典型链路是这样的:

浏览器/App -> Gateway -> IAM 认证中心 -> Sa-Token/OAuth2/SSO -> 业务服务 -> UserContextFilter/TenantContext

用户先请求/iam/auth/login,IAM 校验用户名密码、客户端信息,调用 Sa-Token 完成登录,返回 token。前端拿到 token 后,后续请求带:

Authorization: Bearer xxxxxx X-Tenant-Id: 1001

按预期,Gateway 应该放行该请求,并把用户身份和租户身份继续往下游透传。但线上常见现象是:

  1. 登录接口 200,业务接口 401。
  2. 白名单接口匿名能过,带 token 的业务接口反而被拦。
  3. 业务接口 200,但下游AuthContext.getUserId()为空。
  4. TenantContext.getTenantId()为空,数据权限和租户隔离失效。
  5. 网关日志显示放行,业务服务日志却显示没有收到X-User-IdX-UsernameX-Tenant-Id
  6. 本地 curl 正常,前端联调异常,最后发现是 Header 名称或大小写不一致。

这类问题通常不是单点故障,而是 Gateway 认证链路上的多个小配置对不上:

  • auth.white-list只写了/iam/auth/login,漏掉了/iam/auth/logout/iam/captcha/doc.html/v3/api-docs/**
  • AuthGlobalFilter.filter()里先判断Authorization是否为空,再判断白名单,导致白名单请求也被 401。
  • 白名单采用startsWith前缀匹配,路径边界没考虑清楚,误放行或漏放行。
  • mutate()之后只加了X-Request-From,没有把解析出来的X-User-IdX-UsernameX-Tenant-Id往下游传。
  • StripPrefix=1改变了转发路径,但 IAM 白名单、业务权限注解仍按带前缀路径配置。
  • 网关、IAM、下游服务各自解析 token,Header 规范不统一,排查时不知道信哪个。

Gateway 是门卫,IAM 才更像办证大厅。Gateway 可以做 token 前置校验、白名单放行、Header 透传,但真正的登录态和用户身份通常仍来自 IAM 或 Sa-Token 体系。排障时先把“谁拦的、在哪拦的、拦之前 path 是什么、Header 有没有变化”查清楚。

二、TaoToken 前置:Codex 的 config.toml 只配 Key 和 Base URL

这一篇里,TaoToken 的职责很窄:给 Codex 提供调用模型所需的 Key 和 Base URL,让 Codex 能读取项目文件并按断点清单做静态排查。它不参与 Sa-Token 登录,不参与 OAuth2 授权,不参与 Gateway 白名单判断,也不是 Gateway 的替代品。

先打开官网注册账号:

https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=

创建 Key 后,把 Key 写入环境变量。Base URL 使用:

https://taotoken.net/api

Codex 的配置文件一般在~/.codex/config.toml。可以按下面这样配置:

# ~/.codex/config.toml model = "gpt-5-codex" model_provider = "taotoken" [model_providers.taotoken] name = "TaoToken" base_url = "https://taotoken.net/api" env_key = "TAOTOKEN_API_KEY" wire_api = "chat"

如果你本机 Codex 版本要求使用 Responses API,可以把wire_api改为"responses",但base_url和 Key 不变。然后设置环境变量:

export TAOTOKEN_API_KEY=YOUR_API_KEY codex

Windows PowerShell 下可以这样:

$env:TAOTOKEN_API_KEY="YOUR_API_KEY" codex

配置完成后,可以让 Codex 按下面这个提示词检查项目:

请按以下顺序检查本项目 Gateway 认证链路: 1. 打开 application.yml,确认 routes 中 /iam/**、/order/** 的 StripPrefix=1 是否正确; 2. 打开 auth.white-list,确认 /iam/auth/login、/iam/auth/logout、/iam/captcha、/doc.html、/v3/api-docs/** 是否都放行; 3. 打开 AuthGlobalFilter.java,检查白名单判断是否在 Authorization 为空判断之前; 4. 检查 mutate 后是否写入 X-User-Id、X-Username、X-Tenant-Id,而不只是 X-Request-From; 5. 打开 UserContextFilter.java,确认能读取并写入 AuthContext 和 TenantContext; 6. 分别给出白名单请求和带 token 请求的 curl 验证命令。

这样做的意义是:把原本靠肉眼看配置的排障过程,变成按文件、按分支、按 Header 逐项核对的检查清单。Codex 只负责辅助查断点,最终放行逻辑仍然在 Gateway 和 IAM 里。

三、可复制配置:application.yml、AuthGlobalFilter.java、UserContextFilter.java

先看 Gateway 路由。原步骤保持不变:/iam/**转发到iam-service/order/**转发到order-service,并用StripPrefix=1去掉第一级路径。

spring: cloud: gateway: routes: - id: iam-route uri: lb://iam-service predicates: - Path=/iam/** filters: - StripPrefix=1 - id: order-route uri: lb://order-service predicates: - Path=/order/** filters: - StripPrefix=1 auth: white-list: - /iam/auth/login - /iam/auth/logout - /iam/captcha - /doc.html - /v3/api-docs/**

注意StripPrefix=1后,进入下游 IAM 的路径可能从/iam/auth/login变成/auth/login。所以 Gateway 白名单匹配的是进入网关时的原始 path,而 IAM 自己的白名单、权限注解、Controller 映射要按下游收到的 path 配置。这里一旦混淆,就会出现“网关放行但 IAM 继续 401”或“网关误拦登录接口”的情况。

再看AuthGlobalFilter.java。核心顺序是:先判断白名单,白名单命中直接放行;再判断Authorization是否为空;非空时构造新的请求,把X-Request-From、用户身份、租户身份往下游透传。

@Component public class AuthGlobalFilter implements GlobalFilter, Ordered { private static final List<String> WHITE_LIST = List.of( "/iam/auth/login", "/iam/auth/logout", "/iam/captcha", "/doc.html", "/v3/api-docs" ); @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { String path = exchange.getRequest().getURI().getPath(); // 先白名单放行,再判断 token 是否为空 if (WHITE_LIST.stream().anyMatch(path::startsWith)) { return chain.filter(exchange); } String token = exchange.getRequest().getHeaders().getFirst("Authorization"); if (token == null || token.isBlank()) { exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED); return exchange.getResponse().setComplete(); } // 真实项目里应从 token 解析或从 IAM 缓存查询 LoginUser String userId = "20001"; String username = "demo"; String tenantId = "1001"; ServerHttpRequest request = exchange.getRequest().mutate() .header("X-Request-From", "gateway") .header("X-User-Id", userId) .header("X-Username", username) .header("X-Tenant-Id", tenantId) .build(); return chain.filter(exchange.mutate().request(request).build()); } @Override public int getOrder() { return -100; } }

这里的userIdusernametenantId只是示例占位,真实项目里必须从 token 对应的登录缓存、IAM 或 Sa-Token 会话中获取。重点是:如果你想解决“下游拿不到身份”,mutate()里就不能只加X-Request-From,而要把X-User-IdX-UsernameX-Tenant-Id一起透传。

下游业务服务的UserContextFilter.java负责把这些 Header 写入线程上下文:

@Component public class UserContextFilter extends OncePerRequestFilter { @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { try { String userId = request.getHeader("X-User-Id"); String username = request.getHeader("X-Username"); String tenantId = request.getHeader("X-Tenant-Id"); if (userId != null && !userId.isBlank()) { LoginUser user = new LoginUser(); user.setUserId(Long.parseLong(userId)); user.setUsername(username); AuthContext.set(user); } if (tenantId != null && !tenantId.isBlank()) { TenantContext.setTenantId(Long.parseLong(tenantId)); } filterChain.doFilter(request, response); } finally { AuthContext.clear(); TenantContext.clear(); } } }

业务代码最终只读上下文:

Long userId = AuthContext.getUserId(); Long tenantId = TenantContext.getTenantId();

如果这里读不到,就不要再往业务逻辑里查,应该立刻回到 Gateway,看 Header 有没有在 mutate 阶段被丢掉。

四、验证请求与成功结果:白名单匿名通过,带 token 请求透传身份

配置改完后,让 Codex 各跑一次白名单请求和一次带 token 的业务请求。白名单请求用登录接口验证:

curl -i -X POST "http://localhost:8080/iam/auth/login" \ -H "Content-Type: application/json" \ -d '{"username":"demo","password":"123456","clientId":"web-client","clientSecret":"web-secret"}'

预期结果:

  • HTTP 状态为 200。
  • 返回体里有 token。
  • Gateway 日志显示命中auth.white-list
  • 不会出现 401。
  • 即使没有Authorization,该请求也应该匿名通过并到达 IAM。

带 token 的业务请求:

TOKEN="把上一步返回的 token 填这里" curl -i "http://localhost:8080/order/orders?page=1" \ -H "Authorization: Bearer ${TOKEN}" \ -H "X-Tenant-Id: 1001"

预期结果:

  • Gateway 不再返回 401。
  • 请求被转发到order-service
  • 下游UserContextFilter能读到X-User-IdX-UsernameX-Tenant-Id
  • AuthContext.getUserId()非空。
  • TenantContext.getTenantId()等于 1001。
  • 业务查询能按当前用户和当前租户返回数据。

可以在下游服务日志里加一行临时日志:

[UserContextFilter] userId=20001 username=demo tenantId=1001

如果这里打印为空,但 Gateway 日志显示已经放行,就重点查 mutate 后的 Header 是否真的写入,以及下游过滤器是否被注册到 Servlet 容器中。如果这里正常,但 Sa-Token 注解仍然报无权限,再查 IAM 或业务服务自己的权限配置。

最后回到 TaoToken 官网查看本次 Codex 调用记录是否成功:

https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=

这里确认的是 Key 和 Base URL 配置是否可用,不是 Gateway 的鉴权结果。Gateway 的结果仍以网关日志、下游日志和 curl 返回为准。

五、本篇常见错排查:白名单前缀、token 空分支、mutate 透传、StripPrefix

第一类错:白名单漏项或过期。
只写/iam/auth/login往往不够。登录之外,验证码、退出、文档、OpenAPI 路径也可能需要匿名访问。如果这些接口也被AuthGlobalFilter拦下,前端会表现为“登录页都打不开”或“登录后立刻退出”。

第二类错:白名单前缀匹配过宽。
path.startsWith("/iam/auth/login")会放行/iam/auth/login-admin/iam/auth/login-test这类路径。如果希望精确匹配,应该用equalsAntPathMatcher。如果业务确实需要前缀放行,就要明确路径边界,避免把受保护接口放进白名单。

第三类错:Authorization空分支提前命中。
AuthGlobalFilter.filter()中如果先取 token,再判断白名单,那么白名单请求也会因为没有Authorization被返回 401。正确顺序是先白名单放行,再判断 token。这个分支顺序一错,表现就是“登录接口也被拦”,非常容易误判成 IAM 故障。

第四类错:token 名称不一致。
Sa-Token 的token-name可能配置为Authorization,也可能配置为satokentoken。前端传的是Authorization: Bearer xxx,网关读的却是token,两边对不上就会一直 401。Bearer 前缀、大小写、CORS 预检OPTIONS请求也要一起检查。

第五类错:mutate 只加了X-Request-From
网关放行不代表下游能拿到身份。如果AuthGlobalFilter只做了:

.header("X-Request-From", "gateway")

却没有加X-User-IdX-UsernameX-Tenant-Id,那么UserContextFilter自然读不到。要解决“下游拿不到身份”,必须在网关阶段完成身份解析或从缓存查询,再写入这些 Header。

第六类错:StripPrefix=1与下游路径不一致。
/iam/auth/login经过StripPrefix=1后可能变成/auth/login。IAM 自己的白名单如果还写/iam/auth/login,就会漏掉。业务接口同理,/order/orders变成/orders后,Controller 映射、权限注解、网关日志里的 path 都要按实际转发路径核对。

第七类错:Header 规范不统一。
AuthorizationX-User-IdX-UsernameX-Tenant-IdX-Trace-Id如果没有统一约定,网关写一个名,下游读另一个名,排查会非常痛苦。建议在自定义 Framework 里统一常量,避免各服务手写字符串。

第八类错:ThreadLocal 没清理。
AuthContext.clear()TenantContext.clear()必须放在finally中。否则线程复用时会串用户、串租户,这类问题比单纯 401 更危险。

第九类错:网关和 IAM 重复认证。
Gateway 做前置校验,IAM 也做登录态校验,两边对 token 有效期、缓存、续期策略理解不一致时,会出现“网关说有效、IAM 说无效”或反过来。排障时要明确每一层分别负责什么。

六、语义一致 CTA:排障后把 API Key 与接入文档固定下来

这条故障线的排查顺序可以固定为:

  1. 看进入 Gateway 的原始 path。
  2. auth.white-list是否覆盖登录、验证码、退出、文档路径。
  3. AuthGlobalFilter.filter()是否先白名单、后 token 空判断。
  4. mutate()后是否写入X-User-IdX-UsernameX-Tenant-Id
  5. StripPrefix=1后下游真实 path。
  6. UserContextFilter是否写入AuthContextTenantContext
  7. 用白名单请求和带 token 请求各验证一次。

TaoToken 在这条链路里只负责给 Codex 提供 Key 和 Base URL,不参与 Sa-Token 鉴权,不参与 OAuth2 授权,也不替代 Gateway。把 Key 管理好之后,后续每次遇到网关白名单、Header 透传、多租户上下文问题,都可以让 Codex 按同一份清单查。

创建和管理 Key 可以从这里进入:

https://taotoken.net/console/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api-keys&utm_campaign=rewrite

Codex 接入、Base URL、环境变量和常见配置看接入文档:

https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite

如果团队会把 Codex 长期用于网关排障、微服务接入和日常编码,也可以再看 Coding Plan:

https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding-plan&utm_campaign=rewrite

但本篇的核心仍然是:Sa-Token 认证成功只是第一步,Gateway 的白名单、token 前置校验、AuthGlobalFilterStripPrefix、下游UserContextFilter和租户上下文必须逐段对齐,才算真正把请求放行并让身份传到业务服务。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/20 1:25:15

Linux服务器部署标准化手册:从分区规划到安全加固

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/20 1:24:24

THERM5门窗热工建模:ISO 15099合规性实战指南

简介&#xff1a;本资源是一份面向建筑节能设计人员、门窗研发工程师及高校暖通/建环专业师生的THERM5&#xff08;LBNL&#xff09;软件入门级教学课件&#xff0c;系统解决门窗框与玻璃边缘稳态传热建模难、操作门槛高、结果解读不直观等实际问题。课件以PPT形式呈现&#xf…

作者头像 李华
网站建设 2026/9/20 1:23:04

微型电动汽车前脸虚拟装配质量分析:从公差建模到Python实现

简介&#xff1a;《机械设计与制造》2016年刊发的论文《基于虚拟装配的微型电动汽车前脸装配质量分析》是一份PDF格式的学术文献&#xff0c;面向新能源汽车、汽车制造及机械装配领域的工程师、科研人员和研究生&#xff0c;聚焦微型电动汽车前脸装配偏差控制与公差优化问题。该…

作者头像 李华