ORA-28000:账号被锁——FAILED_LOGIN_ATTEMPTS 的来龙去脉
凌晨两点告警:应用全挂,报 ORA-28000 the account is locked。解锁之后十分钟又锁了。账号锁定几乎从来不是"有人手滑输错密码",而是某处配置里的密码和数据库不一致,应用在后台疯狂重试。这篇讲清楚锁定机制、解锁命令、怎么找到"是谁在错",以及改 PROFILE 之前要想清楚的事。
文章目录
- ORA-28000:账号被锁——FAILED_LOGIN_ATTEMPTS 的来龙去脉
- 一、报错现象
- 二、为什么会锁:FAILED_LOGIN_ATTEMPTS 机制
- 三、解决步骤
- 第一步:查账号状态
- 第二步:解锁(顺带改密)
- 第三步:找到是谁在错——这一步不做,十分钟后再锁一次
- 第四步:修掉错误源头
- 四、验证
- 五、PROFILE 密码策略全景与"永久不锁"的代价
- 六、预防
- 七、批量解锁:一次锁一片的应急脚本
一、报错现象
ORA-28000: the account is locked客户端表现:登录直接被拒,密码改对了也没用——锁是加在账号上的,和密码对错无关。
二、为什么会锁:FAILED_LOGIN_ATTEMPTS 机制
每个账号都挂在一个 PROFILE 上,PROFILE 里有条规则:
FAILED_LOGIN_ATTEMPTS:允许的连续登录失败次数,11g 起的 DEFAULT profile 默认10 次- 连续失败达到 10 次 → 账号进入
LOCKED(TIMED)状态 PASSWORD_LOCK_TIME:锁多久自动解开,默认1 天(一天,不是一小时)
注意"连续"两个字:中间成功登录一次,失败计数就清零。
看看自己库里的实际配置(不同版本默认值有差异,以查询结果为准):
SELECTresource_name,limitFROMdba_profilesWHEREprofile='DEFAULT'ANDresource_type='PASSWORD';重点看这几行:
RESOURCE_NAME LIMIT -------------------------------- ---------- FAILED_LOGIN_ATTEMPTS 10 PASSWORD_LIFE_TIME 180 PASSWORD_LOCK_TIME 1 PASSWORD_GRACE_TIME 7最常见的真实剧本:应用换了台服务器部署,配置文件里的密码是旧的 → 连接池启动即失败 → 连接池按秒级重试 → 10 次失败几秒内用完 → 账号锁定 → 告警电话。
三、解决步骤
第一步:查账号状态
SELECTusername,account_status,lock_date,expiry_date,profileFROMdba_usersWHEREusername='APPUSER';状态对照:
| account_status | 含义 | 处理 |
|---|---|---|
| OPEN | 正常 | 无 |
| LOCKED(TIMED) | 失败次数超限,等 PASSWORD_LOCK_TIME 自动解锁 | 手动解锁,见第二步 |
| LOCKED | 管理员手动锁的 | 确认该不该开,再手动解锁 |
| EXPIRED | 密码过期 | 改密,见系列第 18 篇 |
| EXPIRED & LOCKED(TIMED) | 过期 + 锁定双重 | 先解锁再改密 |
lock_date值得看一眼:锁定发生的时间,和日志、变更单对时间,往往直接指向根因。
第二步:解锁(顺带改密)
只解锁:
ALTERUSERappuser ACCOUNTUNLOCK;解锁同时改密(推荐,一步到位):
ALTERUSERappuser IDENTIFIEDBY"新密码"ACCOUNTUNLOCK;不想等 1 天自动解锁、又不想动密码时,手动解锁是最快的路径。解锁后失败计数归零。
第三步:找到是谁在错——这一步不做,十分钟后再锁一次
光解锁是治不了病的。11g 起登录审计默认开启,直接查失败记录:
SELECTusername,userhost,terminal,timestamp,returncodeFROMdba_audit_sessionWHEREreturncodeIN(1017,28000)ANDtimestamp>SYSDATE-1/24-- 最近一小时ORDERBYtimestamp;returncode = 1017:密码错误(ORA-01017)userhost/terminal:从哪台机器发起的失败登录——这就是要找的元凶
一屏典型输出:
USERNAME USERHOST TIMESTAMP RETURNCODE ---------- --------------------- --------------------- ---------- APPUSER WORKSTATION\ops-pc01 2026-09-28 01:58:12 1017 APPUSER WORKSTATION\ops-pc01 2026-09-28 01:58:13 1017每秒一次失败,来自 ops-pc01——去查这台机器上的定时任务或客户端配置。
如果dba_audit_session查不到数据(审计被关了),退而求其次查监听日志里的连接来源 IP,结合锁定时间点比对:
grep"28-SEP-2026 01:5"$ORACLE_BASE/diag/tnslsnr/*/listener/trace/listener.log|head第四步:修掉错误源头
按 userhost 指到的位置处理:改配置文件里的密码、停掉用旧凭据的定时任务、收回散落的连接脚本。先修源头,再解锁,顺序反了就是反复锁。
四、验证
-- 1. 状态回到 OPENSELECTusername,account_statusFROMdba_usersWHEREusername='APPUSER';-- 2. 应用会话恢复SELECTmachine,COUNT(*)FROMv$sessionWHEREusername='APPUSER'GROUPBYmachine;-- 3. 持续观察,不再有新的失败登录SELECTCOUNT(*)FROMdba_audit_sessionWHEREreturncode=1017ANDtimestamp>SYSDATE-10/1440;第三条返回 0 且持续为 0,才算真正闭环。
五、PROFILE 密码策略全景与"永久不锁"的代价
改 PROFILE 是很多人想到的第一招,但它应该放在最后:
-- 调大失败次数上限到 30ALTERPROFILEDEFAULTLIMITFAILED_LOGIN_ATTEMPTS30;-- 锁定时间从 1 天改成 10 分钟(单位是天,支持小数:10/1440 天 ≈ 10 分钟)ALTERPROFILEDEFAULTLIMITPASSWORD_LOCK_TIME10/1440;-- 无限失败、永不锁定ALTERPROFILEDEFAULTLIMITFAILED_LOGIN_ATTEMPTS UNLIMITED;FAILED_LOGIN_ATTEMPTS UNLIMITED的代价,想清楚再敲:
- 暴力破解欢迎光临:锁定机制本质上是防爆破的,10 次就锁让撞库成本变得极高;放开之后,一个弱密码账号可以被无限尝试
- 等保/安全审计过不去:安全扫描项里几乎必有"失败登录锁定策略",UNLIMITED 是典型扣分项
- 掩盖问题而不是解决问题:不锁了,应用里那份错密码依然在错,只是从"告警"变成了"慢性失血",更难发现
更合理的做法是给应用账号单独建 PROFILE,人和系统分开管:
-- 应用账号:失败放宽一些,密码永不过期,但保留锁定CREATEPROFILE APP_PROFILELIMITFAILED_LOGIN_ATTEMPTS30PASSWORD_LIFE_TIME UNLIMITED PASSWORD_LOCK_TIME10/1440;ALTERUSERappuser PROFILE APP_PROFILE;-- 运维/人用的账号:维持 DEFAULT 的严格策略不动这样应用偶尔抖动不至于把自己锁死,而人工账号仍受严格保护。
六、预防
- 改密流程里固化"改库必改配置":数据库密码、应用配置、配置中心、定时任务凭据四处同步
- 应用连接池的重试参数加上限(如 maxRetries=3、退避间隔递增),避免错密码演变成每秒几十次的失败风暴
dba_users.account_status进每日巡检,LOCKED / EXPIRED(GRACE) 状态即时告警- 应用账号与人工账号分离,各挂各的 PROFILE——策略诉求本来就不同,没必要绑在一起
- 定期(每月)跑一次失败登录统计,
dba_audit_session里 1017 集中出现就是配置漂移的早期信号
七、批量解锁:一次锁一片的应急脚本
应用共用一个账号时,一个配置错误可能连带锁掉一批测试账号。手工一个个敲效率太低:
-- 生成批量解锁语句(只解 LOCKED 状态,EXPIRED 的留给改密流程)SELECT'ALTER USER '||username||' ACCOUNT UNLOCK;'FROMdba_usersWHEREaccount_statusLIKE'LOCKED%';把输出直接当脚本执行即可。想在 SQL*Plus 里一条命令跑完:
BEGINFORrecIN(SELECTusernameFROMdba_usersWHEREaccount_statusLIKE'LOCKED%')LOOPEXECUTEIMMEDIATE'ALTER USER '||rec.username||' ACCOUNT UNLOCK';DBMS_OUTPUT.PUT_LINE('unlocked: '||rec.username);ENDLOOP;END;/应急时先解锁恢复业务,再回头按第三步查失败来源——顺序可以倒,两步都不能少。
✅ 亮点:不满足于"解锁命令一条",给出用 dba_audit_session 的 userhost 定位失败来源的完整闭环,以及"先修源头再解锁"的顺序原则;对 UNLIMITED 的代价做了直白剖析,提出应用/人工分 PROFILE 的折中方案。扩展方向:统一审计(unified auditing)下的等价查询、数据库端的弱密码扫描(digoal 的 pwdverify 函数库)。