暂无图片
暂无图片
8
暂无图片
暂无图片
暂无图片

OceanBase设置密码复杂度

OceanBase 2023-01-31
663

OceanBase 数据库 Oracle 模式的密码复杂度策略,兼容 Oracle 数据库的密码策略。为了防止恶意的密码攻击,提升数据库的安全性,OceanBase 数据库用户可以根据需要,设置密码的复杂度函数,验证用户登录身份。

设置密码复杂度并启用密码复杂度验证

OceanBase 数据库的 Oracle 模式提供了 PASSWORD_VERIFY_FUNCTION 参数来验证密码复杂度是否符合要求。

PASSWORD_VERIFY_FUNCTION 参数允许将 PL 的密码复杂性验证函数作为参数传递给 CREATE PROFILE 和 ALTER PROFIL 语句。

  1. 使用管理员用户登录到数据库的 Oracle 租户。

  2. 编写 PASSWORD_VERIFY_FUNCTION参数对应的函数并授权。

    以下以设置密码复杂度为如下规则,提供操作指导。

    • 密码长度最小为 8 个字符,最大不超过 256 个字符。

    • 至少包含 1 个大写字母、1 个小写字母和 1 个特殊字符。

    • 密码不能是用户名,不能是用户名的反序,也不能是服务器名。

    • 密码不能是 welcome1、 database1、 account1user1234password1oracle123computer1abcdefg1change_on_installoracle 等过于简单的密码。

    • 如果修改密码,新密码与旧密码至少要有 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;
    

    创建函数的相关操作,请参见 创建函数

  3. 在 User 的 Profile 中,指定 PASSWORD_VERIFY_FUNCTION 参数,启用密码复杂度验证。

    语句如下:

    obclient> ALTER PROFILE profile_name LIMIT PASSWORD_VERIFY_FUNCTION verify_function;
    

    其中,profile_name 可以使用您自定义的 Profile,也可以使用 DEFAULT Profile。

    使用 DEFAULT Profile 指定 PASSWORD_VERIFY_FUNCTION 参数的示例如下:

    obclient> ALTER PROFILE DEFAULT LIMIT PASSWORD_VERIFY_FUNCTION verify_function;
    Query OK, 0 rows affected
    

    更多 ALTER PROFILE 语句的详细信息,请参见 ALTER PROFILE

  4. 完成后,您可以执行以下语句,确认密码复杂度验证是否开启。

    返回值为 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
    

验证密码复杂度的设置是否有效

  1. 使用管理员用户登录到数据库的 Oracle 租户。

  2. 分别使用符合要求的密码和不符合要求的密码创建用户 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
    

查看密码复杂度规则

密码复杂度设置成功后,如果下次需要查看具体的复杂度规则,可以通过查询函数中的内容来获取。

方法如下:

  1. 使用管理员用户登录到数据库的 Oracle 租户。

  2. 通过 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
    
  3. 通过 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
「喜欢这篇文章,您的关注和赞赏是给作者最好的鼓励」
关注作者
【版权声明】本文为墨天轮用户原创内容,转载时必须标注文章的来源(墨天轮),文章链接,文章作者等基本信息,否则作者和墨天轮有权追究责任。如果您发现墨天轮中有涉嫌抄袭或者侵权的内容,欢迎发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论