package service import ( "fmt" "math/rand" "strings" "time" "unicode" "gofaster/internal/auth/model" "gofaster/internal/auth/repository" "golang.org/x/crypto/bcrypt" ) type PasswordService struct { userService *UserService passwordPolicyRepo *repository.PasswordPolicyRepository passwordHistoryRepo *repository.PasswordHistoryRepository passwordResetRepo *repository.PasswordResetRepository } func NewPasswordService( userService *UserService, passwordPolicyRepo *repository.PasswordPolicyRepository, passwordHistoryRepo *repository.PasswordHistoryRepository, passwordResetRepo *repository.PasswordResetRepository, ) *PasswordService { return &PasswordService{ userService: userService, passwordPolicyRepo: passwordPolicyRepo, passwordHistoryRepo: passwordHistoryRepo, passwordResetRepo: passwordResetRepo, } } // GetPasswordPolicy 获取当前密码策略 func (s *PasswordService) GetPasswordPolicy() (*model.PasswordPolicy, error) { return s.passwordPolicyRepo.GetActivePolicy() } // ValidatePassword 验证密码是否符合策略要求 func (s *PasswordService) ValidatePassword(password string) (*model.PasswordValidationResult, error) { policy, err := s.GetPasswordPolicy() if err != nil { return nil, err } result := &model.PasswordValidationResult{ IsValid: true, Errors: []string{}, } // 检查长度 if len(password) < policy.MinLength { result.IsValid = false result.Errors = append(result.Errors, fmt.Sprintf("密码长度不能少于%d位", policy.MinLength)) } // 检查字符类型要求 charTypes := 0 hasUppercase := false hasLowercase := false hasNumbers := false hasSpecial := false for _, char := range password { switch { case unicode.IsUpper(char): hasUppercase = true case unicode.IsLower(char): hasLowercase = true case unicode.IsNumber(char): hasNumbers = true case unicode.IsPunct(char) || unicode.IsSymbol(char): hasSpecial = true } } if hasUppercase { charTypes++ } if hasLowercase { charTypes++ } if hasNumbers { charTypes++ } if hasSpecial { charTypes++ } // 添加调试日志 fmt.Printf("密码验证调试 - 密码: %s, 长度: %d, 字符类型: %d\n", password, len(password), charTypes) fmt.Printf("字符类型详情 - 大写: %v, 小写: %v, 数字: %v, 特殊: %v\n", hasUppercase, hasLowercase, hasNumbers, hasSpecial) // 检查具体类型要求 if policy.RequireUppercase && !hasUppercase { result.IsValid = false result.Errors = append(result.Errors, "密码必须包含大写字母") } if policy.RequireLowercase && !hasLowercase { result.IsValid = false result.Errors = append(result.Errors, "密码必须包含小写字母") } if policy.RequireNumbers && !hasNumbers { result.IsValid = false result.Errors = append(result.Errors, "密码必须包含数字") } if policy.RequireSpecial && !hasSpecial { result.IsValid = false result.Errors = append(result.Errors, "密码必须包含特殊字符") } // 检查最少字符类型数 if charTypes < policy.MinCharTypes { result.IsValid = false result.Errors = append(result.Errors, fmt.Sprintf("密码必须包含至少%d种字符类型", policy.MinCharTypes)) } // 计算密码强度 result.Strength = s.calculatePasswordStrength(password, charTypes) result.Level = s.calculatePasswordLevel(password, charTypes, policy) // 检查密码强度是否达到最低要求 if result.Strength < policy.MinRequiredLevel { result.IsValid = false result.Errors = append(result.Errors, fmt.Sprintf("密码强度必须达到%d级或以上,当前强度为%d级", policy.MinRequiredLevel, result.Strength)) } // 添加调试日志 fmt.Printf("密码强度计算结果 - 强度: %d, 等级: %d\n", result.Strength, result.Level) fmt.Printf("最低要求等级: %d, 是否满足要求: %v\n", policy.MinRequiredLevel, result.Strength >= policy.MinRequiredLevel) fmt.Printf("返回结果结构: %+v\n", result) fmt.Printf("JSON序列化测试: Strength=%d, Level=%d\n", result.Strength, result.Level) return result, nil } // calculatePasswordStrength 计算密码强度 (0-5) func (s *PasswordService) calculatePasswordStrength(password string, charTypes int) int { // 直接返回密码等级,不再使用百分制 return s.calculatePasswordLevel(password, charTypes, nil) } // calculatePasswordLevel 计算密码等级 (0-5) func (s *PasswordService) calculatePasswordLevel(password string, charTypes int, policy *model.PasswordPolicy) int { // 从最高等级开始判断,一旦满足条件就返回对应等级 // 5级:长度>=8,字符类型>=4 if len(password) >= 8 && charTypes >= 4 { return 5 } // 4级:长度>=8,字符类型>=3 if len(password) >= 8 && charTypes >= 3 { return 4 } // 3级:长度>=6,字符类型>=3 if len(password) >= 6 && charTypes >= 3 { return 3 } // 2级:长度>=6,字符类型>=2 if len(password) >= 6 && charTypes >= 2 { return 2 } // 1级:长度>=6,字符类型>=1 if len(password) >= 6 && charTypes >= 1 { return 1 } // 0级:长度>=1,字符类型>=1(任何密码都至少有一种字符类型) if len(password) >= 1 { return 0 } // 如果连0级都不满足,返回-1表示无效 return -1 } // CheckPasswordReuse 检查新密码是否与历史密码重复 func (s *PasswordService) CheckPasswordReuse(userID uint, newPassword string) (bool, error) { // 获取密码历史 history, err := s.passwordHistoryRepo.GetRecentPasswords(userID, 3) if err != nil { return false, err } // 检查新密码是否与历史密码重复 for _, hist := range history { if err := bcrypt.CompareHashAndPassword([]byte(hist.Password), []byte(newPassword)); err == nil { return true, nil // 发现重复 } } return false, nil } // ChangePassword 修改用户密码 func (s *PasswordService) ChangePassword(userID uint, currentPassword, newPassword string) error { fmt.Printf("开始修改密码,用户ID: %d\n", userID) // 获取用户信息 user, err := s.userService.GetByID(userID) if err != nil { fmt.Printf("获取用户信息失败: %v\n", err) return fmt.Errorf("获取用户信息失败: %w", err) } fmt.Printf("成功获取用户信息: %s\n", user.Username) // 验证当前密码 if err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(currentPassword)); err != nil { fmt.Printf("当前密码验证失败\n") return fmt.Errorf("当前密码不正确") } fmt.Printf("当前密码验证成功\n") // 验证新密码 validation, err := s.ValidatePassword(newPassword) if err != nil { fmt.Printf("新密码验证失败: %v\n", err) return fmt.Errorf("新密码验证失败: %w", err) } if !validation.IsValid { fmt.Printf("新密码不符合要求: %v\n", validation.Errors) return fmt.Errorf("新密码不符合要求: %s", strings.Join(validation.Errors, "; ")) } fmt.Printf("新密码验证成功,强度等级: %d\n", validation.Level) // 检查是否与历史密码重复 isReused, err := s.CheckPasswordReuse(userID, newPassword) if err != nil { fmt.Printf("检查密码重复失败: %v\n", err) return fmt.Errorf("检查密码重复失败: %w", err) } if isReused { policy, err := s.GetPasswordPolicy() if err != nil { fmt.Printf("获取密码策略失败: %v\n", err) return fmt.Errorf("获取密码策略失败: %w", err) } fmt.Printf("新密码与历史密码重复\n") return fmt.Errorf("新密码不能与前%d次使用的密码重复", policy.PreventReuse) } fmt.Printf("密码重复检查通过\n") // 更新密码 newPasswordHash, err := bcrypt.GenerateFromPassword([]byte(newPassword), bcrypt.DefaultCost) if err != nil { fmt.Printf("生成密码哈希失败: %v\n", err) return fmt.Errorf("生成密码哈希失败: %w", err) } now := time.Now() user.Password = string(newPasswordHash) user.PasswordChangedAt = &now user.ForceChangePassword = false // 取消强制修改密码 fmt.Printf("准备更新用户密码,新密码哈希: %s\n", string(newPasswordHash)[:10]) if err := s.userService.Update(user); err != nil { fmt.Printf("更新用户密码失败: %v\n", err) return fmt.Errorf("更新用户密码失败: %w", err) } fmt.Printf("用户密码更新成功\n") // 记录密码历史 passwordHistory := &model.PasswordHistory{ UserID: userID, Password: string(newPasswordHash), } if err := s.passwordHistoryRepo.Create(passwordHistory); err != nil { fmt.Printf("记录密码历史失败: %v\n", err) // 不返回错误,因为密码已经更新成功 fmt.Printf("警告:密码历史记录失败,但密码已更新\n") } else { fmt.Printf("密码历史记录成功\n") } fmt.Printf("密码修改完成,用户ID: %d\n", userID) return nil } // ResetPassword 重置用户密码 func (s *PasswordService) ResetPassword(userID uint, resetBy uint) (string, error) { // 生成临时密码 tempPassword := s.generateTempPassword() // 更新用户密码 user, err := s.userService.GetByID(userID) if err != nil { return "", err } tempPasswordHash, err := bcrypt.GenerateFromPassword([]byte(tempPassword), bcrypt.DefaultCost) if err != nil { return "", fmt.Errorf("生成临时密码哈希失败: %w", err) } user.Password = string(tempPasswordHash) user.ForceChangePassword = true // 标记需要强制修改密码 now := time.Now() user.PasswordChangedAt = &now if err := s.userService.Update(user); err != nil { return "", err } // 记录密码重置 passwordReset := &model.PasswordReset{ UserID: userID, ResetBy: resetBy, ResetAt: now, } if err := s.passwordResetRepo.Create(passwordReset); err != nil { return "", err } return tempPassword, nil } // CheckPasswordExpiration 检查密码是否过期 func (s *PasswordService) CheckPasswordExpiration(user *model.User) (bool, error) { if user.PasswordChangedAt == nil { return true, nil // 从未修改过密码,视为过期 } policy, err := s.GetPasswordPolicy() if err != nil { return false, err } expirationTime := user.PasswordChangedAt.Add(time.Duration(policy.ExpirationDays) * 24 * time.Hour) return time.Now().After(expirationTime), nil } // CheckForceChangePassword 检查是否需要强制修改密码 func (s *PasswordService) CheckForceChangePassword(user *model.User) bool { return user.ForceChangePassword } // generateTempPassword 生成临时密码 func (s *PasswordService) generateTempPassword() string { const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" password := make([]byte, 8) for i := range password { password[i] = charset[rand.Intn(len(charset))] } return string(password) } // hashPassword 密码哈希 func (s *PasswordService) hashPassword(password string) string { hash, _ := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) return string(hash) } // verifyPassword 验证密码 func (s *PasswordService) verifyPassword(password, hash string) bool { return bcrypt.CompareHashAndPassword([]byte(hash), []byte(password)) == nil } // TestPasswordStrengthCalculation 测试密码强度计算(仅用于调试) func (s *PasswordService) TestPasswordStrengthCalculation() { testPasswords := []string{ "a", // 1位,1种类型 -> 0级 "abc123", // 6位,2种类型 -> 2级 "abc123!", // 7位,3种类型 -> 3级 "abc123!@#", // 9位,3种类型 -> 4级 "abc123!@#$", // 10位,3种类型 -> 4级 "abc123!@#$%", // 11位,3种类型 -> 4级 "abc123!@#$%^", // 12位,3种类型 -> 4级 "abc123!@#$%^&", // 13位,3种类型 -> 4级 "abc123!@#$%^&*", // 14位,3种类型 -> 4级 "abc123!@#$%^&*()", // 15位,3种类型 -> 4级 "abc123!@#$%^&*()_", // 16位,3种类型 -> 4级 "abc123!@#$%^&*()_+", // 17位,3种类型 -> 4级 "abc123!@#$%^&*()_+=", // 18位,3种类型 -> 4级 "abc123!@#$%^&*()_+=[]{]", // 19位,3种类型 -> 4级 "abc123!@#$%^&*()_+=[]{}|", // 20位,3种类型 -> 4级 "abc123!@#$%^&*()_+=[]{}|\\", // 21位,3种类型 -> 4级 "abc123!@#$%^&*()_+=[]{}|\\/", // 22位,3种类型 -> 4级 "abc123!@#$%^&*()_+=[]{}|\\/<>", // 23位,3种类型 -> 4级 "abc123!@#$%^&*()_+=[]{}|\\/<>?", // 24位,3种类型 -> 4级 "abc123!@#$%^&*()_+=[]{}|\\/<>?\"", // 25位,3种类型 -> 4级 "abc123!@#$%^&*()_+=[]{}|\\/<>?\"'", // 26位,3种类型 -> 4级 "abc123!@#$%^&*()_+=[]{}|\\/<>?\"':", // 27位,3种类型 -> 4级 "abc123!@#$%^&*()_+=[]{}|\\/<>?\"':;", // 28位,3种类型 -> 4级 "abc123!@#$%^&*()_+=[]{}|\\/<>?\"':;.", // 29位,3种类型 -> 4级 } fmt.Println("=== 新密码强度计算测试(等级制)===") for _, pwd := range testPasswords { validation, _ := s.ValidatePassword(pwd) fmt.Printf("密码: %-30s | 强度等级: %d | 等级: %d | 有效: %v\n", pwd, validation.Strength, validation.Level, validation.IsValid) } fmt.Println("=== 测试结束 ===") }