package comm import ( "strconv" "testing" "time" ) func TestVerifyConsoleCall(t *testing.T) { const key = "SzwdcXrtrmJMRZkZtQuzhAETOIJnFzMH" const route = "user_login" now := time.Unix(1_800_000_000, 0) ts := strconv.FormatInt(now.Unix(), 10) good := SignConsoleCall(key, ts, route, "") // 翻转末位(末位本就可能是 '0',直接拼 "0" 会得到与原串相同的"篡改"签名,测试就成了假阳性) tampered := good[:len(good)-1] + map[bool]string{true: "1", false: "0"}[good[len(good)-1] == '0'] if err := VerifyConsoleCall(key, ts, good, route, "", now); err != nil { t.Fatalf("合法签名应通过: %v", err) } // 时钟小幅偏差仍应通过(两机时钟不会完全一致) if err := VerifyConsoleCall(key, ts, good, route, "", now.Add(time.Minute)); err != nil { t.Fatalf("1 分钟偏差应通过: %v", err) } cases := []struct { name string key, ts, sign, rt, arg string at time.Time }{ {"密钥不一致(两端 FIELD_ENCRYPT_KEY 不同)", "other-key", ts, good, route, "", now}, {"签名被篡改", key, ts, tampered, route, "", now}, {"签名绑定的是别的接口(防止换路由重放)", key, ts, SignConsoleCall(key, ts, "api_other", ""), route, "", now}, {"时间戳过期", key, ts, good, route, "", now.Add(10 * time.Minute)}, {"时间戳来自未来", key, ts, good, route, "", now.Add(-10 * time.Minute)}, {"缺时间戳", key, "", good, route, "", now}, {"缺签名", key, ts, "", route, "", now}, {"未配置密钥", "", ts, good, route, "", now}, {"时间戳非数字", key, "abc", good, route, "", now}, } for _, c := range cases { t.Run(c.name, func(t *testing.T) { if err := VerifyConsoleCall(c.key, c.ts, c.sign, c.rt, c.arg, c.at); err == nil { t.Fatal("应当被拒绝") } }) } } // 路由白名单与实际注册的接口必须对得上,否则网关要么放行了不该放行的,要么把 console 挡在门外。 func TestConsoleOnlyRoutes(t *testing.T) { if !IsConsoleOnlyRoute("api_reloadmoduleconfig") { t.Fatal("重载接口必须是 console 专用路由(否则会裸奔在公网上)") } if !IsConsoleOnlyRoute("api_resetmoduleconfig") { t.Fatal("重置接口必须是 console 专用路由") } if IsConsoleOnlyRoute("user_login") { t.Fatal("普通用户接口不该被当作 console 专用路由") } } // 参数(模块名)必须被签名绑定:否则截获一个"重置 email"的请求,改个头就能重置 wechatpay。 func TestSignBindsArg(t *testing.T) { const key, route = "k", "api_resetmoduleconfig" now := time.Unix(1_800_000_000, 0) ts := strconv.FormatInt(now.Unix(), 10) signEmail := SignConsoleCall(key, ts, route, "email") if err := VerifyConsoleCall(key, ts, signEmail, route, "email", now); err != nil { t.Fatalf("原参数应通过: %v", err) } if err := VerifyConsoleCall(key, ts, signEmail, route, "wechatpay", now); err == nil { t.Fatal("参数被换成别的模块必须拒绝") } }