feat: 准备 Admin 线上 HTTPS 部署 (#81)

This commit is contained in:
chengma
2026-08-10 02:46:59 +08:00
parent 78c0c4a73b
commit 8cc8574e8a
6 changed files with 101 additions and 9 deletions
+2 -2
View File
@@ -202,7 +202,7 @@ func safeNext(raw string) string {
func setAuthCookie(c *gin.Context, token string, expiresAt time.Time) {
http.SetCookie(c.Writer, &http.Cookie{
Name: authCookieName, Value: token, Path: "/", HttpOnly: true,
Secure: c.Request.TLS != nil, SameSite: http.SameSiteLaxMode,
Secure: requestIsHTTPS(c.Request), SameSite: http.SameSiteLaxMode,
Expires: expiresAt, MaxAge: int(service.WebSessionDuration.Seconds()),
})
}
@@ -210,7 +210,7 @@ func setAuthCookie(c *gin.Context, token string, expiresAt time.Time) {
func clearAuthCookie(c *gin.Context) {
http.SetCookie(c.Writer, &http.Cookie{
Name: authCookieName, Value: "", Path: "/", HttpOnly: true,
Secure: c.Request.TLS != nil, SameSite: http.SameSiteLaxMode,
Secure: requestIsHTTPS(c.Request), SameSite: http.SameSiteLaxMode,
Expires: time.Unix(1, 0), MaxAge: -1,
})
}
+15 -5
View File
@@ -40,12 +40,16 @@ func TestPasswordReturnPath移除改密反馈参数(t *testing.T) {
func TestAuthCookie安全属性(t *testing.T) {
gin.SetMode(gin.TestMode)
for _, test := range []struct {
name string
tls bool
secure bool
name string
tls bool
remoteAddress string
forwarded string
secure bool
}{
{"HTTP", false, false},
{"HTTPS", true, true},
{name: "HTTP", secure: false},
{name: "HTTPS", tls: true, secure: true},
{name: "本机HTTPS反向代理", remoteAddress: "127.0.0.1:12345", forwarded: "https", secure: true},
{name: "外部来源不能伪造代理头", remoteAddress: "203.0.113.8:12345", forwarded: "https", secure: false},
} {
t.Run(test.name, func(t *testing.T) {
response := httptest.NewRecorder()
@@ -53,6 +57,12 @@ func TestAuthCookie安全属性(t *testing.T) {
if test.tls {
request.TLS = &tls.ConnectionState{}
}
if test.remoteAddress != "" {
request.RemoteAddr = test.remoteAddress
}
if test.forwarded != "" {
request.Header.Set("X-Forwarded-Proto", test.forwarded)
}
context, _ := gin.CreateTestContext(response)
context.Request = request
setAuthCookie(context, "raw-token", time.Now().Add(12*time.Hour))
+5 -2
View File
@@ -42,8 +42,11 @@ func CSRFMiddleware() gin.HandlerFunc {
return
}
// HttpOnly 必须为 false —— 双提交方案要让页面把值填进表单。
// 本项目只监听本机,Secure 先留 false,将来上 HTTPS 再打开。
c.SetCookie(csrfCookieName, token, 12*3600, "/", "", false, false)
http.SetCookie(c.Writer, &http.Cookie{
Name: csrfCookieName, Value: token, Path: "/", HttpOnly: false,
Secure: requestIsHTTPS(c.Request), SameSite: http.SameSiteLaxMode,
MaxAge: 12 * 3600,
})
}
// 交给模板渲染成隐藏字段
c.Set(csrfFieldName, token)
+26
View File
@@ -0,0 +1,26 @@
package web
import (
"net"
"net/http"
"strings"
)
// requestIsHTTPS 识别直连 TLS,或仅信任本机反向代理传入的 HTTPS 标记。
// Admin 生产进程只监听 127.0.0.1,Nginx 在同机终止 TLS;不能无条件信任
// X-Forwarded-Proto,否则直接访问端口的客户端可以伪造安全来源。
func requestIsHTTPS(request *http.Request) bool {
if request.TLS != nil {
return true
}
host, _, err := net.SplitHostPort(request.RemoteAddr)
if err != nil {
return false
}
remoteIP := net.ParseIP(host)
if remoteIP == nil || !remoteIP.IsLoopback() {
return false
}
forwardedProto := strings.TrimSpace(strings.Split(request.Header.Get("X-Forwarded-Proto"), ",")[0])
return strings.EqualFold(forwardedProto, "https")
}