refactor: 清理旧版兼容性,升级为 bcrypt 密码算法
- 密码哈希从 MD5+SHA1 升级为 bcrypt - 删除 super_admins/users 表中的 salt 字段 - 删除旧版升级文件(upgrade.php, check_upgrade, execute_upgrade, sql/upgrades/) - 删除 PASSWORD_SALT 配置项 - 清理所有'兼容 Python 版'注释 - 新项目独立,无历史包袱
This commit is contained in:
@@ -16,7 +16,6 @@ import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
|
||||
"hz-gitea.sea-studio.top/canglan/SharedClassManager/internal/config"
|
||||
"hz-gitea.sea-studio.top/canglan/SharedClassManager/internal/model"
|
||||
"hz-gitea.sea-studio.top/canglan/SharedClassManager/internal/repository"
|
||||
"hz-gitea.sea-studio.top/canglan/SharedClassManager/pkg/crypto"
|
||||
@@ -100,10 +99,17 @@ func (s *AdminService) getInitialPassword(classID int) (string, error) {
|
||||
return pwd, nil
|
||||
}
|
||||
|
||||
// hashPassword 对密码进行 bcrypt 哈希,失败时 panic(不应发生)
|
||||
func hashPasswordOrPanic(password string) string {
|
||||
hash, err := crypto.HashPassword(password)
|
||||
if err != nil {
|
||||
logger.Sugared.Fatalf("密码哈希失败: %v", err)
|
||||
}
|
||||
return hash
|
||||
}
|
||||
|
||||
// AddStudent 新增学生
|
||||
func (s *AdminService) AddStudent(studentNo, name string, parentAccount *string, classID int, dormitoryNumber *string) (map[string]interface{}, error) {
|
||||
cfg := config.AppConfig
|
||||
|
||||
// 校验宿舍号格式
|
||||
if !validateDormitoryNumber(dormitoryNumber) {
|
||||
return map[string]interface{}{"success": false, "message": "宿舍号格式不正确,应为如 东1-101 的格式"}, nil
|
||||
@@ -134,7 +140,7 @@ func (s *AdminService) AddStudent(studentNo, name string, parentAccount *string,
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
passwordHash := crypto.HashPassword(defaultPassword, cfg.PasswordSalt)
|
||||
passwordHash := hashPasswordOrPanic(defaultPassword)
|
||||
_, err = s.userRepo.CreateStudent(studentNo, passwordHash, name, studentID)
|
||||
if err != nil {
|
||||
logger.Sugared.Errorf("创建学生登录账号失败: student_no=%s, student_id=%d, err=%v", studentNo, studentID, err)
|
||||
@@ -147,7 +153,7 @@ func (s *AdminService) AddStudent(studentNo, name string, parentAccount *string,
|
||||
if parentAccount != nil && *parentAccount != "" {
|
||||
exists, _ := s.userRepo.CheckUsernameExists(*parentAccount)
|
||||
if !exists {
|
||||
parentHash := crypto.HashPassword(defaultPassword, cfg.PasswordSalt)
|
||||
parentHash := hashPasswordOrPanic(defaultPassword)
|
||||
parentRealName := fmt.Sprintf("%s家长", name)
|
||||
if _, err := s.userRepo.CreateParent(*parentAccount, parentHash, parentRealName, studentID); err != nil {
|
||||
logger.Sugared.Warnf("创建家长账号失败(学生记录已保留): parent_account=%s, student_id=%d, err=%v", *parentAccount, studentID, err)
|
||||
@@ -167,7 +173,6 @@ func (s *AdminService) AddStudent(studentNo, name string, parentAccount *string,
|
||||
// 未使用数据库事务的原因:Repository 层未暴露事务接口,全量事务包裹需要较大重构;
|
||||
// 且批量导入场景下允许部分成功是合理的业务权衡(用户可修正失败记录后重新导入)。
|
||||
func (s *AdminService) ImportStudents(students []map[string]interface{}, classID int) (map[string]interface{}, error) {
|
||||
cfg := config.AppConfig
|
||||
successCount := 0
|
||||
failedCount := 0
|
||||
var details []map[string]interface{}
|
||||
@@ -262,7 +267,7 @@ func (s *AdminService) ImportStudents(students []map[string]interface{}, classID
|
||||
existingSet[studentNo] = true
|
||||
|
||||
// 创建学生登录账号
|
||||
passwordHash := crypto.HashPassword(password, cfg.PasswordSalt)
|
||||
passwordHash := hashPasswordOrPanic(password)
|
||||
if _, err := s.userRepo.CreateStudent(studentNo, passwordHash, name, studentID); err != nil {
|
||||
logger.Sugared.Errorf("批量导入-创建学生登录账号失败: student_no=%s, student_id=%d, err=%v", studentNo, studentID, err)
|
||||
// 回滚学生记录
|
||||
@@ -277,7 +282,7 @@ func (s *AdminService) ImportStudents(students []map[string]interface{}, classID
|
||||
|
||||
// 创建家长账号
|
||||
if parentAccount != nil && *parentAccount != "" && !usernameSet[*parentAccount] {
|
||||
parentHash := crypto.HashPassword(password, cfg.PasswordSalt)
|
||||
parentHash := hashPasswordOrPanic(password)
|
||||
parentRealName := fmt.Sprintf("%s家长", name)
|
||||
if _, err := s.userRepo.CreateParent(*parentAccount, parentHash, parentRealName, studentID); err != nil {
|
||||
logger.Sugared.Errorf("批量导入-创建家长账号失败: parent_account=%s, student_id=%d, err=%v", *parentAccount, studentID, err)
|
||||
@@ -346,7 +351,6 @@ func (s *AdminService) ResetStudentPassword(studentID int, newPassword string) e
|
||||
if valid, msg := crypto.ValidatePasswordStrength(newPassword); !valid {
|
||||
return fmt.Errorf("%s", msg)
|
||||
}
|
||||
cfg := config.AppConfig
|
||||
student, err := s.studentRepo.GetByID(studentID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("学生不存在")
|
||||
@@ -356,14 +360,15 @@ func (s *AdminService) ResetStudentPassword(studentID int, newPassword string) e
|
||||
if err != nil {
|
||||
return fmt.Errorf("学生登录账号不存在")
|
||||
}
|
||||
passwordHash := crypto.HashPassword(newPassword, cfg.PasswordSalt)
|
||||
passwordHash, err := crypto.HashPassword(newPassword)
|
||||
if err != nil {
|
||||
return fmt.Errorf("密码加密失败")
|
||||
}
|
||||
return s.userRepo.UpdatePassword(user.UserID, passwordHash)
|
||||
}
|
||||
|
||||
// AddAdmin 添加管理员
|
||||
func (s *AdminService) AddAdmin(username, realName, password, roleType string, classID int, subjectID *int) (map[string]interface{}, error) {
|
||||
cfg := config.AppConfig
|
||||
|
||||
exists, _ := s.userRepo.CheckUsernameExists(username)
|
||||
if exists {
|
||||
return map[string]interface{}{"success": false, "message": "用户名已存在"}, nil
|
||||
@@ -377,7 +382,10 @@ func (s *AdminService) AddAdmin(username, realName, password, roleType string, c
|
||||
password = pwd
|
||||
}
|
||||
|
||||
passwordHash := crypto.HashPassword(password, cfg.PasswordSalt)
|
||||
passwordHash, err := crypto.HashPassword(password)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("密码加密失败: %w", err)
|
||||
}
|
||||
userID, err := s.userRepo.CreateAdmin(username, passwordHash, realName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -436,8 +444,10 @@ func (s *AdminService) ResetAdminPassword(userID int, newPassword string) error
|
||||
if valid, msg := crypto.ValidatePasswordStrength(newPassword); !valid {
|
||||
return fmt.Errorf("%s", msg)
|
||||
}
|
||||
cfg := config.AppConfig
|
||||
passwordHash := crypto.HashPassword(newPassword, cfg.PasswordSalt)
|
||||
passwordHash, err := crypto.HashPassword(newPassword)
|
||||
if err != nil {
|
||||
return fmt.Errorf("密码加密失败")
|
||||
}
|
||||
return s.userRepo.UpdatePassword(userID, passwordHash)
|
||||
}
|
||||
|
||||
|
||||
@@ -17,13 +17,13 @@ import (
|
||||
|
||||
"github.com/redis/go-redis/v9"
|
||||
|
||||
"hz-gitea.sea-studio.top/canglan/SharedClassManager/internal/config"
|
||||
"hz-gitea.sea-studio.top/canglan/SharedClassManager/internal/model"
|
||||
"hz-gitea.sea-studio.top/canglan/SharedClassManager/internal/repository"
|
||||
"hz-gitea.sea-studio.top/canglan/SharedClassManager/pkg/crypto"
|
||||
"hz-gitea.sea-studio.top/canglan/SharedClassManager/pkg/database"
|
||||
appJwt "hz-gitea.sea-studio.top/canglan/SharedClassManager/pkg/jwt"
|
||||
"hz-gitea.sea-studio.top/canglan/SharedClassManager/pkg/logger"
|
||||
"hz-gitea.sea-studio.top/canglan/SharedClassManager/internal/config"
|
||||
)
|
||||
|
||||
// AuthService 认证服务
|
||||
@@ -124,10 +124,8 @@ func (s *AuthService) Login(username, password, ip, userAgent string) *LoginResu
|
||||
return s.tryParentLogin(username, password, ip, userAgent, cfg, attemptsKey, ipAttemptsKey)
|
||||
}
|
||||
|
||||
// 验证密码(使用全局 PASSWORD_SALT,与 Python 版兼容。
|
||||
// 已知设计局限:全局共享盐值,若泄露则所有普通用户密码面临风险。
|
||||
// 后续迁移计划:为每个用户生成独立盐值,存储在 users 表中。)
|
||||
if !crypto.VerifyPassword(password, user.PasswordHash, cfg.PasswordSalt) {
|
||||
// 验证密码(bcrypt)
|
||||
if !crypto.VerifyPassword(password, user.PasswordHash) {
|
||||
s.logService.WriteLoginLog(username, 0, ip, userAgent, "用户名或密码错误")
|
||||
return &LoginResult{Success: false, Message: "用户名或密码错误"}
|
||||
}
|
||||
@@ -222,7 +220,7 @@ func (s *AuthService) loginAsStudent(student *model.Student, password, ip, userA
|
||||
return &LoginResult{Success: false, Message: "用户名或密码错误"}
|
||||
}
|
||||
|
||||
if !crypto.VerifyPassword(password, user.PasswordHash, cfg.PasswordSalt) {
|
||||
if !crypto.VerifyPassword(password, user.PasswordHash) {
|
||||
return &LoginResult{Success: false, Message: "用户名或密码错误"}
|
||||
}
|
||||
|
||||
@@ -288,7 +286,7 @@ func (s *AuthService) tryParentLogin(username, password, ip, userAgent string, c
|
||||
return &LoginResult{Success: false, Message: "用户名或密码错误"}
|
||||
}
|
||||
|
||||
if !crypto.VerifyPassword(password, user.PasswordHash, cfg.PasswordSalt) {
|
||||
if !crypto.VerifyPassword(password, user.PasswordHash) {
|
||||
return &LoginResult{Success: false, Message: "用户名或密码错误"}
|
||||
}
|
||||
|
||||
@@ -340,8 +338,6 @@ func (s *AuthService) Logout(userID int) error {
|
||||
|
||||
// ChangePassword 修改密码
|
||||
func (s *AuthService) ChangePassword(userID int, oldPassword, newPassword string, force bool) error {
|
||||
cfg := config.AppConfig
|
||||
|
||||
user, err := s.userRepo.GetByUserID(userID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("用户不存在")
|
||||
@@ -349,7 +345,7 @@ func (s *AuthService) ChangePassword(userID int, oldPassword, newPassword string
|
||||
|
||||
// 验证原密码(强制改密时跳过)
|
||||
if !force {
|
||||
if !crypto.VerifyPassword(oldPassword, user.PasswordHash, cfg.PasswordSalt) {
|
||||
if !crypto.VerifyPassword(oldPassword, user.PasswordHash) {
|
||||
return fmt.Errorf("原密码错误")
|
||||
}
|
||||
}
|
||||
@@ -360,7 +356,10 @@ func (s *AuthService) ChangePassword(userID int, oldPassword, newPassword string
|
||||
}
|
||||
|
||||
// 更新密码
|
||||
newHash := crypto.HashPassword(newPassword, cfg.PasswordSalt)
|
||||
newHash, err := crypto.HashPassword(newPassword)
|
||||
if err != nil {
|
||||
return fmt.Errorf("密码加密失败")
|
||||
}
|
||||
if err := s.userRepo.UpdatePassword(userID, newHash); err != nil {
|
||||
return fmt.Errorf("密码修改失败")
|
||||
}
|
||||
@@ -458,4 +457,3 @@ func getPasswordChangePath(userType string) string {
|
||||
return "/"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -40,16 +40,13 @@ func (s *SuperAdminService) EnsureDefaultAdmin() error {
|
||||
|
||||
logger.Sugared.Warnf("⚠️ 当前使用默认超级管理员密码,部署环境请务必修改 SUPER_ADMIN_DEFAULT_PASSWORD 并重启服务")
|
||||
|
||||
// 为超级管理员生成独立的随机 Salt
|
||||
salt, err := crypto.GenerateRandomPassword(16)
|
||||
passwordHash, err := crypto.HashPassword(cfg.SuperAdminDefaultPass)
|
||||
if err != nil {
|
||||
return fmt.Errorf("生成随机盐值失败: %w", err)
|
||||
return fmt.Errorf("密码哈希失败: %w", err)
|
||||
}
|
||||
passwordHash := crypto.HashPassword(cfg.SuperAdminDefaultPass, salt)
|
||||
if err := s.superAdminRepo.EnsureDefaultAdmin(
|
||||
cfg.SuperAdminDefaultUser,
|
||||
passwordHash,
|
||||
salt,
|
||||
"系统管理员",
|
||||
); err != nil {
|
||||
return fmt.Errorf("创建默认超级管理员失败: %w", err)
|
||||
@@ -82,7 +79,7 @@ func (s *SuperAdminService) Login(username, password, ip, userAgent string) (map
|
||||
return map[string]interface{}{"success": false, "message": "用户名或密码错误"}, nil
|
||||
}
|
||||
|
||||
if !crypto.VerifyPassword(password, admin.PasswordHash, admin.Salt) {
|
||||
if !crypto.VerifyPassword(password, admin.PasswordHash) {
|
||||
s.logService.WriteLoginLog(username, 0, ip, userAgent, "用户名或密码错误")
|
||||
return map[string]interface{}{"success": false, "message": "用户名或密码错误"}, nil
|
||||
}
|
||||
@@ -120,7 +117,7 @@ func (s *SuperAdminService) Login(username, password, ip, userAgent string) (map
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ChangePassword 超级管理员修改密码(操作 super_admins 表,使用独立 salt)
|
||||
// ChangePassword 超级管理员修改密码
|
||||
func (s *SuperAdminService) ChangePassword(adminID int, oldPassword, newPassword string, force bool) error {
|
||||
admin, err := s.superAdminRepo.GetByID(adminID)
|
||||
if err != nil {
|
||||
@@ -129,7 +126,7 @@ func (s *SuperAdminService) ChangePassword(adminID int, oldPassword, newPassword
|
||||
|
||||
// 验证原密码(强制改密时跳过)
|
||||
if !force {
|
||||
if !crypto.VerifyPassword(oldPassword, admin.PasswordHash, admin.Salt) {
|
||||
if !crypto.VerifyPassword(oldPassword, admin.PasswordHash) {
|
||||
return fmt.Errorf("原密码错误")
|
||||
}
|
||||
}
|
||||
@@ -139,14 +136,12 @@ func (s *SuperAdminService) ChangePassword(adminID int, oldPassword, newPassword
|
||||
return fmt.Errorf("%s", msg)
|
||||
}
|
||||
|
||||
// 生成新的独立 salt
|
||||
newSalt, err := crypto.GenerateRandomPassword(16)
|
||||
newHash, err := crypto.HashPassword(newPassword)
|
||||
if err != nil {
|
||||
return fmt.Errorf("生成随机盐值失败: %w", err)
|
||||
return fmt.Errorf("密码加密失败: %w", err)
|
||||
}
|
||||
newHash := crypto.HashPassword(newPassword, newSalt)
|
||||
|
||||
if err := s.superAdminRepo.UpdatePasswordWithSalt(adminID, newHash, newSalt); err != nil {
|
||||
if err := s.superAdminRepo.UpdatePassword(adminID, newHash); err != nil {
|
||||
return fmt.Errorf("密码修改失败")
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user