feat: 密码强度多级校验 — low/medium/high/very_high
- site_settings 新增 password.strength 配置项 - 四级:low(len>=8) / medium(两类) / high(三类+无连续) / very_high(四类+无连续) - 连续字符检查:重复/升序/降序/键盘水平/键盘垂直序列 - 注册和改密接口统一读取配置 - 前端 register.js 动态校验 + 管理后台安全设置区
This commit is contained in:
@ -166,6 +166,16 @@ func (s *SiteSettings) ForceGuidelines() bool {
|
||||
return s.GetBool("registration.force_guidelines", true)
|
||||
}
|
||||
|
||||
// PasswordStrength 密码强度级别
|
||||
func (s *SiteSettings) PasswordStrength() string {
|
||||
level := s.Get("password.strength", "medium")
|
||||
switch level {
|
||||
case "low", "medium", "high", "very_high":
|
||||
return level
|
||||
}
|
||||
return "medium"
|
||||
}
|
||||
|
||||
// GuidelinesTimer 强制阅读倒计时秒数
|
||||
func (s *SiteSettings) GuidelinesTimer() int {
|
||||
val := s.Get("registration.guidelines_timer", "60")
|
||||
|
||||
@ -57,6 +57,7 @@ func (ac *AuthController) RegisterPage(c *gin.Context) {
|
||||
"ExtraCSS": "/static/css/auth.css",
|
||||
"Guidelines": guidelines,
|
||||
"RegistrationEnabled": ac.authService.IsRegistrationEnabled(),
|
||||
"PasswordStrength": ac.siteSettings.PasswordStrength(),
|
||||
"ShowGuidelinesModal": ac.siteSettings.ShowGuidelinesModal(),
|
||||
"ForceGuidelines": ac.siteSettings.ForceGuidelines(),
|
||||
"GuidelinesTimer": ac.siteSettings.GuidelinesTimer(),
|
||||
|
||||
@ -2,7 +2,9 @@ package service
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode"
|
||||
|
||||
"metazone.cc/mce/internal/common"
|
||||
"metazone.cc/mce/internal/model"
|
||||
@ -14,6 +16,19 @@ import (
|
||||
var pwLower = regexp.MustCompile(`[a-z]`)
|
||||
var pwUpper = regexp.MustCompile(`[A-Z]`)
|
||||
var pwDigit = regexp.MustCompile(`\d`)
|
||||
var pwSymbol = regexp.MustCompile(`[^a-zA-Z0-9]`)
|
||||
|
||||
// 键盘水平序列(QWERTY 布局)
|
||||
var keyboardHoriz = []string{
|
||||
"qwertyuiop", "asdfghjkl", "zxcvbnm",
|
||||
"poiuytrewq", "lkjhgfdsa", "mnbvcxz",
|
||||
}
|
||||
|
||||
// 键盘垂直序列
|
||||
var keyboardVert = []string{
|
||||
"qaz", "wsx", "edc", "rfv", "tgb", "yhn", "ujm", "ik", "ol", "p",
|
||||
"zaq", "xsw", "cde", "vfr", "bgt", "nhy", "mju", "ki", "lo",
|
||||
}
|
||||
|
||||
// dummyHash 防时序攻击:当用户不存在时,仍对虚拟哈希执行完整的 bcrypt 比对
|
||||
var dummyHash []byte
|
||||
@ -26,14 +41,156 @@ func init() {
|
||||
dummyHash = hash
|
||||
}
|
||||
|
||||
// validatePassword 密码强度:至少 8 位 + 包含大写字母 + 包含小写字母 + 包含数字
|
||||
func validatePassword(pw string) error {
|
||||
if len(pw) < 8 || !pwLower.MatchString(pw) || !pwUpper.MatchString(pw) || !pwDigit.MatchString(pw) {
|
||||
// validatePassword 多级密码强度校验
|
||||
func validatePassword(pw string, level string) error {
|
||||
if level == "" {
|
||||
level = "medium"
|
||||
}
|
||||
if len(pw) < 8 {
|
||||
return common.ErrWeakPassword
|
||||
}
|
||||
|
||||
switch level {
|
||||
case "low":
|
||||
return nil
|
||||
case "medium":
|
||||
if !hasAtLeastN(pw, 2) {
|
||||
return common.ErrWeakPassword
|
||||
}
|
||||
case "high":
|
||||
if !hasAtLeastN(pw, 3) {
|
||||
return common.ErrWeakPassword
|
||||
}
|
||||
if isWeakSequence(pw) {
|
||||
return common.ErrWeakPassword
|
||||
}
|
||||
case "very_high":
|
||||
if !hasAtLeastN(pw, 4) {
|
||||
return common.ErrWeakPassword
|
||||
}
|
||||
if isWeakSequence(pw) {
|
||||
return common.ErrWeakPassword
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// hasAtLeastN 检查密码包含至少 n 类字符(小写/大写/数字/符号)
|
||||
func hasAtLeastN(pw string, n int) bool {
|
||||
count := 0
|
||||
if pwLower.MatchString(pw) {
|
||||
count++
|
||||
}
|
||||
if pwUpper.MatchString(pw) {
|
||||
count++
|
||||
}
|
||||
if pwDigit.MatchString(pw) {
|
||||
count++
|
||||
}
|
||||
if pwSymbol.MatchString(pw) {
|
||||
count++
|
||||
}
|
||||
return count >= n
|
||||
}
|
||||
|
||||
// isWeakSequence 检查连续字符(≥3 重复/排序/键盘序列)
|
||||
func isWeakSequence(pw string) bool {
|
||||
runes := []rune(strings.ToLower(pw))
|
||||
if len(runes) < 3 {
|
||||
return false
|
||||
}
|
||||
|
||||
// 重复字符 aaa 111
|
||||
repeatCount := 1
|
||||
// 升序/降序计数
|
||||
ascCount, descCount := 1, 1
|
||||
|
||||
for i := 1; i < len(runes); i++ {
|
||||
// 重复
|
||||
if runes[i] == runes[i-1] {
|
||||
repeatCount++
|
||||
if repeatCount >= 3 {
|
||||
return true
|
||||
}
|
||||
} else {
|
||||
repeatCount = 1
|
||||
}
|
||||
|
||||
// 升序 abc / 123
|
||||
if runes[i] == runes[i-1]+1 {
|
||||
ascCount++
|
||||
if ascCount >= 3 {
|
||||
return true
|
||||
}
|
||||
} else {
|
||||
ascCount = 1
|
||||
}
|
||||
|
||||
// 降序 cba / 321
|
||||
if runes[i] == runes[i-1]-1 {
|
||||
descCount++
|
||||
if descCount >= 3 {
|
||||
return true
|
||||
}
|
||||
} else {
|
||||
descCount = 1
|
||||
}
|
||||
}
|
||||
|
||||
// 键盘序列
|
||||
lower := strings.ToLower(pw)
|
||||
for _, seq := range keyboardHoriz {
|
||||
if len(seq) >= 3 {
|
||||
for i := 0; i <= len(seq)-3; i++ {
|
||||
if strings.Contains(lower, seq[i:i+3]) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, seq := range keyboardVert {
|
||||
if len(seq) >= 3 && strings.Contains(lower, seq) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// PasswordStrengthLevel 获取当前密码强度级别
|
||||
func PasswordStrengthLevel(strengthStr string) string {
|
||||
switch strengthStr {
|
||||
case "low", "medium", "high", "very_high":
|
||||
return strengthStr
|
||||
}
|
||||
return "medium"
|
||||
}
|
||||
|
||||
// PasswordStrengthHint 前端校验提示文案
|
||||
func PasswordStrengthHint(level string) string {
|
||||
switch level {
|
||||
case "low":
|
||||
return "密码至少 8 位"
|
||||
case "medium":
|
||||
return "密码至少 8 位,需包含字母、数字或符号中的两类"
|
||||
case "high":
|
||||
return "密码至少 8 位,需包含大小写字母、数字和符号中的至少三类,且不含连续字符"
|
||||
case "very_high":
|
||||
return "密码至少 8 位,需包含大小写字母、数字和符号全部四类,且不含连续字符"
|
||||
}
|
||||
return "密码至少 8 位"
|
||||
}
|
||||
|
||||
// hasUnicode 检查是否包含非 ASCII 字符
|
||||
func hasUnicode(s string) bool {
|
||||
for _, r := range s {
|
||||
if r > unicode.MaxASCII {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// ChangePassword 修改密码
|
||||
// 流程:验证当前密码 → 校验新密码强度 → 哈希 → 更新 → 删除所有 session(强制重新登录)
|
||||
func (s *AuthService) ChangePassword(userID uint, currentPassword, newPassword string) error {
|
||||
@ -46,7 +203,7 @@ func (s *AuthService) ChangePassword(userID uint, currentPassword, newPassword s
|
||||
return common.ErrIncorrectPassword
|
||||
}
|
||||
|
||||
if err := validatePassword(newPassword); err != nil {
|
||||
if err := validatePassword(newPassword, s.siteSettings.PasswordStrength()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@ -48,7 +48,7 @@ func (s *AuthService) Register(req model.RegisterRequest, regIP string) (*model.
|
||||
}
|
||||
|
||||
// 2. 密码强度
|
||||
if err := validatePassword(req.Password); err != nil {
|
||||
if err := validatePassword(req.Password, s.siteSettings.PasswordStrength()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user