外观设置

本站

连上路由器却登不上校园网:一次 ac_id 排查与修正

2026-09-17 · 校园网 · SZUDesktop · 网络排查

开发 SZUDesktop 的校园网登录功能时,我在连接路由器的环境下遇到了这个报错:

认证失败:ac_id 用错了(Unknow ac-type)。

起初我怀疑是自己的实现有问题。随后发现,在同一个网络环境里,官方认证客户端也无法登录,而手动打开带有 ac_id=12 的门户页面可以完成登录。

这个差异让我把排查方向转向了认证参数。不过,回头整理时需要修正原先过强的判断:两个客户端一起失败,只能说明它们可能受到了共同条件的影响,不能排除客户端实现问题,也不能证明原因已经唯一确定。

下面保留当时的排查经历,同时把实际观察、代码行为和仍需验证的推断分开。

登录页会显示参数,却未必能证明参数正确

当时使用的页面地址里带着 ac_id=12,而客户端原先使用的是另一个值。这是一条值得检查的线索。

在项目采用的 SRun 登录实现里,ac_id 会参与用户信息的编码及 chksum 校验和计算,并被放进认证请求。因此,它不能被当作随便填的界面参数。但编号不适用于当前认证环境,与校验算法本身算错,是两个不同问题;仅凭错误文案不能把二者混为一谈。

我尝试过依次请求不同编号的门户页面,得到的结果大致如下:

ac_id=1   → 8333 字节
ac_id=2   → 8333 字节
ac_id=3   → 8334 字节
ac_id=4   → 8333 字节
ac_id=5   → 8333 字节
ac_id=10  → 8333 字节
ac_id=12  → 8333 字节

这些请求都拿到了完整页面,体积几乎一样。页面还会把传入的编号填回配置里。

这意味着,通过“能否打开登录页”挑编号并不可行。它甚至不能严格证明某个编号在认证系统中有效:门户可能只是接受了参数并渲染模板。我把一个值传进去,页面再把它展示出来,这是一条回显,不是独立证据。

比猜编号更有用的是观察真实访问路径

校园网、酒店 Wi-Fi 这类网络,可能在认证前限制外部访问,并将浏览器引导到登录页。请求一个有已知响应的外部 HTTP 地址,再比较实际返回内容,是常见的门户探测办法。但不同网络的行为并不统一:可能重定向,可能返回拦截页面,也可能只拒绝连接。RFC 8952 对既有门户探测方式的说明

对这次排查,更有价值的线索,是当前未认证连接被导向了哪个学校认证地址,而非我主动拼出来的地址。若真实跳转中带有 ac_id,它比逐个试参数更接近当前网络要求,之后仍要通过认证结果确认。

我之前还把认证门户自己放进探测地址,结果它会跳到自己的默认入口,产生一个看起来合理的编号。问题在于,这只能证明门户有默认跳转,无法证明校园网刚刚识别了我的接入环境。

这次经验让我意识到:不仅要看返回了什么,还要看这个返回是被什么请求触发的。

原来的代码说明有两个漏洞

第一,HTTP 200 不代表已经能够正常上网。 返回的可能就是一个登录页或拦截页。判断探测结果,需要把状态码与目标地址、预期内容、实际内容一起看。反过来,看到 302 也不能立刻当作校园网认证跳转,普通网站本来就可能重定向。

原文一边写“只接受 3xx”,一边声称兼容 HTML 中的跳转,这两种描述没有对齐。对于返回 200 的拦截页面,前面的筛选会直接跳过它,后面的 HTML 解析根本不会执行。

第二,如果用 Go 读取第一次跳转的 Location,需要明确处理自动重定向。http.Client 默认会跟随重定向;等 Get 返回时,拿到的可能已经是最终页面。Go 提供 CheckRedirect 来改变这一行为。Go net/http 文档

下面只是读取跳转前响应所需的配置片段,不是一份完整的校园网识别实现:

client := &http.Client{
    Timeout: 5 * time.Second,
    CheckRedirect: func(req *http.Request, via []*http.Request) error {
        return http.ErrUseLastResponse
    },
}

随后还要检查响应是不是预期的认证入口,并正确关闭响应体。拿到了某个 Location,不等于已经完成了参数发现,更不等于登录成功。

来源优先级与正确性,要分开记录

当时我给 ac_id 标了四种来源:手动填写、历史缓存、探测跳转和门户猜测。这个区分值得保留,因为它能解释一个值是怎样来的。

但“手动填写的优先级最高”应当理解为尊重用户指定,不能写成“绝对可信”。人可能填错,缓存可能过期,自动探测也可能受到代理、门户默认跳转等因素影响。

更合理的记录方式,是同时保留两个信息:这个值从哪里来,以及它是否在当前认证过程中得到过成功验证。

项目当时采用了“猜测值不写入可信缓存”的限制。它避免把仅仅能打开页面的编号留下来,当成下一次登录的依据。但“来源较可靠”本身也不够,写缓存仍应与真正的认证结果对应,不能只因页面打开、请求返回 ok 或“已经在线”就宣布该参数被验证过。

缓存是一条历史线索。它可以减少重复操作,却不能让过去的成功永远代表现在。

网关地址也不能直接代表墙上的某个网口

原文把默认网关当成网络标识,并进一步推导:“换墙口,网关和 ac_id 就会一起变;不换墙口,编号就不会变。”这个结论缺少足够证据。

最直观的反例就在路由器后面。电脑看到的默认网关可能一直是路由器的局域网地址,例如 192.168.1.1;把路由器的上联线路换掉,电脑看到的这个地址仍然可能不变。不同地方的两台路由器,也可能使用同一个局域网地址。

因此,默认网关可以作为区分网络的一个线索,却不能单独证明认证接入点相同。本机地址也不等于学校看到的出口地址。这些字段的名称和含义,需要在设计缓存时就分清。

至于“换一台路由器会不会改变 ac_id”,这次记录不足以给出普遍答案。路由模式、桥接方式、上联路径和学校侧配置都可能影响观察结果。要确认此次故障的具体原因,还需要对照同一时段直连与经过路由器时的真实跳转和认证响应,而不是从设备名称推断。

这次排查真正留下的东西

对我来说,这次最有价值的收获,是开始追问一个配置值的证据来源。

默认值、页面回显、网络探测和认证成功,长得都像“得到了一个编号”,代表的事情却不同。把它们压成同一个字符串,程序就很容易忘记自己到底知道了什么;文章如果把这些步骤写成一条已经证明的因果链,也会误导下一位遇到类似问题的人。

这篇记录能确定的是:当时存在客户端失败与网页入口可用的差异,门户枚举不足以发现当前参数,探测和缓存的解释也需要修正。它不应被理解为已经实测覆盖所有深大路由器场景的修复报告。

项目仍在继续维护,代码与当前版本可见 SZUDesktop 仓库。

柯西 Alakazam

评论