☰
19-ORA-28000账号锁定与密码策略
2026/10/15 10:10:49 网站建设 项目流程

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的代价,想清楚再敲:

  1. 暴力破解欢迎光临:锁定机制本质上是防爆破的,10 次就锁让撞库成本变得极高;放开之后,一个弱密码账号可以被无限尝试
  2. 等保/安全审计过不去:安全扫描项里几乎必有"失败登录锁定策略",UNLIMITED 是典型扣分项
  3. 掩盖问题而不是解决问题:不锁了,应用里那份错密码依然在错,只是从"告警"变成了"慢性失血",更难发现

更合理的做法是给应用账号单独建 PROFILE,人和系统分开管:

-- 应用账号:失败放宽一些,密码永不过期,但保留锁定CREATEPROFILE APP_PROFILELIMITFAILED_LOGIN_ATTEMPTS30PASSWORD_LIFE_TIME UNLIMITED PASSWORD_LOCK_TIME10/1440;ALTERUSERappuser PROFILE APP_PROFILE;-- 运维/人用的账号:维持 DEFAULT 的严格策略不动

这样应用偶尔抖动不至于把自己锁死,而人工账号仍受严格保护。


六、预防

  1. 改密流程里固化"改库必改配置":数据库密码、应用配置、配置中心、定时任务凭据四处同步
  2. 应用连接池的重试参数加上限(如 maxRetries=3、退避间隔递增),避免错密码演变成每秒几十次的失败风暴
  3. dba_users.account_status进每日巡检,LOCKED / EXPIRED(GRACE) 状态即时告警
  4. 应用账号与人工账号分离,各挂各的 PROFILE——策略诉求本来就不同,没必要绑在一起
  5. 定期(每月)跑一次失败登录统计,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 函数库)。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询