Oracle 原生不支援 MySQL 風格的 CREATE USER ... REQUIRE IP 語法,但通過 AFTER LOGON ON DATABASE 系統級觸發器,結合 SYS_CONTEXT('USERENV', 'IP_ADDRESS') 獲取客戶端 IP,可靈活實現用戶級 IP 白名單/黑名單控制。本文提供生產就緒的觸發器腳本、通配符支援邏輯、本地連接豁免機制與完整測試驗證案例。
AFTER LOGON ON DATABASE(系統級,捕獲所有登入)SYS_CONTEXT('USERENV', 'SESSION_USER') 當前用戶,SYS_CONTEXT('USERENV', 'IP_ADDRESS') 客戶端 IPRAISE_APPLICATION_ERROR(-20001, ...) 拒絕連接CREATE OR REPLACE TRIGGER logon_ip_control
AFTER LOGON ON DATABASE
DECLARE
v_user VARCHAR2(128) := SYS_CONTEXT('USERENV', 'SESSION_USER');
v_ip VARCHAR2(45) := SYS_CONTEXT('USERENV', 'IP_ADDRESS');
v_allowed BOOLEAN := FALSE;
BEGIN
-- 僅針對指定用戶啟用控制
IF v_user = 'EPAY_USER' THEN
-- 白名單支援精確 IP、CIDR、通配符 (* 或 %)
IF v_ip IS NULL THEN
-- 本地連接 (IPC/Bequeath) 無 IP,默認放行
v_allowed := TRUE;
ELSIF v_ip IN ('192.168.219.20', '192.168.219.22') THEN
-- 精確匹配
v_allowed := TRUE;
ELSIF v_ip LIKE '192.168.219.%' THEN
-- 通配符匹配 (支援 * 或 %)
v_allowed := TRUE;
-- 可擴展:CIDR 判斷需調用 UTL_INADDR 或自定義函數
END IF;
IF NOT v_allowed THEN
RAISE_APPLICATION_ERROR(-20001,
'User ' || v_user || ' is not allowed to connect from ' || NVL(v_ip, 'LOCAL (IPC)'));
END IF;
END IF;
EXCEPTION
WHEN OTHERS THEN
-- 記錄審計日志 (可選)
-- INSERT INTO logon_audit (logon_time, user_name, ip_addr, error_msg)
-- VALUES (SYSDATE, v_user, v_ip, SQLERRM);
RAISE; -- 重新拋出,保持拒絕效果
END;
/
| 連接方式 | IP_ADDRESS 值 | 觸發器行為 |
|---|---|---|
| TCP/IP 遠程連接 (sqlplus user@tns) | 客戶端真實 IP (如 192.168.219.21) | 受白名單控制 |
| 本地連接 (sqlplus / as sysdba 或 sqlplus user) | NULL (IPC/Bequeath 協議) | 自動豁免 (腳本判斷 v_ip IS NULL) |
| JDBC Thin 驅動 | 客戶端 IP (需驅動支援) | 受控制 |
| JDBC OCI 驅動 | 可能為 NULL 或服務器 IP | 需測試驗證 |
-- 簡易 CIDR 判斷函數 (192.168.219.0/24)
CREATE OR REPLACE FUNCTION ip_in_cidr(p_ip VARCHAR2, p_cidr VARCHAR2) RETURN BOOLEAN IS
v_ip_num NUMBER;
v_net_num NUMBER;
v_mask_num NUMBER;
v_prefix NUMBER;
BEGIN
-- 解析 CIDR: 192.168.219.0/24
v_prefix := TO_NUMBER(SUBSTR(p_cidr, INSTR(p_cidr, '/')+1));
v_net_num := UTL_INADDR.GET_HOST_ADDRESS(SUBSTR(p_cidr, 1, INSTR(p_cidr, '/')-1));
v_mask_num := POWER(2, 32-v_prefix) - 1;
v_ip_num := UTL_INADDR.GET_HOST_ADDRESS(p_ip);
RETURN BITAND(v_ip_num, v_mask_num) = BITAND(v_net_num, v_mask_num);
EXCEPTION
WHEN OTHERS THEN RETURN FALSE;
END;
/
-- 觸發器中調用
IF ip_in_cidr(v_ip, '192.168.219.0/24') THEN v_allowed := TRUE; END IF;
[oracle@clientA ~]$ sqlplus epay_user@pri
SQL*Plus: Release 11.2.0.3.0 Production
Enter password:
ERROR:
ORA-00604: error occurred at recursive SQL level 1
ORA-20001: User EPAY_USER is not allowed to connect from 192.168.219.21
ORA-06512: at line 10
✅ 預期行為:觸發器攔截,返回自定義錯誤碼 -20001,連接被拒絕。
[oracle@primary ~]$ sqlplus epay_user
SQL*Plus: Release 11.2.0.3.0 Production
Enter password:
Connected to:
Oracle Database 11g Enterprise Edition Release 11.2.0.3.0 - 64bit Production
With the Partitioning, OLAP, Data Mining and Real Application Testing options
✅ 預期行為:IP 在白名單,連接成功。
[oracle@primary ~]$ sqlplus epay_user
SQL*Plus: Release 11.2.0.3.0 Production
Enter password:
Connected to:
Oracle Database 11g Enterprise Edition Release 11.2.0.3.0 - 64bit Production
...
✅ 預期行為:本地 IPC 連接 v_ip 為 NULL,觸發器判斷豁免,連接成功。
-- 審計表
CREATE TABLE logon_ip_audit (
audit_id NUMBER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
logon_time TIMESTAMP DEFAULT SYSTIMESTAMP,
os_user VARCHAR2(128),
db_user VARCHAR2(128),
ip_address VARCHAR2(45),
terminal VARCHAR2(128),
module VARCHAR2(128),
action VARCHAR2(20), -- ALLOWED / DENIED
error_msg VARCHAR2(4000)
);
-- 觸發器中記錄 (使用自治事務避免影響主事務)
CREATE OR REPLACE TRIGGER logon_ip_control
AFTER LOGON ON DATABASE
DECLARE
PRAGMA AUTONOMOUS_TRANSACTION;
v_user VARCHAR2(128) := SYS_CONTEXT('USERENV', 'SESSION_USER');
v_ip VARCHAR2(45) := SYS_CONTEXT('USERENV', 'IP_ADDRESS');
v_os_user VARCHAR2(128) := SYS_CONTEXT('USERENV', 'OS_USER');
v_term VARCHAR2(128) := SYS_CONTEXT('USERENV', 'TERMINAL');
v_mod VARCHAR2(128) := SYS_CONTEXT('USERENV', 'MODULE');
v_allowed BOOLEAN := FALSE;
BEGIN
-- ... 判斷邏輯同前 ...
IF v_user = 'EPAY_USER' THEN
-- 記錄審計
INSERT INTO logon_ip_audit (db_user, ip_address, os_user, terminal, module, action, error_msg)
VALUES (v_user, v_ip, v_os_user, v_term, v_mod,
CASE WHEN v_allowed THEN 'ALLOWED' ELSE 'DENIED' END,
CASE WHEN v_allowed THEN NULL ELSE 'IP not in whitelist' END);
COMMIT;
IF NOT v_allowed THEN
RAISE_APPLICATION_ERROR(-20001, 'Access denied from ' || NVL(v_ip, 'LOCAL'));
END IF;
END IF;
END;
/
| 局限 | 影響 | 替代/補充方案 |
|---|---|---|
| 無法控制 SYS/SYSDBA 連接 | 特權用戶繞過觸發器 | 作業系統層面防火牆、Oracle Connection Manager (CMAN) |
| JDBC OCI/連接池可能獲取不到真實客戶端 IP | 誤判豁免或誤攔截 | 應用層傳遞 CLIENT_IDENTIFIER、使用 CMAN |
| 觸發器失效導致所有用戶無法登入 | 單點故障風險 | 保留一個無觸發器控制的應急管理員用戶、監控觸發器狀態 |
| 性能開銷 (每次登入觸發) | 高頻連接場景有影響 | 使用 CMAN 或網絡層 ACL (Oracle Database Firewall) |
基於 AFTER LOGON 觸發器 + SYS_CONTEXT 實現 IP 訪問控制是 Oracle 原生最靈活、零成本的方案。關鍵成功要素:
將本文觸發器腳本納入資料庫安全基線標準,即可滿足絕多數合規與安全管控需求。