优化部分部署信息
This commit is contained in:
@@ -115,7 +115,7 @@ func registerHandler(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !ValidatePassword(req.Password) {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -205,7 +205,7 @@ func changePasswordHandler(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !ValidatePassword(req.NewPassword) {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,22 @@ import (
|
|||||||
"golang.org/x/crypto/bcrypt"
|
"golang.org/x/crypto/bcrypt"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// 密码策略常量(集中管理,便于后续调整)
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
const (
|
||||||
|
// MinPasswordLength 密码最小长度
|
||||||
|
MinPasswordLength = 8
|
||||||
|
|
||||||
|
// PasswordRequirementMsg 统一错误提示(前端/后端共用)
|
||||||
|
PasswordRequirementMsg = "密码至少 8 位,需包含大小写字母和数字"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// JWT 密钥管理
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
var jwtSecretCache []byte
|
var jwtSecretCache []byte
|
||||||
|
|
||||||
func getJwtSecret() []byte {
|
func getJwtSecret() []byte {
|
||||||
@@ -27,11 +43,13 @@ func getJwtSecret() []byte {
|
|||||||
return jwtSecretCache
|
return jwtSecretCache
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Claims JWT 载荷
|
||||||
type Claims struct {
|
type Claims struct {
|
||||||
Username string `json:"username"`
|
Username string `json:"username"`
|
||||||
jwt.RegisteredClaims
|
jwt.RegisteredClaims
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GenerateJWT 生成 JWT Token
|
||||||
func GenerateJWT(username string) (string, error) {
|
func GenerateJWT(username string) (string, error) {
|
||||||
claims := Claims{
|
claims := Claims{
|
||||||
Username: username,
|
Username: username,
|
||||||
@@ -44,6 +62,7 @@ func GenerateJWT(username string) (string, error) {
|
|||||||
return token.SignedString(getJwtSecret())
|
return token.SignedString(getJwtSecret())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ParseJWT 解析 JWT Token
|
||||||
func ParseJWT(tokenString string) (*Claims, error) {
|
func ParseJWT(tokenString string) (*Claims, error) {
|
||||||
token, err := jwt.ParseWithClaims(tokenString, &Claims{}, func(token *jwt.Token) (interface{}, error) {
|
token, err := jwt.ParseWithClaims(tokenString, &Claims{}, func(token *jwt.Token) (interface{}, error) {
|
||||||
return getJwtSecret(), nil
|
return getJwtSecret(), nil
|
||||||
@@ -57,25 +76,42 @@ func ParseJWT(tokenString string) (*Claims, error) {
|
|||||||
return nil, errors.New("invalid token")
|
return nil, errors.New("invalid token")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// 密码校验(核心策略)
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
// ValidatePassword 校验密码强度
|
||||||
|
// 规则:长度 ≥ 8 位,且同时包含大写字母、小写字母、数字
|
||||||
|
// 注意:不强制要求特殊字符(与之前版本的区别)
|
||||||
func ValidatePassword(pwd string) bool {
|
func ValidatePassword(pwd string) bool {
|
||||||
if len(pwd) < 8 {
|
if len(pwd) < MinPasswordLength {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
var hasUpper, hasLower, hasDigit, hasSpecial bool
|
|
||||||
|
var hasUpper, hasLower, hasDigit bool
|
||||||
for _, ch := range pwd {
|
for _, ch := range pwd {
|
||||||
if ch >= 'A' && ch <= 'Z' {
|
switch {
|
||||||
|
case ch >= 'A' && ch <= 'Z':
|
||||||
hasUpper = true
|
hasUpper = true
|
||||||
} else if ch >= 'a' && ch <= 'z' {
|
case ch >= 'a' && ch <= 'z':
|
||||||
hasLower = true
|
hasLower = true
|
||||||
} else if ch >= '0' && ch <= '9' {
|
case ch >= '0' && ch <= '9':
|
||||||
hasDigit = true
|
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() {
|
func InitAdminUser() {
|
||||||
count, err := CountUsers()
|
count, err := CountUsers()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -85,6 +121,7 @@ func InitAdminUser() {
|
|||||||
log.Println("✅ 已存在管理员账户,跳过初始化")
|
log.Println("✅ 已存在管理员账户,跳过初始化")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Println("⚠️ 首次启动,请设置管理员账户")
|
log.Println("⚠️ 首次启动,请设置管理员账户")
|
||||||
log.Print("用户名: ")
|
log.Print("用户名: ")
|
||||||
var username string
|
var username string
|
||||||
@@ -92,10 +129,12 @@ func InitAdminUser() {
|
|||||||
if username == "" {
|
if username == "" {
|
||||||
username = "admin"
|
username = "admin"
|
||||||
}
|
}
|
||||||
|
|
||||||
for {
|
for {
|
||||||
log.Print("密码 (至少8位,含大小写、数字、特殊字符): ")
|
log.Printf("密码 (至少 %d 位,需包含大小写字母和数字): ", MinPasswordLength)
|
||||||
var password string
|
var password string
|
||||||
fmt.Scanln(&password)
|
fmt.Scanln(&password)
|
||||||
|
|
||||||
if ValidatePassword(password) {
|
if ValidatePassword(password) {
|
||||||
hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -110,12 +149,16 @@ func InitAdminUser() {
|
|||||||
log.Println("✅ 管理员账户创建成功!")
|
log.Println("✅ 管理员账户创建成功!")
|
||||||
break
|
break
|
||||||
} else {
|
} else {
|
||||||
log.Println("❌ 密码不符合复杂度要求,请重新输入")
|
log.Printf("❌ 密码不符合要求: %s", PasswordRequirementMsg)
|
||||||
log.Println(" 要求: 至少8位,包含大小写字母、数字和特殊字符")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Gin 中间件:JWT 认证
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
// AuthMiddleware JWT 认证中间件
|
||||||
func AuthMiddleware() gin.HandlerFunc {
|
func AuthMiddleware() gin.HandlerFunc {
|
||||||
return func(c *gin.Context) {
|
return func(c *gin.Context) {
|
||||||
authHeader := c.GetHeader("Authorization")
|
authHeader := c.GetHeader("Authorization")
|
||||||
@@ -124,18 +167,21 @@ func AuthMiddleware() gin.HandlerFunc {
|
|||||||
c.Abort()
|
c.Abort()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
parts := strings.SplitN(authHeader, " ", 2)
|
parts := strings.SplitN(authHeader, " ", 2)
|
||||||
if len(parts) != 2 || parts[0] != "Bearer" {
|
if len(parts) != 2 || parts[0] != "Bearer" {
|
||||||
c.JSON(http.StatusUnauthorized, gin.H{"code": 1, "msg": "认证令牌格式错误"})
|
c.JSON(http.StatusUnauthorized, gin.H{"code": 1, "msg": "认证令牌格式错误"})
|
||||||
c.Abort()
|
c.Abort()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
claims, err := ParseJWT(parts[1])
|
claims, err := ParseJWT(parts[1])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusUnauthorized, gin.H{"code": 1, "msg": "无效的认证令牌"})
|
c.JSON(http.StatusUnauthorized, gin.H{"code": 1, "msg": "无效的认证令牌"})
|
||||||
c.Abort()
|
c.Abort()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Set("username", claims.Username)
|
c.Set("username", claims.Username)
|
||||||
c.Next()
|
c.Next()
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user