Oracle密码验证函数必须严格使用username、password、old_password三个VARCHAR2参数并返回BOOLEAN,禁止DML、自治事务、外部包调用及副作用操作,需显式授权并重新加载脚本生效。

verify_function签名必须严格匹配三个VARCHAR2参数
Oracle密码验证函数不是普通PL/SQL函数,它只认一种固定接口:必须且仅能有username、password、old_password三个VARCHAR2类型的IN参数,返回类型必须是BOOLEAN。任何偏差都会导致静默失效——比如返回NUMBER或加第四个参数,函数能编译成功,但绑定到profile后改密码时直接报ORA-28003,不提示具体原因。
常见错误写法:CREATE OR REPLACE FUNCTION my_check(p_user VARCHAR2, p_pass VARCHAR2) RETURN VARCHAR2 —— 这种函数即使授权了EXECUTE,也不会被profile调用。
正确骨架长这样:
CREATE OR REPLACE FUNCTION my_verify_func ( username IN VARCHAR2, password IN VARCHAR2, old_password IN VARCHAR2 ) RETURN BOOLEAN IS BEGIN IF password IS NULL THEN RETURN FALSE; END IF; -- 后续校验逻辑... RETURN TRUE; END;
函数体内禁止DML、自治事务和副作用操作
密码验证发生在用户登录或ALTER USER ... IDENTIFIED BY过程中,此时数据库处于鉴权上下文,不允许写表、提交事务或调用含状态变更的包。否则会触发ORA-06502或ORA-00604等深层错误,且调试困难。
- 不能执行
INSERT INTO log_table记录密码变更(想记日志得另走审计或触发器) - 不能调用
DBMS_OUTPUT.PUT_LINE——输出不可见,还可能因权限缺失导致校验中断 - 不能用
COMMIT或PRAGMA AUTONOMOUS_TRANSACTION - 避免调用
UTL_HTTP、UTL_FILE等外部访问包
如果真需要留痕,建议用DBMS_APPLICATION_INFO.SET_CLIENT_INFO临时标记,再配合审计视图查行为。
强规则要拆解成独立原子检查,别堆AND
把“长度≥8且含大写且含小写且含数字且含特殊字符”写成一行IF LENGTH(...) >= 8 AND REGEXP_LIKE(...) AND ...,容易漏边界:比如密码为"Aa1!"(长度4)会因第一个条件失败而跳过后续所有检查,但你其实想知道它到底缺哪项。
更可靠的做法是逐项判断并提前返回:
IF LENGTH(password) < 8 THEN RETURN FALSE; END IF; IF REGEXP_LIKE(password, '[A-Z]') = FALSE THEN RETURN FALSE; END IF; IF REGEXP_LIKE(password, '[a-z]') = FALSE THEN RETURN FALSE; END IF; IF REGEXP_LIKE(password, '[0-9]') = FALSE THEN RETURN FALSE; END IF; IF REGEXP_LIKE(password, '[^a-zA-Z0-9]') = FALSE THEN RETURN FALSE; END IF;
注意:REGEXP_LIKE在10g+可用,但某些字符集下对中文或控制字符匹配异常;若环境不稳定,改用INSTR组合判断更稳妥,例如检查数字:INSTR(TRANSLATE(password, '0123456789', '##########'), '#') > 0。
绑定profile前必须显式授权并确认生效路径
函数创建在SYS下只是第一步。必须执行GRANT EXECUTE ON my_verify_func TO PUBLIC(或至少TO SYS),否则ALTER PROFILE会报权限不足。
真正起作用的是profile里的PASSWORD_VERIFY_FUNCTION字段,不是函数名本身。务必确认:
- 查当前用户用哪个profile:
SELECT profile FROM dba_users WHERE username = 'SCOTT' - 查该profile绑定了什么函数:
SELECT limit FROM dba_profiles WHERE profile = 'DEFAULT' AND resource_name = 'PASSWORD_VERIFY_FUNCTION' - 如果返回
NULL或旧函数名,用ALTER PROFILE DEFAULT LIMIT PASSWORD_VERIFY_FUNCTION my_verify_func更新
最后测试一定要用ALTER USER scott IDENTIFIED BY "NewPass123!",而不是CREATE USER——后者可能绕过部分校验链。
最易忽略的一点:函数修改后,必须重新运行@?/rdbms/admin/utlpwdmg.sql(注意是@命令,不是粘贴内容),再ALTER PROFILE,否则数据库仍加载旧版本字节码。


















