尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

微信云托管迁移后“内容含违规信息”:一次从 SSLHandshakeException 到成功调用的完整排查

微信云托管迁移后“内容含违规信息”:一次从 SSLHandshakeException 到成功调用的完整排查 目录一、现象每发一条消息都弹内容含违规信息二、初判全报违规 ≈ 检测服务全挂三、弯路我以为是 DNS 解析问题还尝试固定微信 API 的解析地址四、转折用户一句话点醒五、核心认知旁挂组件改写了调用规则六、方案抉择关开关 vs 拥抱免鉴权七、代码改造内容安全 订阅消息云调用免鉴权八、连带坑登录也得重构前端通道也要换九、上云再踩502 白名单前导斜杠十、收尾意外数据“像新用户”十一、教训小结本文记录了微信小程序后端Spring Boot 4.0.7 / Java 17部署在微信云托管 CloudBase Run服务名coach从一个云托管环境迁移到新环境后聊天接口全部返回内容含违规信息、登录链路再次抛SSLHandshakeException的连环排查与修复过程。起初怀疑 DNS 解析异常走了使用 DoH 获取真实解析结果的弯路翻官方文档才发现根因是云托管开放接口服务的官方旁挂组件改变了微信 API 的调用规则——HTTPS 直连会握手失败官方要求改走 HTTP 由云托管侧组件自动完成 token 注入 。最终用云调用免鉴权 登录读X-WX-OPENID头完成重构前端调用通道也顺势从wx.request公网调用切换到wx.cloud.callContainer内网调用又踩了微信令牌权限白名单路径漏前导斜杠一跤才全通。本文完整记录了从提出假设、验证问题到推翻假设的排查过程并总结了关键代码改动和踩坑经验供同样在微信云托管上调微信 API 的同学避坑。上回刚写了《微信小程序云托管 Spring Boot 登录失败排查Docker CA 证书导致 SSLHandshakeException》以为 SSL 的坑已经趟平了。结果十一天后同一个项目从云托管老环境迁到新环境、切完流量——聊天全炸、登录也挂。同样的SSLHandshakeException根因却升了一层上次是容器层基础镜像缺 CA 证书这次是架构层云托管把微信 API 的调用规则整个改了。一、现象每发一条消息都弹内容含违规信息迁移后第一件事就是聊天页没法用了每发一条消息都弹内容含违规信息请修改后重试。再翻后端日志熟悉的javax.net.ssl.SSLHandshakeException又回来了这次出现在登录链路上。二、初判全报违规 ≈ 检测服务全挂后端在用户输入和 AI 输出上都接了微信内容安全检测wxa/msg_sec_check而且刻意做成了fail-closed检测服务不可用时按拒绝处理生产安全优先宁误杀不放行。代码注释原话// 生产 fail-closed微信检测服务不可用时按拒绝处理宁误杀不放行log.error(msgSecCheck unavailable after {} attempts, failing closed,MAX_ATTEMPTS);returnVerdict.REJECTED;所以全报违规≈检测请求全失败≈后端到微信的 HTTPS 出问题了。方向锁定为什么云端的api.weixin.qq.com调不通。三、弯路我以为是 DNS 解析问题还尝试固定微信 API 的解析地址第一反应是 DNS 解析问题。抓了一下解析结果api.weixin.qq.com → 198.18.0.77 ← 这是本地网络工具 fake-ip 模式生成的虚拟解析地址后来复盘才发现这大概率是我自己开的路由的锅198.18.0.0/15 是 Clash/Surge 系代理客户端 fake-ip 模式的保留假 IP 段。当时开着路由系统 DNS 被代理接管它给域名分配假 IP 用于内部转发根本不是被劫持。我误判成 DNS 解析异常于是改用阿里云 DoH223.5.5.5重新获取真实解析结果真实 IP101.91.34.103 / 101.91.37.13 / 101.89.47.18 证书腾讯云 / DigiCert 签发合法然后在代码里使用Apache HttpClient 5 的InMemoryDnsResolver在进程内固定微信域名的解析地址。固定解析地址后本地验证居然是通的因为连接直接使用了真实服务地址避开了本地网络工具的解析接管。现在回头看这条弯路本身就是最大的看点我以为是 DNS 解析异常后来发现一部分原因来自自己的路由工具另一部分则是云托管官方旁挂组件在起作用。从头到尾都不是DNS 劫持。四、转折用户一句话点醒卡了两天朋友一句话点醒我你查一下官方文档呢是不是有什么没加白名单之类的。于是我去翻了微信开放文档里云托管的三篇核心文档《开放接口服务》guide/weixin/open.html《access_token 与调用》guide/weixin/token.html《云托管调用微信 API 常见问题》guide/weixin/faq.html核心发现就一句话云托管环境里负责处理api.weixin.qq.com请求的旁挂组件并不是异常网络行为而是微信云托管开放接口服务提供的官方能力。我以为的攻击其实是官方特性。当场人麻了。五、核心认知旁挂组件改写了调用规则开启开放接口服务后规则变成容器内对api.weixin.qq.com的请求会由旁挂组件接管旁挂组件使用自签证书处理 HTTPS 请求因此直接通过 HTTPS 调用会出现SSLHandshakeException官方预期的调用方式是使用 HTTP不是 HTTPS请求不携带access_token由云托管侧组件自动完成 token 注入你得在云托管控制台微信令牌权限白名单里登记你要调的接口路径。而 faq.html 还点明一个致命连带开启开放接口服务后jscode2sessionwx.login 的 code 换 openid会握手失败官方建议云托管别再用 code 登录。登录为什么也炸全对上了。六、方案抉择关开关 vs 拥抱免鉴权方案 A关掉开放接口服务开关回公网 HTTPS 直连但access_token的缓存、过期、刷新要自己维护一整套。方案 B拥抱开放接口服务改成云调用免鉴权HTTP 云托管侧自动完成 token 注入登录同步改成读取云托管透传的身份信息。我选了B。理由很实在免鉴权意味着能删掉整个 token 生命周期管理cachedToken、expiresAtMillis、刷新逻辑还有appId/appSecret依赖登录在callContainer通道下本来就有更省事的官方姿势顺手一起改干净。七、代码改造内容安全 订阅消息云调用免鉴权改前https://api.weixin.qq.com/wxa/msg_sec_check?access_tokenxxx自己缓存 token。改后// 云调用免鉴权使用 HTTP不携带 access_token。// HTTPS 请求会经过旁挂组件的证书处理直接调用可能出现 SSLHandshakeException。privatestaticfinalStringMSG_SEC_CHECK_URLhttp://api.weixin.qq.com/wxa/msg_sec_check;请求体直接 POST不带 token构造函数里删掉appId/appSecret和整套 token 缓存字段由云托管侧组件自动完成 token 注入。协议从 HTTPS 降到 HTTP是这次最反直觉但最关键的一步。旁挂组件涉及自签证书处理因此直接 HTTPS 调用会失败按照官方方式改用 HTTP 后反而能够正常调用。订阅消息/cgi-bin/message/subscribe/send是同样的问题同样处理// 云调用免鉴权使用 HTTP不携带 access_token。// 直接使用 HTTPS 调用会出现 SSLHandshakeException。privatestaticfinalStringSEND_URLhttp://api.weixin.qq.com/cgi-bin/message/subscribe/send;顺手删掉原先注入的WechatAccessTokenProvider依赖、删掉access_token拼参。八、连带坑登录也得重构前端通道也要换faq.html 明说开关开后jscode2session必挂所以登录不能再走wx.login 拿 code→后端 code 换 openid。但这里有个前提我一开始根本没意识到在当前小程序云托管调用场景下微信用户身份相关的透传请求头由微信调用链路提供。云托管对前端调用分两种形态形态调用方式是否带微信用户信息头典型场景微信体系内调用wx.cloud.callContainer带X-WX-OPENID/X-WX-APPID/X-WX-UNIONID等由微信链路校验过小程序、公众号 H5公网调用wx.request直接请求服务公网域名不带任何微信用户信息普通 Web、APP、web-view 内嵌页我们之前前端一直用的wx.request公网调用后端自己维护 JWT 登录态——所以在公网通道下X-WX-OPENID头是永远拿不到的登录只能老老实实走 code 换 openid。这次既然要改成读头登录前端通道必须同步切到wx.cloud.callContainerwx.cloud.callContainer({config:{env:你的环境ID},path:/api/auth/login,header:{X-WX-SERVICE:coach,content-type:application/json},method:POST,data:{mockUserKey},}).then(res{/* res.data 就是后端 ApiResponse */})几个配套前提全是官方文档里写明的坑基础库要≥ 2.23.0小程序管理后台「设置 → 功能设置 → 基础库最低版本设置」否则callContainer直接不可用callContainer仅限被该云托管环境授权的小程序/公众号调用别的 AppID 调不通web-view 内嵌 H5 只能走公网调用拿不到透传头那部分页面得另想登录方案默认公网域名官方只支持接口测试用、不能用于正式生产公网调用生产要走自定义备案域名。切到 callContainer 后后端直接读头即可privatestaticfinalStringWX_OPENID_HEADERX-WX-OPENID;PostMapping(/login)publicApiResponseLoginResponselogin(RequestHeader(valueWX_OPENID_HEADER,requiredfalse)StringheaderOpenid,RequestBody(requiredfalse)MapString,Stringrequest){StringopenidresolveOpenid(headerOpenid,request!null?request.get(mockUserKey):null);...}privateStringresolveOpenid(StringheaderOpenid,StringmockUserKey){if(StringUtils.hasText(headerOpenid)){returnheaderOpenid.trim();}// 生产环境无头直接拒绝不留任何绕过口子防伪造登录if(!wechatMockEnabled){thrownewBusinessException(2001,未获取到微信登录身份请通过小程序进入,HttpStatus.UNAUTHORIZED);}// 仅本地 mock 模式伪造一个稳定 openidStringsourceStringUtils.hasText(mockUserKey)?mockUserKey.trim():UUID.randomUUID().toString();StringstableIdUUID.nameUUIDFromBytes(source.getBytes(StandardCharsets.UTF_8)).toString().replace(-,);returnmock_openid_stableId;}前端在callContainer模式下直接跳过wx.login根本不需要 code 了asyncfunctionloginWithWechat():PromiseAuthLoginData{constmockUserKeygetMockUserKey()constcodeUSE_CALLCONTAINER?:awaitgetWechatLoginCode()constresultawaitrequestAuthLoginData({url:/api/auth/login,method:POST,data:{code,mockUserKey},requireAuth:false,})...}安全要点生产环境读不到X-WX-OPENID头时直接拒绝绝不回退到任何 code 兜底——防止绕过云托管身份注入伪造登录。我配了专门的回归测试把这条语义锁死。九、上云再踩502 白名单前导斜杠改完跑测试后端mvnw.cmd test210/210 全绿前端npm run typecheck零错误。提交上云后聊天和登录还在报错但日志从SSLHandshakeException变成了HTTP 502。这是关键进度信号报错变了 HTTP 通道已经打通不再出现 TLS 握手失败此时返回的 502 说明请求已经进入云托管侧处理流程下一步应该检查微信令牌权限白名单是否配置正确。去云托管控制台云调用 → 微信令牌权限入口带 你的环境ID一看果然错wxa/msg_sec_check 对/wxa/msg_sec_check白名单路径漏了前导斜杠。加上那个/全线通畅。一个斜杠卡了半天白名单路径漏掉了最前面的/。十、收尾意外数据“像新用户”登录通了、聊天通了用户一看——历史数据全没了像新注册的号。排查后确认不是 bug是迁移的必然结果openid与小程序AppID及用户身份存在绑定关系。迁移时换了一个小程序号AppID变了同一个微信用户在新号下的openid也变了旧数据按旧openid存的自然查不到。结论对少量内测用户不值得做openid迁移接受换号即新用户。十一、教训小结云托管的 SSLHandshakeException 不一定是 CA 证书上次是 Docker 基础镜像缺 CA这次是官方开放接口服务的旁挂代理自签证书。表象相同根因差着一整个架构层。看到握手失败先别急着补证书。遇到网络异常前先查官方文档这次先是把自己路由fake-ip的虚拟解析地址当成 DNS 异常又把官方旁挂组件误认为异常网络行为。在当前云托管架构下云调用免鉴权是更合适的方案HTTP非 HTTPS 不带access_token 云托管侧自动完成 token 注入省掉整套 token 生命周期管理。白名单路径要带前导斜杠/wxa/msg_sec_check不能写成wxa/msg_sec_check漏一个/直接 502且不会有明显报错提示。登录在云托管上别用 jscode2session开启开放接口服务后该调用方式可能出现握手问题用callContainer透传的X-WX-OPENID头更省事且生产必须对无头严格拒绝。openid 绑定小程序 AppID换小程序号 全员 openid 换 ID 全员变新用户迁移前要心里有数。wx.request 公网调用拿不到微信透传头X-WX-OPENID这类身份头只有wx.cloud.callContainer内网通道才有要用云托管的免鉴权 / 透传头能力前端就得从wx.request切到callContainer基础库 ≥ 2.23.0、仅限授权小程序、web-view 除外。
返回列表