You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
382 lines
11 KiB
382 lines
11 KiB
2 weeks ago
|
package service
|
||
|
|
||
|
import (
|
||
|
"crypto/sha256"
|
||
|
"encoding/hex"
|
||
|
"fmt"
|
||
|
mathrand "math/rand"
|
||
|
"strings"
|
||
|
"time"
|
||
|
"unicode"
|
||
|
|
||
|
"gofaster/internal/auth/model"
|
||
|
"gofaster/internal/auth/repository"
|
||
|
)
|
||
|
|
||
|
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) {
|
||
|
policy, err := s.GetPasswordPolicy()
|
||
|
if err != nil {
|
||
|
return false, err
|
||
|
}
|
||
|
|
||
|
if policy.PreventReuse <= 0 {
|
||
|
return false, nil // 不检查重复
|
||
|
}
|
||
|
|
||
|
// 获取历史密码
|
||
|
history, err := s.passwordHistoryRepo.GetRecentPasswords(userID, policy.PreventReuse)
|
||
|
if err != nil {
|
||
|
return false, err
|
||
|
}
|
||
|
|
||
|
// 检查新密码是否与历史密码重复
|
||
|
newPasswordHash := s.hashPassword(newPassword)
|
||
|
for _, hist := range history {
|
||
|
if hist.Password == newPasswordHash {
|
||
|
return true, nil // 发现重复
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return false, nil
|
||
|
}
|
||
|
|
||
|
// ChangePassword 修改用户密码
|
||
|
func (s *PasswordService) ChangePassword(userID uint, currentPassword, newPassword string) error {
|
||
|
// 获取用户信息
|
||
|
user, err := s.userService.GetByID(userID)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
// 验证当前密码
|
||
|
if !s.verifyPassword(currentPassword, user.Password) {
|
||
|
return fmt.Errorf("当前密码不正确")
|
||
|
}
|
||
|
|
||
|
// 验证新密码
|
||
|
validation, err := s.ValidatePassword(newPassword)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
if !validation.IsValid {
|
||
|
return fmt.Errorf("新密码不符合要求: %s", strings.Join(validation.Errors, "; "))
|
||
|
}
|
||
|
|
||
|
// 检查是否与历史密码重复
|
||
|
isReused, err := s.CheckPasswordReuse(userID, newPassword)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
if isReused {
|
||
|
policy, err := s.GetPasswordPolicy()
|
||
|
if err != nil {
|
||
|
return fmt.Errorf("获取密码策略失败: %w", err)
|
||
|
}
|
||
|
return fmt.Errorf("新密码不能与前%d次使用的密码重复", policy.PreventReuse)
|
||
|
}
|
||
|
|
||
|
// 更新密码
|
||
|
newPasswordHash := s.hashPassword(newPassword)
|
||
|
now := time.Now()
|
||
|
|
||
|
user.Password = newPasswordHash
|
||
|
user.PasswordChangedAt = &now
|
||
|
user.ForceChangePassword = false // 取消强制修改密码
|
||
|
|
||
|
if err := s.userService.Update(user); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
// 记录密码历史
|
||
|
passwordHistory := &model.PasswordHistory{
|
||
|
UserID: userID,
|
||
|
Password: newPasswordHash,
|
||
|
}
|
||
|
if err := s.passwordHistoryRepo.Create(passwordHistory); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
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
|
||
|
}
|
||
|
|
||
|
user.Password = s.hashPassword(tempPassword)
|
||
|
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[mathrand.Intn(len(charset))]
|
||
|
}
|
||
|
return string(password)
|
||
|
}
|
||
|
|
||
|
// hashPassword 密码哈希
|
||
|
func (s *PasswordService) hashPassword(password string) string {
|
||
|
hash := sha256.Sum256([]byte(password))
|
||
|
return hex.EncodeToString(hash[:])
|
||
|
}
|
||
|
|
||
|
// verifyPassword 验证密码
|
||
|
func (s *PasswordService) verifyPassword(password, hash string) bool {
|
||
|
return s.hashPassword(password) == hash
|
||
|
}
|
||
|
|
||
|
// 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("=== 测试结束 ===")
|
||
|
}
|