修复旧钉钉回调丢失域名会话 (#115)

This commit is contained in:
WuYongJie 2026-09-08 06:30:59 +08:00
parent 0743f298a3
commit cc355b32ce
4 changed files with 16 additions and 23 deletions

View File

@ -39,7 +39,7 @@ probe="$(curl -sS -o /dev/null -w '%{http_code} %{redirect_url}' --max-time 15 \
"http://$SERVER_IP:8442/auth/dingtalk/callback?state=configuration-probe")"
status="${probe%% *}"
redirect_url="${probe#* }"
expected_redirect="https://caiwu.jiansuntuzhi.cn:8442/auth/login"
expected_redirect="https://caiwu.jiansuntuzhi.cn:8442/auth/dingtalk/callback?state=configuration-probe"
if [ "$status" != "302" ] || [ "$redirect_url" != "$expected_redirect" ]; then
install -m 644 "$BACKUP_DIR/caiwuxitong-backend-8442.conf" "$TARGET_CONFIG"

View File

@ -9,26 +9,15 @@ server {
ssl_protocols TLSv1.2 TLSv1.3;
client_max_body_size 12m;
# Plain HTTP is accepted only for the DingTalk OAuth callback registered
# by the enterprise administrator. Laravel must still generate HTTPS
# destinations after consuming the callback because every following page
# on port 8442 is HTTPS-only.
# The legacy registered HTTP callback must reach the browser's canonical
# HTTPS origin before Laravel reads its session. Proxying it with a forged
# Host loses the domain cookie and defeats browser-bound OAuth state.
error_page 497 = @plain_http_request;
location @plain_http_request {
if ($request_uri !~ "^/auth/dingtalk/callback(?:[?]|$)") {
return 308 https://caiwu.jiansuntuzhi.cn:8442$request_uri;
}
proxy_pass http://127.0.0.1:18088;
proxy_http_version 1.1;
proxy_set_header Host caiwu.jiansuntuzhi.cn;
proxy_set_header X-Forwarded-Host caiwu.jiansuntuzhi.cn;
proxy_set_header X-Forwarded-Port 8442;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto https;
proxy_read_timeout 30s;
add_header Cache-Control "no-store" always;
add_header Referrer-Policy "no-referrer" always;
return 302 https://caiwu.jiansuntuzhi.cn:8442$request_uri;
}
location / {

View File

@ -60,4 +60,6 @@
- 开发节点通过实际只读入口抽查 3 名有有效钉钉唯一值的财务花名册人员,3 名均被新准入查询识别;不输出人员身份、手机号或原始 HR 行。
- 生产节点使用现有只读查询入口读到两个确认的财务根部门,说明该节点可独立访问 HR 来源。本次没有在生产执行开户或写入。
- 浏览器检查实际 Blade 渲染的虚构账号页面:桌面和 390px 窄屏首次登录提示、管理员绑定页新说明和开户审计。权限保存与后续工作台跳转通过 HTTP 功能测试验证。
- 尚未发布,尚未进行真实钉钉扫码开户验收。
- `0743f29` 已发布;生产 3 名花名册有效样本判断通过,原 11 个账号保留,10 个旧 remember 令牌已失效,使用新 session cookie 名隔离旧会话(旧会话文件保留于备份)。
- 真实扫码发现钉钉开放平台尚未登记域名回调,且当前平台登录账号没有开发者权限。已恢复旧 IP 回调配置;Nginx 必须先通过 302 跳回 HTTPS 域名,不能将旧 IP 请求伪装成域名请求交给 Laravel,否则无法取得浏览器会话。域名登记后再切换生产回调变量。
- 自动开户的真实扫码验收仍在进行,不能用只读样本或登录页跳转代替最终开户证据。

View File

@ -6,14 +6,16 @@ use Tests\TestCase;
class DingTalkCallbackNginxTest extends TestCase
{
public function test_plain_http_is_limited_to_the_registered_dingtalk_callback(): void
public function test_plain_http_redirects_to_domain_before_laravel_reads_session(): void
{
$config = file_get_contents(base_path('deploy/host-nginx-8442-dingtalk.conf'));
$this->assertStringContainsString('error_page 497 = @plain_http_request;', $config);
$this->assertStringContainsString('if ($request_uri !~ "^/auth/dingtalk/callback(?:[?]|$)")', $config);
$this->assertStringContainsString('return 302 https://caiwu.jiansuntuzhi.cn:8442$request_uri;', $config);
$this->assertStringContainsString('add_header Cache-Control "no-store" always;', $config);
$this->assertStringNotContainsString('proxy_set_header Host caiwu.jiansuntuzhi.cn;', $config);
$this->assertStringNotContainsString('proxy_set_header X-Forwarded-Proto http;', $config);
$this->assertSame(2, substr_count($config, 'proxy_set_header X-Forwarded-Proto https;'));
$this->assertSame(1, substr_count($config, 'proxy_set_header X-Forwarded-Proto https;'));
$this->assertStringContainsString('return 308 https://caiwu.jiansuntuzhi.cn:8442$request_uri;', $config);
$this->assertStringContainsString('ssl_certificate /etc/nginx/caiwu-jiansuntuzhi-fullchain.pem;', $config);
$this->assertStringNotContainsString('caiwuxitong-backend.crt', $config);
@ -25,7 +27,7 @@ class DingTalkCallbackNginxTest extends TestCase
$this->assertStringContainsString('nginx -t', $script);
$this->assertStringContainsString('the previous configuration was restored', $script);
$this->assertStringContainsString("expected_redirect=\"https://caiwu.jiansuntuzhi.cn:8442/auth/login\"", $script);
$this->assertStringContainsString("expected_redirect=\"https://caiwu.jiansuntuzhi.cn:8442/auth/dingtalk/callback?state=configuration-probe\"", $script);
$this->assertStringContainsString("[ \"\$status\" != \"302\" ] || [ \"\$redirect_url\" != \"\$expected_redirect\" ]", $script);
$this->assertStringContainsString('DINGTALK_HTTP_CALLBACK_READY=', $script);
}