EdgeOne 边缘函数重定向跟随导致 WordPress 无法正常登录 —— 问题排查与解决思路
前言
最近一个月有一件事情一直困扰着我:我的 WordPress 后台一直无法正常登录了,因为发现这个问题的时候很忙,简单排除了几个情况了以后问题依然没有解决,于是就暂时放着不管了。
结果前几天逛 Linux Do,有佬友说他也遇到过这个问题(尽管最后发现应该不是同一个问题),让我发工单问,于是我就发了个工单,后续联系到了 EdgeOne 的研发同学,跟这位同学捣鼓了一下午,最后终于解决了问题,至于具体怎么解决的,可以慢慢往下看。由于这个排查问题的思路挺值得记录的,遂写一篇博客记录下。
问题发现
问题大概出现在我把 CDN 从腾讯云 CDN 迁移到 EdgeOne 后的半个月,我突然发现我手机登录不上博客后台了,具体体现为输入完账号密码点击登录后,又跳转回了登录页面,没有任何报错。
但是我的 PC 上依然可以正常用后台(因为 session 还没过期),因此,当时我姑且认为是 Safari(Webkit)导致的,所以暂时没管,但是过了几天 PC 浏览器登录态过期后,我发现 PC 也登不进去了,这下我可着急了,开始疯狂找原因。
时间问题?
首先 WordPress 通过 POST /wp-login.php
进行登录提交,正常情况下登录以后应该会下发包含 wordpress_logged_in_xxxxxx
和 wordpress_sec_xxxxx
的 Cookie,但实际测试发现 Set-Cookie
确实有,但是设置的 Expired 时间却是一年以前!于是我立马考虑是不是服务器时间错误导致的问题,很遗憾,最后发现无论是 Linux 服务器时间,还是 WordPress 实际获取到的时间,都没有任何问题。
缓存问题?
于是我开始着手调查是不是其他原因,于是第一个被我考虑到的就是缓存问题:有没有可能是因为读到了过时的缓存/CDN 回源异常导致 Cookie 没有设置成功呢?于是我停用了博客上的 W3 Total Cache 插件,并设置 EdgeOne 的缓存策略为永不缓存,然而即便在浏览器设置 Disable cache 并注意到 EdgeOne 在响应头中返回的 Eo-Cache-Status
值为 MISS
后,上述问题依旧存在。
外部问题?
后来又考虑插件和主题问题(尽管可能性不大,因为迁移到 EdgeOne 之后我没有变动过它们),但移除全部插件和主题,甚至重装 WordPress 后问题依然没有解决;遂考虑是不是 PHP/Nginx/Redis 问题,逐一排查后均一无所获。
发现临时解决方案,暂时放弃
后来我又看了一下 wp-login.php
的源代码,发现上述将 Cookie Expired 时间设置为当前时间的一年前的操作,是WordPress 为已登录用户访问此页面可以重新登录而强制清除客户端登录态的正常操作,因此可以彻底断定不是时间问题。
但在探索上述问题的过程中,我无意间发现干一件事情会让我可以登录上的事情:首先在 wp-login.php
页面上点击登录,跳转回同一页面以后删除 wordpress_test_cookie
这个 Cookie,刷新页面,这时页面会提示浏览器未启用 Cookie,此时再看浏览器 Cookie 状态会注意到正确的用户登录态 Cookie 已经被设置上了!这时再立刻跳转到 /wp-admin
,就可以正常登录后台了。
虽然依然不知道到底发生了什么,但至少此番操作让我了解到,当我点击“登录”时,服务器侧的 session 状态设置是正常的(不然我不可能简单刷新一下页面就给我设置上登录态了),但是具体原因仍旧不清楚,不过无论如何好歹可以正常登录了,先凑活用着吧。
问题解决 —— 我们需要再深入些
时间回到现在,我就此问题提交了工单咨询,并于下午联系到了 EdgeOne 的研发同学,这位同学使用自己的环境,在我的服务器上复现了我的问题;但随后无法在自己的测试服务器上复现这个问题,于是我们开始尝试尽量的同步我的服务器和测试服务器的环境,从 PHP 到 WordPress 插件都同步了个遍,但依然没能复现问题,这令我们都很疑惑。
这时我又注意到,一旦我直接回源访问博客,不经过 EdgeOne,那么一切问题都恢复了,因此这让我确定应该是 EdgeOne 侧的问题,而不是服务器侧的问题。而这位研发同学刚开始也考虑是不是 EdgeOne 缓存相关的问题,但他确定 Eo-Cache-Status
值为 MISS
后便排除了是 EO 缓存导致的问题。虽然没有找出原因,但我们大致可以确定,问题出现在 EdgeOne 到源站的回源过程中。
就在思路又回到最初的状态的时候,我突然注意到一个非常关键的因素,即我服务器和测试服务器一个非常重要的不同点:使用正常 WordPress 实例访问 /wp-admin
的时候,应该先 301 跳转到/wp-admin/
,随后 302 跳转到 /wp-login.php
,并携带一个 redirect_to
查询参数;但是我的网站访问 /wp-admin
的时候竟然啥也没发生,/wp-admin
直接渲染了 /wp-login.php
的页面,没有跳转,而回源访问是正常的,也就是说,只要上了 EO,/wp-admin
就会奇怪的渲染本不该由他渲染的 /wp-login.php
页面。后经另外一位同学提醒,不止是 /wp-admin
,/login
/admin
全部都会直接渲染出 /wp-login.php
页面,这些页面全部有一个特点就是,本来应该 302 到 /wp-login.php
,但全部没跳转,原地渲染了。这让我立马想到一个之前在腾讯云 CDN 看到过的功能 —— 回源自动跟随 301/302 跳转。询问研发同学后回复 EdgeOne 同样支持此功能,但经过检查发现我并没有开启。
不过这给了我一个非常好的思路,是不是有什么东西因为自动跟随了301/302跳转导致登录流程损坏呢?翻来覆去,我突然意识到有没有可能是边缘函数的问题,经过排除法后发现还真是,遂开始深度探究,最后没想到,竟然是一个简单的函数调用导致的这个问题。
一切都是 fetch
的错
时间回到 9 月中,为了支持在 WP Statistics 中使用 Cloudflare IP GeoLocation,我写了一个边缘函数,在回源请求中设置了几个包含访客 IP 地址信息的请求头,一遍可以精确的获取访客的位置信息。
而在这个边缘函数的代码中,设置完请求头以后,我使用如下的方式调用了 fetch
函数获取响应并返回给 EdgeOne 处理:
return await fetch(request);
此处的 request
参数类型为 Request
,来自于 FetchEvent
,包含 method
, headers
, body
属性代表请求的请求方法、请求头和请求体,以及 redirect
代表重定向策略,在初始化 Request
对象(RequestInit
)时,如果不传入这些参数,那么会采用一个默认值,而 redirect
属性的默认值恰好是...
属性名 | 类型 | 必填 | 默认值 | 说明 |
---|---|---|---|---|
redirect | string | 否 | follow | 重定向策略,支持 manual 、error 和 follow 。 |
follow......
于是我迅速修改了代码,改为使用手动重定向:
return await fetch(new Request(request.url, {
method: request.method,
headers: request.headers,
body: request.body,
redirect: 'manual'
}));
保存,部署,然后一切都正常了。
问题分析 —— 为什么会发生这样的问题
这个 fetch
函数和 Request
对象并不是 EdgeOne 独创的,而是一套 Web 标准对象,而在 Node 或 Bun 环境中,调用 fetch
并设置 redirect
为 follow
后,运行时并不会记录此过程中的 Set-Cookie
请求,并将其设置到 Cookie
中,相反,它们只是忠实的记录这些请求头,然后将连接跳转到 301/302 请求指向的位置。
于是,这就导致当请求跳转时,WordPress 发现它设置的 Session Cookie 根本不存在,于是认为用户没有登录,进而重新将用户跳转回登录页面,而又由于跳转跟随,因此访问的 /wp-admin
便渲染出了用于登录的 /wp-login.php
的页面。
最后
特别要感谢轻友团的 @若海 和 @陈小磊 同学协助我查找并发现这些问题,如果没有他们的帮助,我可能到现在也没有解决问题;同时还要感谢腾讯云优质的客服服务,一下午都在陪我解决这个问题。万幸,问题得到了解决,我又能愉快的登录我的博客了。