创建只读用户须显式授予select权限且禁用密码与账户,禁用quota并避免connect角色;需在对应pdb执行授权,过滤临时表与物化视图,验证insert/create/v$访问均被拒绝。
创建只读用户前必须确认的权限模型
Oracle 没有内置的“只读角色”,SELECT_CATALOG_ROLE 看似可用,但它允许访问数据字典视图(如 DBA_TABLES),实际超出只读范围。真正安全的做法是显式授权:只给 SELECT 权限,且仅限业务表。否则容易误授 EXECUTE 或 READ(12c+ 新权限)导致越权。
- 必须用具有
SELECT ANY TABLE 或具体对象权限的用户(如 DBA)执行授权
- 不要直接授予
CONNECT 角色——它隐含 CREATE SESSION,但还可能带过时的默认权限(如 11g 中附带 UNLIMITED TABLESPACE)
- 如果用 PDB,注意容器级别权限不自动下放,需在对应 PDB 内执行
建用户 + 限制登录 + 授予最小 SELECT 权限
先建用户并禁用密码过期、锁定等干扰项,再精确授权。以下命令在目标 PDB 或非CDB中执行:
CREATE USER app_ro IDENTIFIED BY "StrongPass123!"
DEFAULT TABLESPACE users
TEMPORARY TABLESPACE temp
QUOTA 0 ON users;
ALTER USER app_ro PASSWORD EXPIRE;
ALTER USER app_ro ACCOUNT LOCK;
GRANT CREATE SESSION TO app_ro;
-
QUOTA 0 ON users 防止用户意外创建对象(哪怕没授 CREATE TABLE,某些工具仍可能触发隐式临时段)
-
ACCOUNT LOCK 是关键:避免用户被误启用,后续只通过 ALTER USER ... ACCOUNT UNLOCK 开放访问
- 只授
CREATE SESSION,不授 CONNECT 角色,规避历史兼容性包袱
批量授予指定 schema 下所有表的 SELECT 权限
手动对每个表 GRANT SELECT ON schema.tab TO app_ro 不现实。用动态 SQL 生成授权语句更可靠:
SELECT 'GRANT SELECT ON ' || owner || '.' || table_name || ' TO app_ro;'
FROM dba_tables
WHERE owner = 'APP_SCHEMA'
AND temporary = 'N';
- 执行结果复制粘贴后运行;不要用 PL/SQL 自动执行——出错难回滚
- 过滤
temporary = 'N',排除全局临时表(GTTS),它们不支持跨用户 SELECT
- 若 schema 含物化视图,
dba_tables 不包含它们,需额外查 dba_mviews 并单独授权
- 不要漏掉
SYNONYM:如果业务用同义词访问,需确保底层对象已授权,且用户有 SELECT 权限才能解析同义词
验证只读行为与常见翻车点
登录后立刻测试边界行为,别依赖“应该没问题”:
- 执行
SELECT COUNT(*) FROM app_schema.orders; → 应成功
- 执行
INSERT INTO app_schema.orders VALUES (...); → 应报 ORA-01031: insufficient privileges
- 执行
SELECT * FROM v$session; → 应报 ORA-00942: table or view does not exist(没授数据字典权限)
- 尝试
CREATE TABLE t AS SELECT * FROM app_schema.orders; → 应报 ORA-01031(没 CREATE TABLE,且 QUOTA 0 也拦住)
SELECT ANY TABLE 或具体对象权限的用户(如 DBA)执行授权 CONNECT 角色——它隐含 CREATE SESSION,但还可能带过时的默认权限(如 11g 中附带 UNLIMITED TABLESPACE) CREATE USER app_ro IDENTIFIED BY "StrongPass123!" DEFAULT TABLESPACE users TEMPORARY TABLESPACE temp QUOTA 0 ON users; ALTER USER app_ro PASSWORD EXPIRE; ALTER USER app_ro ACCOUNT LOCK; GRANT CREATE SESSION TO app_ro;
-
QUOTA 0 ON users防止用户意外创建对象(哪怕没授CREATE TABLE,某些工具仍可能触发隐式临时段) -
ACCOUNT LOCK是关键:避免用户被误启用,后续只通过ALTER USER ... ACCOUNT UNLOCK开放访问 - 只授
CREATE SESSION,不授CONNECT角色,规避历史兼容性包袱
批量授予指定 schema 下所有表的 SELECT 权限
手动对每个表 GRANT SELECT ON schema.tab TO app_ro 不现实。用动态 SQL 生成授权语句更可靠:
SELECT 'GRANT SELECT ON ' || owner || '.' || table_name || ' TO app_ro;'
FROM dba_tables
WHERE owner = 'APP_SCHEMA'
AND temporary = 'N';
- 执行结果复制粘贴后运行;不要用 PL/SQL 自动执行——出错难回滚
- 过滤
temporary = 'N',排除全局临时表(GTTS),它们不支持跨用户 SELECT
- 若 schema 含物化视图,
dba_tables 不包含它们,需额外查 dba_mviews 并单独授权
- 不要漏掉
SYNONYM:如果业务用同义词访问,需确保底层对象已授权,且用户有 SELECT 权限才能解析同义词
验证只读行为与常见翻车点
登录后立刻测试边界行为,别依赖“应该没问题”:
- 执行
SELECT COUNT(*) FROM app_schema.orders; → 应成功
- 执行
INSERT INTO app_schema.orders VALUES (...); → 应报 ORA-01031: insufficient privileges
- 执行
SELECT * FROM v$session; → 应报 ORA-00942: table or view does not exist(没授数据字典权限)
- 尝试
CREATE TABLE t AS SELECT * FROM app_schema.orders; → 应报 ORA-01031(没 CREATE TABLE,且 QUOTA 0 也拦住)
temporary = 'N',排除全局临时表(GTTS),它们不支持跨用户 SELECT dba_tables 不包含它们,需额外查 dba_mviews 并单独授权 SYNONYM:如果业务用同义词访问,需确保底层对象已授权,且用户有 SELECT 权限才能解析同义词 - 执行
SELECT COUNT(*) FROM app_schema.orders;→ 应成功 - 执行
INSERT INTO app_schema.orders VALUES (...);→ 应报ORA-01031: insufficient privileges - 执行
SELECT * FROM v$session;→ 应报ORA-00942: table or view does not exist(没授数据字典权限) - 尝试
CREATE TABLE t AS SELECT * FROM app_schema.orders;→ 应报ORA-01031(没CREATE TABLE,且QUOTA 0也拦住)
最容易被忽略的是:应用连接字符串若含 AS SYSDBA 或使用外部认证,会绕过用户级权限控制;还有 Oracle Wallet 配置可能隐式提升权限,这类环境需单独审计。










