Sa-Token 反向代理 URI 丢失问题排查与解决:`getUrl()` 原理及 curr-domain 配置实战
发布时间:2026/9/14 0:18:43 锦皓数字建站
` 原理及 curr-domain 配置实战`)
Sa-Token 反向代理 URI 丢失问题排查与解决getUrl()原理及 curr-domain 配置实战【免费下载链接】Sa-Token✨ 开源、免费、一站式 Java 权限认证框架让鉴权变得简单、优雅—— 登录认证、权限认证、分布式 Session 会话、微服务网关鉴权、SSO 单点登录、OAuth2.0 统一认证、jwt 集成、API Key 秘钥授权、API 参数签名项目地址: https://gitcode.com/GitHub_Trending/sa/Sa-Token导读在 Java Web 项目中使用 Nginx 反向代理后SaHolder.getRequest().getUrl()获取到的地址常常与用户浏览器中实际访问的地址不一致域名、端口或路径前缀丢失进而导致 Sa-Token 的 SSO 单点登录等依赖真实访问地址的功能异常。本文以 Sa-Token 框架源码为线索剖析getUrl()的底层实现原理并给出两种官方推荐解决方案Nginx 追加 header 重写 SaRequest 创建策略与配置sa-token.curr-domain读完即可在真实项目中落地修复。一、问题背景为什么反向代理会弄丢URI1.1getUrl()的底层实现Sa-Token 通过SaHolder.getRequest()获取当前请求的包装对象其中getUrl()方法用于获取当前程序所在外网的访问地址不带 query 参数。从源码接口定义看// cn.dev33.satoken.context.model.SaRequest /** * 返回当前请求的url不带query参数例http://xxx.com/test */ String getUrl();接口定义见 SaRequest.java。在 Servlet 环境下其默认实现位于 SaRequestForServlet.java代码如下public String getUrl() { String currDomain SaManager.getConfig().getCurrDomain(); if( ! SaFoxUtil.isEmpty(currDomain)) { return currDomain this.getRequestPath(); } return request.getRequestURL().toString(); }可以看到默认逻辑直接调用 Servlet 原生的request.getRequestURL().toString()。也就是说Sa-Token 的getUrl()最终依赖 Servlet 容器提供的请求信息。1.2 依赖getUrl()的核心模块框架内很多模块都用到了这个能力最典型的就是SSO 单点登录客户端。在 SaSsoClientProcessor.java 中构建 SSO 认证中转地址时// 获取当前项目的 sso 登录中转页地址形如http://sso-client.com/sso/login // 全局配置了就是用全局的否则使用当前请求的地址 String currSsoLoginUrl cfg.getCurrSsoLogin(); if(SaFoxUtil.isEmpty(currSsoLoginUrl)) { currSsoLoginUrl SaHolder.getRequest().getUrl(); } // 构建最终授权地址 url形如http://sso-server.com/sso/auth?redirectUrlhttp://sso-client.com/sso/login?backhttp://sso-client.com String serverAuthUrl ssoClientTemplate.buildServerAuthUrl(currSsoLoginUrl, back);此处若getUrl()拿不到正确的外网地址SSO 客户端回调地址就会出错导致整个单点登录流程无法闭环。此外同一文件第 331 行 在拼接单点登出回调地址时也使用了getUrl()。1.3 复现问题我们可以编写一个简单的接口来验证当前环境下getUrl()的返回结果// 显示当前程序所在外网的访问地址 RequestMapping(test) public String test() { return 您访问的是 SaHolder.getRequest().getUrl(); }从浏览器直接访问此接口正常情况下返回的是真实外网地址形如您访问的是http://local.dev33.cn:8902/api/test然而当部署时使用 Nginx 做了一层反向代理例如将http://local.dev33.cn:8902/api代理到内网服务后由于Nginx 转发给后端服务的是内部请求地址Servlet 容器解析出的requestURL可能是内网主机、默认端口或去掉了上下文路径的地址最终结果就和我们预想的出现偏差返回的往往是http://内网IP:端口/api/test之类的内部地址甚至丢失了api路径前缀。需要特别说明不仅是 Nginx所有包含路由转发的地方都有可能导致上述丢失 uri 的现象例如网关转发、二级域名分发、负载均衡等。它们的共性是Servlet 容器拿到的原始请求信息不再等价于浏览器端的访问信息。二、解决思路从自动识别改为手动获取既然程序无法在反向代理场景下自动识别外网地址官方给出的解决思路是放弃自动推导改为手动注入真实外网地址。Sa-Token 为此提供了两种官方方案可根据项目实际情况选用。三、方案一Nginx 转发时追加 header 参数该方案分两步Nginx 侧透传外网地址程序侧重写SaRequest创建策略两步必须同步存在原因见下文安全警告。3.1 第一步Nginx 代理转发处增加 header在 Nginx 的location代理配置中增加一行proxy_set_header Public-Network-URL http://$http_host$request_uri;其原理是$http_host是 Nginx 从请求头中解析出的 Host即浏览器访问时的域名与端口$request_uri是浏览器请求的完整原始 URI包含 query 参数两者拼接即可还原用户真正访问的外网地址并通过自定义请求头Public-Network-URL传递给后端程序。3.2 第二步程序中重写 SaRequest 创建策略在 Spring Boot 项目中新建配置类通过SaStrategy.instance.setCreateSaRequest(...)重写框架创建SaRequest的策略Configuration public class SaTokenConfigure { PostConstruct public void configSaStrategy() { SaStrategy.instance.setCreateSaRequest(source - { HttpServletRequest request (HttpServletRequest) source; return new SaRequestForServlet(request) { Override public String getUrl() { String publicUrl request.getHeader(Public-Network-URL); if (publicUrl ! null) { return publicUrl; } return super.getUrl(); } }; }); } }代码要点解读SaStrategy是 Sa-Token 的策略对象统一定义了框架内的一些关键性逻辑算法方便开发者按需重写。其中createSaRequest策略负责将底层源对象这里是 Servlet 的HttpServletRequest包装为框架统一的SaRequest其默认实现会抛出NotImplException见 SaStrategy.java在 Servlet 集成环境中由 starter 提供默认包装实现。这里通过匿名子类覆写getUrl()优先读取 Nginx 传入的Public-Network-URL请求头若该请求头存在则直接返回否则回退到super.getUrl()默认逻辑。source是Object类型因此需要先强转为HttpServletRequest。setCreateSaRequest采用链式风格赋值见 SaStrategy.java返回SaStrategy自身便于集中配置。其它逻辑保持不变框架即可正确获取外网 uri 地址。3.3 风险警告步骤一与步骤二需要同步存在如果只做第二步程序读取 header而不在 Nginx 中统一注入该 header那么任意前端请求都可以假传Public-Network-URL请求头从而伪造自己的访问地址在 SSO 等对地址敏感的流程中可能引发安全风险。同理只配 Nginx 不做程序侧读取则配置无效。因此header 注入必须由可信的网关层Nginx统一完成且程序侧逻辑必须与之一一对应。四、方案二直接在 yml 中配置当前项目的网络访问地址如果项目的外网访问地址是固定不变的不涉及多域名、多环境动态切换官方提供了更简单直接的方案在application.yml中配置sa-token.curr-domain。4.1 配置示例yaml风格sa-token: # 配置当前项目的网络访问地址 curr-domain: http://local.dev33.cn:8902/apiproperties风格# 配置当前项目的网络访问地址 sa-token.curr-domainhttp://local.dev33.cn:8902/api配置完成后框架内部所有依赖getUrl()的逻辑都会以http://local.dev33.cn:8902/api作为基准地址即可避免路由转发过程中丢失 uri 的问题。4.2 配置项源码解析curr-domain是SaTokenConfig中的核心配置字段之一源码定义如下见 SaTokenConfig.java/** * 配置当前项目的网络访问地址 */ private String currDomain;其在getUrl()中的实际生效逻辑见 SaRequestForServlet.java为String currDomain SaManager.getConfig().getCurrDomain(); if( ! SaFoxUtil.isEmpty(currDomain)) { return currDomain this.getRequestPath(); }即只要currDomain配置非空getUrl()就返回currDomain getRequestPath()其中getRequestPath()取自request.getRequestURI()并去除应用上下文前缀见 SaRequestForServlet.java。因此使用本方案时有两点需要注意curr-domain必须包含协议、域名、端口和上下文路径若外网访问存在额外路径前缀例如http://local.dev33.cn:8902/api由于返回结果是currDomain requestPathrequestPath来自 Servlet 容器的 URI 解析若反向代理同时改变了 URI 本身而非仅改变 host仍需结合 Nginx 配置保证requestURI的路径部分与外部访问路径一致。4.3 与方案一的取舍对比维度方案一header 策略重写方案二curr-domain 配置适用场景外网地址不固定、多域名/多环境动态切换外网访问地址固定不变配置复杂度需要同时改 Nginx 与 Java 代码仅需一行 yml 配置动态性每次请求从 header 实时取地址天然适配多域名静态地址域名变更需改配置安全要求必须保证 Nginx 统一注入 header防止前端伪造无额外安全注意点侵入性重写全局SaRequest创建策略影响面较大零代码侵入仅配置文件实际项目中若仅需在 SSO 场景下固定外网地址优先选择方案二若存在多套域名或复杂网关环境可选择方案一并务必遵守其两步同步的安全约束。五、补充getUrl()的正确使用姿势最后补充两个与本文主题相关的使用细节getUrl()不带 query 参数接口注释明确说明返回的是形如http://xxx.com/test的地址不含?xxxyyy。如果业务上需要完整 URI可在重写策略时自行拼接request.getQueryString()。Mock 环境同样可用在单元测试场景下SaRequestForMock.java 提供了可手动赋值的url字段测试时可以直接注入期望地址便于验证 SSO 等依赖getUrl()的流程。结语反向代理导致 URI 丢失是 Web 部署中的高频问题理解SaHolder.getRequest().getUrl()基于request.getRequestURL()的默认实现后就能明白其失效根因。Sa-Token 给出的两种官方方案——Nginx 透传 header 并重写SaStrategy.instance.setCreateSaRequest策略、或直接配置sa-token.curr-domain——分别覆盖了动态多域名与固定地址两类场景开发者可按需选用即可让 SSO 单点登录等依赖真实访问地址的模块在网关/代理环境下稳定工作。【免费下载链接】Sa-Token✨ 开源、免费、一站式 Java 权限认证框架让鉴权变得简单、优雅—— 登录认证、权限认证、分布式 Session 会话、微服务网关鉴权、SSO 单点登录、OAuth2.0 统一认证、jwt 集成、API Key 秘钥授权、API 参数签名项目地址: https://gitcode.com/GitHub_Trending/sa/Sa-Token创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。