From 848062da84e0a07dfa7fafd4c84a97de5714bd7f Mon Sep 17 00:00:00 2001 From: lxh2875931338 Date: Sat, 1 Aug 2026 19:59:55 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E9=83=A8=E5=88=86=E9=83=A8?= =?UTF-8?q?=E7=BD=B2=E4=BF=A1=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api.go | 4 ++-- auth.go | 68 +++++++++++++++++++++++++++++++++++++++++++++++---------- 2 files changed, 59 insertions(+), 13 deletions(-) diff --git a/api.go b/api.go index dd04d8f..2f885d8 100644 --- a/api.go +++ b/api.go @@ -115,7 +115,7 @@ func registerHandler(c *gin.Context) { } if !ValidatePassword(req.Password) { - c.JSON(http.StatusBadRequest, gin.H{"code": 1, "msg": "密码至少 8 位,需包含大小写字母、数字和特殊字符"}) + c.JSON(http.StatusBadRequest, gin.H{"code": 1, "msg": "密码至少 8 位,需包含大小写字母和数字"}) return } @@ -205,7 +205,7 @@ func changePasswordHandler(c *gin.Context) { } if !ValidatePassword(req.NewPassword) { - c.JSON(http.StatusBadRequest, gin.H{"code": 1, "msg": "密码至少 8 位,需包含大小写字母、数字和特殊字符"}) + c.JSON(http.StatusBadRequest, gin.H{"code": 1, "msg": "密码至少 8 位,需包含大小写字母和数字"}) return } diff --git a/auth.go b/auth.go index 5b46007..3b71d14 100644 --- a/auth.go +++ b/auth.go @@ -13,6 +13,22 @@ import ( "golang.org/x/crypto/bcrypt" ) +// ============================================================ +// 密码策略常量(集中管理,便于后续调整) +// ============================================================ + +const ( + // MinPasswordLength 密码最小长度 + MinPasswordLength = 8 + + // PasswordRequirementMsg 统一错误提示(前端/后端共用) + PasswordRequirementMsg = "密码至少 8 位,需包含大小写字母和数字" +) + +// ============================================================ +// JWT 密钥管理 +// ============================================================ + var jwtSecretCache []byte func getJwtSecret() []byte { @@ -27,11 +43,13 @@ func getJwtSecret() []byte { return jwtSecretCache } +// Claims JWT 载荷 type Claims struct { Username string `json:"username"` jwt.RegisteredClaims } +// GenerateJWT 生成 JWT Token func GenerateJWT(username string) (string, error) { claims := Claims{ Username: username, @@ -44,6 +62,7 @@ func GenerateJWT(username string) (string, error) { return token.SignedString(getJwtSecret()) } +// ParseJWT 解析 JWT Token func ParseJWT(tokenString string) (*Claims, error) { token, err := jwt.ParseWithClaims(tokenString, &Claims{}, func(token *jwt.Token) (interface{}, error) { return getJwtSecret(), nil @@ -57,25 +76,42 @@ func ParseJWT(tokenString string) (*Claims, error) { return nil, errors.New("invalid token") } +// ============================================================ +// 密码校验(核心策略) +// ============================================================ + +// ValidatePassword 校验密码强度 +// 规则:长度 ≥ 8 位,且同时包含大写字母、小写字母、数字 +// 注意:不强制要求特殊字符(与之前版本的区别) func ValidatePassword(pwd string) bool { - if len(pwd) < 8 { + if len(pwd) < MinPasswordLength { return false } - var hasUpper, hasLower, hasDigit, hasSpecial bool + + var hasUpper, hasLower, hasDigit bool for _, ch := range pwd { - if ch >= 'A' && ch <= 'Z' { + switch { + case ch >= 'A' && ch <= 'Z': hasUpper = true - } else if ch >= 'a' && ch <= 'z' { + case ch >= 'a' && ch <= 'z': hasLower = true - } else if ch >= '0' && ch <= '9' { + case ch >= '0' && ch <= '9': hasDigit = true - } else if strings.ContainsAny(string(ch), "!@#$%^&*()_+-=[]{}|;:,.<>?") { - hasSpecial = true + } + // 提前退出优化:一旦三类字符都已满足,立即返回 true + if hasUpper && hasLower && hasDigit { + return true } } - return hasUpper && hasLower && hasDigit && hasSpecial + + return hasUpper && hasLower && hasDigit } +// ============================================================ +// 命令行管理员初始化(首次启动交互式创建) +// ============================================================ + +// InitAdminUser 交互式创建管理员账户(仅在无用户时触发) func InitAdminUser() { count, err := CountUsers() if err != nil { @@ -85,6 +121,7 @@ func InitAdminUser() { log.Println("✅ 已存在管理员账户,跳过初始化") return } + log.Println("⚠️ 首次启动,请设置管理员账户") log.Print("用户名: ") var username string @@ -92,10 +129,12 @@ func InitAdminUser() { if username == "" { username = "admin" } + for { - log.Print("密码 (至少8位,含大小写、数字、特殊字符): ") + log.Printf("密码 (至少 %d 位,需包含大小写字母和数字): ", MinPasswordLength) var password string fmt.Scanln(&password) + if ValidatePassword(password) { hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) if err != nil { @@ -110,12 +149,16 @@ func InitAdminUser() { log.Println("✅ 管理员账户创建成功!") break } else { - log.Println("❌ 密码不符合复杂度要求,请重新输入") - log.Println(" 要求: 至少8位,包含大小写字母、数字和特殊字符") + log.Printf("❌ 密码不符合要求: %s", PasswordRequirementMsg) } } } +// ============================================================ +// Gin 中间件:JWT 认证 +// ============================================================ + +// AuthMiddleware JWT 认证中间件 func AuthMiddleware() gin.HandlerFunc { return func(c *gin.Context) { authHeader := c.GetHeader("Authorization") @@ -124,18 +167,21 @@ func AuthMiddleware() gin.HandlerFunc { c.Abort() return } + parts := strings.SplitN(authHeader, " ", 2) if len(parts) != 2 || parts[0] != "Bearer" { c.JSON(http.StatusUnauthorized, gin.H{"code": 1, "msg": "认证令牌格式错误"}) c.Abort() return } + claims, err := ParseJWT(parts[1]) if err != nil { c.JSON(http.StatusUnauthorized, gin.H{"code": 1, "msg": "无效的认证令牌"}) c.Abort() return } + c.Set("username", claims.Username) c.Next() }