package main import ( "errors" "fmt" "log" "net/http" "strings" "time" "github.com/gin-gonic/gin" "github.com/golang-jwt/jwt/v5" "golang.org/x/crypto/bcrypt" ) // ============================================================ // 密码策略常量(集中管理,便于后续调整) // ============================================================ const ( // MinPasswordLength 密码最小长度 MinPasswordLength = 8 // PasswordRequirementMsg 统一错误提示(前端/后端共用) PasswordRequirementMsg = "密码至少 8 位,需包含大小写字母和数字" ) // ============================================================ // JWT 密钥管理 // ============================================================ var jwtSecretCache []byte func getJwtSecret() []byte { if len(jwtSecretCache) > 0 { return jwtSecretCache } secret, err := GetJwtSecret() if err != nil { log.Fatal("❌ 获取 JWT 密钥失败:", err) } jwtSecretCache = []byte(secret) 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, RegisteredClaims: jwt.RegisteredClaims{ ExpiresAt: jwt.NewNumericDate(time.Now().Add(7 * 24 * time.Hour)), IssuedAt: jwt.NewNumericDate(time.Now()), }, } token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) 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 }) if err != nil { return nil, err } if claims, ok := token.Claims.(*Claims); ok && token.Valid { return claims, nil } return nil, errors.New("invalid token") } // ============================================================ // 密码校验(核心策略) // ============================================================ // ValidatePassword 校验密码强度 // 规则:长度 ≥ 8 位,且同时包含大写字母、小写字母、数字 // 注意:不强制要求特殊字符(与之前版本的区别) func ValidatePassword(pwd string) bool { if len(pwd) < MinPasswordLength { return false } var hasUpper, hasLower, hasDigit bool for _, ch := range pwd { switch { case ch >= 'A' && ch <= 'Z': hasUpper = true case ch >= 'a' && ch <= 'z': hasLower = true case ch >= '0' && ch <= '9': hasDigit = true } // 提前退出优化:一旦三类字符都已满足,立即返回 true if hasUpper && hasLower && hasDigit { return true } } return hasUpper && hasLower && hasDigit } // ============================================================ // 命令行管理员初始化(首次启动交互式创建) // ============================================================ // InitAdminUser 交互式创建管理员账户(仅在无用户时触发) func InitAdminUser() { count, err := CountUsers() if err != nil { log.Fatal("❌ 检查用户表失败:", err) } if count > 0 { log.Println("✅ 已存在管理员账户,跳过初始化") return } log.Println("⚠️ 首次启动,请设置管理员账户") log.Print("用户名: ") var username string fmt.Scanln(&username) if username == "" { username = "admin" } for { log.Printf("密码 (至少 %d 位,需包含大小写字母和数字): ", MinPasswordLength) var password string fmt.Scanln(&password) if ValidatePassword(password) { hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) if err != nil { log.Println("❌ 密码加密失败:", err) continue } err = CreateUser(username, string(hash)) if err != nil { log.Println("❌ 创建用户失败:", err) continue } log.Println("✅ 管理员账户创建成功!") break } else { log.Printf("❌ 密码不符合要求: %s", PasswordRequirementMsg) } } } // ============================================================ // Gin 中间件:JWT 认证 // ============================================================ // AuthMiddleware JWT 认证中间件 func AuthMiddleware() gin.HandlerFunc { return func(c *gin.Context) { authHeader := c.GetHeader("Authorization") if authHeader == "" { c.JSON(http.StatusUnauthorized, gin.H{"code": 1, "msg": "未提供认证令牌"}) 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() } }