业务场景
某电子厂 SMT/组装车间还在用老 MES。操作员、班组长、工程师用工号加密码登录,用来报工、查工单、看工艺参数。安全审计和客户审厂时发现,很多账号还是初始密码,密码规则也简单,同一个工号可以一直试。弱密码容易被猜中,也可能被脚本暴力破解,共享账号冒用也不好查。
涉及系统
老 MES:登录入口、用户表、密码校验、改密页面、锁定策略。
HR/员工主数据系统:给 MES 同步工号、在职状态、初始密码标记。
Redis:存登录失败次数、验证码、临时锁定状态。
短信/邮件/企业消息网关:发验证码。
审计/安全平台:记登录失败、改密、锁定、解锁日志。
AD/LDAP/统一认证:这期没接,后面再考虑。
原始需求
安全部门和 IT 要求老 MES 登录策略整改,满足等保和客户审厂,同时不能大改系统,不能影响产线登录。原来问题:初始密码长期不改,工号后六位、123456 这类容易猜到;密码没有复杂度要求;登录失败不限次数;失败日志分散,异常账号不好找;如果全量强制改密,可能影响开班和夜班。
排查/分析过程
先从 MES 登录日志和用户表查。看到同一工号短时间大量失败,部分账号长期没改初始密码。数据流是 HR 把工号、状态、初始密码标记同步到 MES 用户表;用户登录时 MES 查用户表校验密码;失败只写普通日志,没有计数和锁定;成功后发会话。确定在认证链路加三件事:登录时识别初始密码,强制跳改密;改密时后端校验复杂度,达到强才让改;登录失败累计,超过阈值弹验证码,再错锁定。前端提示可以做,但后端必须校验。
方案设计
考虑过三种。
A 全量强制重置密码。安全高,但影响所有产线人员,容易停线投诉。
B 只在前端加复杂度提示和初始密码提示。成本低,但能绕过接口,效果差。
C 后端渐进加固。登录识别初始密码并跳改密;改密接口强校验;失败计数放 Redis,超过次数要求验证码,再错锁定;管理员可解锁。
最后选 C。改造成本可控,兼容老 MES,能覆盖弱密码和暴力破解,也能灰度上线。
技术实现
接口:
POST /api/login 入参 account、password、captcha;返回 code:0 成功,1001 需改初始密码,1002 需验证码,1003 账号锁定,1004 密码错误。
POST /api/password/change 入参 oldPassword、newPassword、confirmPassword;后端校验强度。
GET /api/captcha 生成图形或短信验证码。
POST /api/account/unlock 管理员解锁。表结构:
bash
ALTER TABLE t_user ADD (
is_init_pwd TINYINT DEFAULT 1,
pwd_strength VARCHAR(10),
pwd_update_time DATETIME,
fail_count INT DEFAULT 0,
lock_status TINYINT DEFAULT 0,
lock_time DATETIME,
last_login_ip VARCHAR(64)
);
CREATE TABLE t_login_fail_log (
id BIGINT,
account VARCHAR(64),
login_ip VARCHAR(64),
fail_reason VARCHAR(128),
create_time DATETIME
);
Redis 键:login:fail:{account}、login:captcha:{account}、login:lock:{account}。
伪代码:
bash
login(account, password, captcha) {
user = queryUser(account);
if (user.lockStatus == 1) return ACCOUNT_LOCKED;
if (failCount >= 5 && !verifyCaptcha(account, captcha)) return NEED_CAPTCHA;
if (!verifyPassword(password, user.pwdHash)) {
failCount = incrFail(account);
if (failCount >= 5) return NEED_CAPTCHA;
if (failCount >= 6) lockAccount(account);
return PASSWORD_ERROR;
}
if (user.isInitPwd == 1) return INIT_PWD_NEED_CHANGE;
clearFail(account);
return SUCCESS;
}
changePassword(account, oldPwd, newPwd) {
if (!verifyPassword(oldPwd, user.pwdHash)) return OLD_PWD_ERROR;
if (!isStrong(newPwd)) return WEAK_PWD;
if (contains(account, birthday, phone, newPwd)) return WEAK_PWD;
updatePwd(account, hash(newPwd), isInitPwd = 0, pwdStrength = "STRONG");
auditLog(account, "CHANGE_PWD");
}
复杂度规则:长度不少于 12 位,大小写字母、数字、特殊字符至少三类,不能包含工号、生日、手机号,强度达到强才允许提交。日志里账号脱敏成 U***,手机号 138****,密码不落日志。
上线效果
上线后,产线登录流程基本没变化,只有初始密码账号被引导改密,影响可控。初始密码未改比例从约 35% 降到 3% 以下。同一账号短时间暴力失败尝试下降约 90%。锁定和验证码触发都能审计、能告警。客户审厂相关登录安全项关闭,安全部门能查异常 IP、异常账号和锁定记录。
踩坑与反思
锁定阈值一开始设得太激进,差点误伤夜班产线账号。后来改成先验证码、再锁定,并保留管理员快速解锁。
初始密码标记依赖 HR 同步,同步延迟会误判。后来加了定时校准和手工修正入口。
短信验证码有延迟,夜班体验差。后来补了图形验证码和企业消息通道。
失败计数放 Redis,要考虑宕机降级和 DB 一致性。后面改成本地缓存加异步落库。
前端复杂度校验只能提示,后端必须强校验。
锁定策略没覆盖共享账号、多设备登录。后面要接 AD/SSO、MFA、密码历史和风险自适应认证。
后续先灰度再全量,提前准备白名单、自助解锁和审计看板,保证能观测、能回滚。