Aekor

Aekor
专注于用户阅读体验的响应式博客主题
  1. 首页
  2. Oracle 数据库
  3. 正文

Oracle 账号锁死与密码保活:用存储过程批量处理

2026-03-08 19466点热度 0人点赞 0条评论

账号被锁、密码到期,是值班时最常见的两类工单。单条 alter user 谁都会写,但一批账号同时锁死、或者希望在不改密码的前提下延长有效期时,手工操作既慢又容易漏。下面这个 USER_MGR 包把两个动作封装起来:unlock_user 解锁,reset_pwd 在不改变密码的前提下把“上次修改时间”推到当前,绕过密码有效期。

环境是 Oracle 19c,包建在 SYS 下,以 SYSDBA 身份执行。这段代码会操作密码哈希和 PROFILE,属于高权限操作,只应放在受控的运维库里,不要授予普通应用账号。

一、创建包与包体

create or replace package user_mgr is

-- Author  : 止水
-- Created : 2025/08/05 10:00:00
-- Purpose : unlock user and reset password
-- Public procedure declarations
procedure unlock_user(v_username in dba_users.username%type);

procedure reset_pwd(v_username in user$.name%type);
end user_mgr;
/

create or replace package body user_mgr is

procedure unlock_user(v_username in dba_users.username%type) is
v_sql            varchar2(1000);
v_account_status dba_users.account_status%type;
exc_is_unlock exception;
begin
v_sql := 'alter user ' || v_username || ' account unlock';
select account_status into v_account_status from dba_users where username =
v_username;

if instr(v_account_status, 'LOCKED') = 0 then
raise exc_is_unlock;
end if;
execute immediate v_sql;
dbms_output.put_line('The user has been updated succesfully.');
select account_status into v_account_status from dba_users where username =
v_username;

dbms_output.put_line('The status of ' || v_username || ' is ' || v_account_s
tatus || '.');

exception
when no_data_found then
raise_application_error(-20001, 'No found the user.');
when exc_is_unlock then
raise_application_error(-20002, 'The status of user isn''t locked.');
when others then
raise_application_error(-20003, 'Unknown error');
end unlock_user;

procedure reset_pwd(v_username in user$.name%type) is
v_sql        varchar2(1000);
v_pwd        user$.password%type;
v_reuse_time dba_profiles.limit%type;
v_reuse_max  dba_profiles.limit%type;
exc_no_profile exception;
begin
begin
select LIMIT
into v_reuse_time
from dba_profiles
where PROFILE = 'DEFAULT'
and RESOURCE_NAME = 'PASSWORD_REUSE_TIME';
select LIMIT
into v_reuse_max
from dba_profiles
where PROFILE = 'DEFAULT'
and RESOURCE_NAME = 'PASSWORD_REUSE_MAX';
exception
when no_data_found then
raise exc_no_profile;
end;
select password into v_pwd from user$ where name = v_username;
v_sql := 'alter user ' || v_username || ' identified by values ''' || v_pwd
|| '''';

execute immediate 'alter profile default limit PASSWORD_REUSE_TIME UNLIMITED
PASSWORD_REUSE_MAX UNLIMITED';

execute immediate v_sql;
execute immediate 'alter profile default limit PASSWORD_REUSE_TIME ' || v_re
use_time || ' PASSWORD_REUSE_MAX ' || v_reuse_max;

dbms_output.put_line('The password of ' || v_username || ' has been reseted
succesfully.');

exception
when exc_no_profile then
raise_application_error(-20004, 'No found the PROFILE.');
when no_data_found then
raise_application_error(-20005, 'No found the user.');
when others then
raise_application_error(-20006, 'Unknown error.');
end reset_pwd;
end;
/

二、两段逻辑分别在做什么

unlock_user

先查 dba_users.account_status,只有包含 LOCKED 才执行解锁,否则抛 -20002。这样做的好处是重复调用不会报错,脚本可以批量跑。

注意它只处理 LOCKED,不处理 EXPIRED 和 EXPIRED(GRACE)。账号状态是 EXPIRED & LOCKED 时,先解锁仍然是 EXPIRED,还得改密码或执行下面的 reset_pwd。

reset_pwd

核心是这一句:

alter user <user> identified by values '<密码哈希>';

identified by values 直接写入密码哈希,不改变密码本身,但会把 DBA_USERS.PASSWORD_CHANGE_DATE(或 EXPIRY_DATE)刷新到当前时间,相当于“续期”。

问题在于:如果 PROFILE 里配了 PASSWORD_REUSE_TIME / PASSWORD_REUSE_MAX,Oracle 会认为“新密码和旧密码相同”而拒绝,报 ORA-28007。所以包体的做法是临时把这两项改成 UNLIMITED,执行完再恢复原值。

这个“改全局 PROFILE 再改回来”的动作有风险:

  • 中间失败会导致 DEFAULT PROFILE 停留在 UNLIMITED,所有用户的密码复用限制都失效。
  • 执行期间其他会话如果正好改密码,可能受瞬时配置影响。
  • 包体里恢复语句在 execute immediate 之后,如果 alter user 抛异常,恢复语句不会执行——异常分支里没有 finally 语义。

生产环境建议改成:先查出原值,用 begin ... exception ... end 包住 alter user,在异常处理里也恢复 PROFILE。或者干脆建一个专用的临时 PROFILE 给目标用户,改完再切回原 PROFILE,避免动 DEFAULT。

三、调用方式

SQL*Plus 里直接 exec:

-- 账号密码延期,不更改密码
SQL> exec sys.user_mgr.reset_pwd('username');

-- 账号解锁
SQL> exec sys.user_mgr.unlock_user('username');

SQL Developer、DBeaver 等工具里用匿名块:

-- 账号密码延期,不更改密码
BEGIN
    sys.user_mgr.reset_pwd('username');
END;
/

-- 账号解锁
BEGIN
    sys.user_mgr.unlock_user('username');
END;
/

用户名区分大小写,必须和 dba_users.username 完全一致。默认是 ALTER SESSION SET NLS_COMP=BINARY 下的精确匹配,传小写会找不到用户。

批量处理

把用户名放进一张临时表,用循环调用:

begin
  for r in (select username from dba_users
             where account_status like '%LOCKED%'
               and username not in ('SYS','SYSTEM','DBSNMP','APPQOSSYS'))
  loop
    begin
      user_mgr.unlock_user(r.username);
    exception
      when others then
        dbms_output.put_line(r.username || ': ' || sqlerrm);
    end;
  end loop;
end;
/

内层 begin ... exception 不能省,否则一个账号失败会中断整个循环。

四、19c 上的注意事项

user$.password 这个写法在 11g 及以前是标准做法。12c 之后密码哈希迁移到 SYS.USER$.SPARE4(S:... 格式),PASSWORD 列在很多版本上为空或只有旧格式哈希。在 19c 上直接 select password from user$ 可能拿到空值,拼出来的语句就是 identified by values '',执行后密码被清空,属于严重事故。

上线前必须先在测试库验证这两点:

-- 1. 目标用户是否还有可用的 PASSWORD 值
select name, password, spare4 from user$ where name = 'YOUR_USER';

-- 2. 包体在 19c 上能否正常执行并保持原密码可登录

如果 PASSWORD 为空,正确做法是使用 spare4,或者改用 alter user ... identified by values 'S:...' 的完整哈希。这一点原文没有验证,属于待核对项,不要直接在生产 19c 上跑。

另外 dba_users.password 在 19c 默认也不可见(受 _enable_password_verifier 等参数影响),需要 SYS 权限。

五、更稳妥的替代方案

如果目的只是“别让密码过期”,比改哈希更安全的做法是:

-- 查当前策略
select profile, resource_name, limit
from dba_profiles
where resource_name in ('PASSWORD_LIFE_TIME','PASSWORD_GRACE_TIME','FAILED_LOGIN_ATTEMPTS');

-- 对指定 PROFILE 调整有效期
alter profile default limit password_life_time unlimited;

代价是所有用该 PROFILE 的账号都不再强制改密码,安全等级下降,需要配合其他审计手段。更精细的做法是给这类服务账号单独建一个 PROFILE。

无论用哪种方式,不要把明文密码写进脚本、crontab 或本文这类文档。上面的 orapwd 和 sqlplus 示例统一用 your_password 占位,实际执行时从受控的密码管理系统取。

六、要点

  1. unlock_user 只解 LOCKED,EXPIRED 要另外处理。
  2. reset_pwd 依赖 user$.password,19c 上需先验证哈希是否存在。
  3. 临时改 DEFAULT PROFILE 有全局副作用,异常路径必须能恢复。
  4. 批量调用时给每个用户包一层异常处理。
  5. 生产环境优先考虑调整 PROFILE 有效期,而不是改密码哈希。

原文首发于微信公众号「止水研习社」。

本作品采用 知识共享署名 4.0 国际许可协议 进行许可
标签: Oracle 存储过程 账号安全
最后更新:2026-10-17

Aekor

这个人很懒,什么都没留下

点赞
< 上一篇
下一篇 >

文章评论

razz evil exclaim smile redface biggrin eek confused idea lol mad twisted rolleyes wink cool arrow neutral cry mrgreen drooling persevering
取消回复

使用AI教程

  • API报错解决方案
  • API 基础知识
  • API Key 获取
  • 最新接入教程

分类

  • Blog
  • Oracle 数据库
  • TradingAgents-CN
  • 使用教程
  • 数据库

COPYRIGHT © 2026 Aekor. ALL RIGHTS RESERVED.

Theme Kratos Made By Seaton Jiang