OceanBase 数据库 Oracle 模式的密码复杂度策略,兼容 Oracle 数据库的密码策略。为了防止恶意的密码攻击,提升数据库的安全性,OceanBase 数据库用户可以根据需要,设置密码的复杂度函数,验证用户登录身份。
设置密码复杂度并启用密码复杂度验证
OceanBase 数据库的 Oracle 模式提供了 PASSWORD_VERIFY_FUNCTION 参数来验证密码复杂度是否符合要求。
PASSWORD_VERIFY_FUNCTION 参数允许将 PL 的密码复杂性验证函数作为参数传递给 CREATE PROFILE 和 ALTER PROFIL 语句。
使用管理员用户登录到数据库的 Oracle 租户。
编写
PASSWORD_VERIFY_FUNCTION参数对应的函数并授权。以下以设置密码复杂度为如下规则,提供操作指导。
密码长度最小为 8 个字符,最大不超过 256 个字符。
至少包含 1 个大写字母、1 个小写字母和 1 个特殊字符。
密码不能是用户名,不能是用户名的反序,也不能是服务器名。
密码不能是
welcome1、database1、account1、user1234、password1、oracle123、computer1、abcdefg1、change_on_install、oracle等过于简单的密码。如果修改密码,新密码与旧密码至少要有 3 个以上的字符不相同。
示例如下:
delimiter // CREATE OR REPLACE FUNCTION complexity_check (password varchar2, chars integer := NULL, letter integer := NULL, upper_c integer := NULL, lower_c integer := NULL, digit integer := NULL, special integer := NULL) RETURN boolean IS digit_array varchar2(10) := '0123456789'; alpha_array varchar2(26) := 'abcdefghijklmnopqrstuvwxyz'; cnt_letter integer := 0; cnt_upper integer := 0; cnt_lower integer := 0; cnt_digit integer := 0; cnt_special integer := 0; flag boolean := FALSE; len INTEGER := NVL(length(password), 0); i integer ; ch CHAR(1); BEGIN -- Check that the password length does not exceed 2 * (max DB pwd len) -- The maximum length of any DB User password is 128 bytes. -- This limit improves the performance of the Edit Distance calculation -- between old and new passwords. IF len > 256 THEN raise_application_error(-20020, 'Password length more than 256'); END IF; -- Classify each character in the password. FOR i in 1..len LOOP ch := substr(password, i, 1); IF ch = '"' THEN flag := TRUE; ELSIF instr(digit_array, ch) > 0 THEN cnt_digit := cnt_digit + 1; ELSIF instr(alpha_array, LOWER(ch)) > 0 THEN cnt_letter := cnt_letter + 1; IF ch = LOWER(ch) THEN cnt_lower := cnt_lower + 1; ELSE cnt_upper := cnt_upper + 1; END IF; ELSE cnt_special := cnt_special + 1; END IF; END LOOP; IF flag = 1 THEN raise_application_error(-20012, 'password must NOT contain a ' || 'double-quote character, which is ' || 'reserved as a password delimiter'); END IF; IF chars IS NOT NULL AND len < chars THEN raise_application_error(-20001, 'Password length less than ' || chars); END IF; IF letter IS NOT NULL AND cnt_letter < letter THEN raise_application_error(-20022, 'Password must contain at least ' || letter || ' letter(s)'); END IF; IF upper_c IS NOT NULL AND cnt_upper < upper_c THEN raise_application_error(-20023, 'Password must contain at least ' || upper_c || ' uppercase character(s)'); END IF; IF lower_c IS NOT NULL AND cnt_lower < lower_c THEN raise_application_error(-20024, 'Password must contain at least ' || lower_c || ' lowercase character(s)'); END IF; IF digit IS NOT NULL AND cnt_digit < digit THEN raise_application_error(-20025, 'Password must contain at least ' || digit || ' digit(s)'); END IF; IF special IS NOT NULL AND cnt_special < special THEN raise_application_error(-20026, 'Password must contain at least ' || special || ' special character(s)'); END IF; RETURN(TRUE); END; // CREATE OR REPLACE FUNCTION string_distance (s varchar2, t varchar2) RETURN integer IS s_len INTEGER := NVL (length(s), 0); t_len INTEGER := NVL (length(t), 0); TYPE arr_type is TABLE OF NUMBER INDEX BY BINARY_INTEGER; d_col arr_type ; dist INTEGER := 0; BEGIN IF s_len = 0 THEN dist := t_len; ELSIF t_len = 0 THEN dist := s_len; ELSE FOR j IN 1 .. (t_len+1) * (s_len+1) - 1 LOOP d_col(j) := 0 ; END LOOP; FOR i IN 0 .. s_len LOOP d_col(i) := i; END LOOP; FOR j IN 1 .. t_len LOOP d_col(j * (s_len + 1)) := j; END LOOP; FOR i IN 1.. s_len LOOP FOR j IN 1 .. t_len LOOP IF substr(s, i, 1) = substr(t, j, 1) THEN d_col(j * (s_len + 1) + i) := d_col((j-1) * (s_len+1) + i-1) ; ELSE d_col(j * (s_len + 1) + i) := LEAST ( d_col( j * (s_len+1) + (i-1)) + 1, -- Deletion d_col((j-1) * (s_len+1) + i) + 1, -- Insertion d_col((j-1) * (s_len+1) + i-1) + 1 ) ; -- Substitution END IF ; END LOOP; END LOOP; dist := d_col(t_len * (s_len+1) + s_len); END IF; RETURN (dist); END; // CREATE OR REPLACE FUNCTION verify_function (username varchar2, password varchar2, old_password varchar2) // 此例中自定义了三个函数参数---用户名 username、新密码 password 和旧密码 old_password RETURN boolean IS differ integer; pw_lower varchar2(256); db_name varchar2(40); i integer; simple_password varchar2(10); reverse_user varchar2(32); BEGIN IF NOT complexity_check(password, 8, 1, 1) THEN RETURN(FALSE); END IF; -- Check if the password contains the username //验证新密码中是否包含用户名 pw_lower := LOWER(password); IF instr(pw_lower, LOWER(username)) > 0 THEN raise_application_error(-20002, 'Password contains the username'); END IF; -- Check if the password contains the username reversed //验证新密码中是否包含用户名 reverse_user := ''; FOR i in REVERSE 1..length(username) LOOP reverse_user := reverse_user || substr(username, i, 1); END LOOP; IF instr(pw_lower, LOWER(reverse_user)) > 0 THEN raise_application_error(-20003, 'Password contains the username ' || 'reversed'); END IF; -- Check if the password contains the server name //验证新密码中是否包含服务器名 db_name := 'oceanbase'; IF instr(pw_lower, LOWER(db_name)) > 0 THEN raise_application_error(-20004, 'Password contains the server name'); END IF; -- Check if the password contains 'oracle' //验证新密码中是否包含"oracle" IF instr(pw_lower, 'oracle') > 0 THEN raise_application_error(-20006, 'Password too simple'); END IF; -- Check if the password is too simple. A dictionary of words may be //验证新密码是否过于简单 -- maintained and a check may be made so as not to allow the words -- that are too simple for the password. IF pw_lower IN ('welcome1', 'database1', 'account1', 'user1234', 'password1', 'oracle123', 'computer1', 'abcdefg1', 'change_on_install') THEN raise_application_error(-20006, 'Password too simple'); END IF; -- Check if the password differs from the previous password by at least -- 3 characters //验证新密码与旧密码是否至少有 3 个以上的字符不相同 IF old_password IS NOT NULL THEN differ := string_distance(old_password, password); IF differ < 3 THEN raise_application_error(-20010, 'Password should differ from the ' || 'old password by at least 3 characters'); END IF; END IF ; RETURN(TRUE); END; // delimiter ; obclient> GRANT EXECUTE ON verify_function TO PUBLIC; obclient> CREATE PUBLIC SYNONYM verify_function FOR sys.verify_function;创建函数的相关操作,请参见 创建函数。
在 User 的 Profile 中,指定
PASSWORD_VERIFY_FUNCTION参数,启用密码复杂度验证。语句如下:
obclient> ALTER PROFILE profile_name LIMIT PASSWORD_VERIFY_FUNCTION verify_function;其中,
profile_name可以使用您自定义的 Profile,也可以使用DEFAULTProfile。使用
DEFAULTProfile 指定PASSWORD_VERIFY_FUNCTION参数的示例如下:obclient> ALTER PROFILE DEFAULT LIMIT PASSWORD_VERIFY_FUNCTION verify_function; Query OK, 0 rows affected更多
ALTER PROFILE语句的详细信息,请参见 ALTER PROFILE。完成后,您可以执行以下语句,确认密码复杂度验证是否开启。
返回值为
NULL则表示密码复杂度验证未开启,值为刚刚指定的函数则表示密码复杂度验证已开启。obclient> SELECT LIMIT FROM DBA_PROFILES WHERE RESOURCE_NAME='PASSWORD_VERIFY_FUNCTION' AND PROFILE='profile_name';示例如下:
obclient> SELECT LIMIT FROM DBA_PROFILES WHERE RESOURCE_NAME='PASSWORD_VERIFY_FUNCTION' AND PROFILE='DEFAULT'; +-----------------+ | LIMIT | +-----------------+ | VERIFY_FUNCTION | +-----------------+ 1 row in set
验证密码复杂度的设置是否有效
使用管理员用户登录到数据库的 Oracle 租户。
分别使用符合要求的密码和不符合要求的密码创建用户
sectest1。obclient> CREATE USER sectest1 IDENTIFIED BY ***s**; ERROR-20023: Password must contain at least 1 uppercase character(s) obclient> CREATE USER sectest1 IDENTIFIED BY ***S**; ERROR-20002: Password contains the username obclient> CREATE USER sectest1 IDENTIFIED BY **W_1*; Query OK, 0 rows affected
查看密码复杂度规则
密码复杂度设置成功后,如果下次需要查看具体的复杂度规则,可以通过查询函数中的内容来获取。
方法如下:
使用管理员用户登录到数据库的 Oracle 租户。
通过
DBA_SOURCE视图查找对应的函数。DBA_SOURCE视图记录了系统中所有的存储过程和函数。更多
DBA_SOURCE视图及其字段的说明请参见 DBA_SOURCE。示例如下:
obclient> SELECT OWNER,NAME,TYPE FROM DBA_SOURCE WHERE TYPE='FUNCTION'; +-------+------------------+----------+ | OWNER | NAME | TYPE | +-------+------------------+----------+ | SYS | COMPLEXITY_CHECK | FUNCTION | | SYS | STRING_DISTANCE | FUNCTION | | SYS | VERIFY_FUNCTION | FUNCTION | +-------+------------------+----------+ 3 rows in set通过
USER_SOURCE视图查看当前用户拥有的函数VERIFY_FUNCTION的内容。USER_SOURCE视图记录了系统中当前用户拥有的所有存储过程和函数。更多
USER_SOURCE视图及其字段的说明请参见 USER_SOURCE。示例如下:
obclient>SELECT TEXT FROM USER_SOURCE WHERE NAME='VERIFY_FUNCTION' AND TYPE='FUNCTION'\G *************************** 1. row *************************** TEXT: FUNCTION verify_function (username varchar2, password varchar2, old_password varchar2) RETURN boolean IS differ integer; pw_lower varchar2(256); db_name varchar2(40); i integer; simple_password varchar2(10); reverse_user varchar2(32); BEGIN IF NOT complexity_check(password, 8, 1, 1) THEN RETURN(FALSE); END IF; pw_lower := LOWER(password); IF instr(pw_lower, LOWER(username)) > 0 THEN raise_application_error(-20002, 'Password contains the username'); END IF; reverse_user := ''; FOR i in REVERSE 1..length(username) LOOP reverse_user := reverse_user || substr(username, i, 1); END LOOP; IF instr(pw_lower, LOWER(reverse_user)) > 0 THEN raise_application_error(-20003, 'Password contains the username ' || 'reversed'); END IF; db_name := 'oceanbase'; IF instr(pw_lower, LOWER(db_name)) > 0 THEN raise_application_error(-20004, 'Password contains the server name'); END IF; IF instr(pw_lower, 'oracle') > 0 THEN raise_application_error(-20006, 'Password too simple'); END IF; IF pw_lower IN ('welcome1', 'database1', 'account1', 'user1234', 'password1', 'oracle123', 'computer1', 'abcdefg1', 'change_on_install') THEN raise_application_error(-20006, 'Password too simple'); END IF; IF old_password IS NOT NULL THEN differ := string_distance(old_password, password); IF differ < 3 THEN raise_application_error(-20010, 'Password should differ from the ' || 'old password by at least 3 characters'); END IF; END IF ; RETURN(TRUE); END 1 row in set




