package web import ( "crypto/tls" "net/http" "net/http/httptest" "net/url" "strings" "testing" ) // S4 回归:https 下会话 cookie 必须带 Secure;http 下不能带 —— // 无条件打 Secure 会让本机 http 调试和本仓库的 CDP 验证流程直接登不进去。 func TestSessionCookieSecureFlag(t *testing.T) { f := newLoginFixture(t) login := func(https bool, xfp string) *http.Cookie { t.Helper() f.srv.loginGuard.Reset() id, _, err := f.srv.captcha.Issue() if err != nil { t.Fatalf("签发验证码失败: %v", err) } code, ok := f.srv.captcha.Answer(id) if !ok { t.Fatal("刚签发的验证码取不到答案") } form := url.Values{"username": {f.user}, "password": {f.pass}, "captcha": {code}} r := httptest.NewRequest("POST", "/admin/login", strings.NewReader(form.Encode())) r.Header.Set("Content-Type", "application/x-www-form-urlencoded") if xfp != "" { r.Header.Set("X-Forwarded-Proto", xfp) } if https { r.TLS = &tls.ConnectionState{} } r.AddCookie(&http.Cookie{Name: captchaCookie, Value: id}) w := httptest.NewRecorder() f.srv.loginSubmit(w, r) if w.Code != http.StatusSeeOther { t.Fatalf("登录应 303,实际 %d:%s", w.Code, firstLine(w.Body.String())) } for _, c := range w.Result().Cookies() { if c.Name == f.srv.cookie { return c } } t.Fatal("登录成功却没有下发会话 cookie") return nil } cases := []struct { name string https bool xfp string want bool }{ {"直连 http", false, "", false}, {"直连 https", true, "", true}, {"反代 https", false, "https", true}, {"反代多段取首段", false, "https, http", true}, {"反代 http", false, "http", false}, } for _, c := range cases { got := login(c.https, c.xfp) if got.Secure != c.want { t.Errorf("%s: Secure=%v,期望 %v", c.name, got.Secure, c.want) } // 其余属性不能被顺手改掉 if !got.HttpOnly || got.Path != "/" || got.SameSite != http.SameSiteLaxMode { t.Errorf("%s: cookie 其它属性有变: HttpOnly=%v Path=%q SameSite=%v", c.name, got.HttpOnly, got.Path, got.SameSite) } } }