Oracle数据库的权限控制并不只有用户直接获得系统权限或对象权限这一条路。为了简化大规模授权,Oracle引入了角色这个概念,而且角色还可以被授予给另一个角色。当一个角色被授予给另一个角色后,后者就拥有了前者的全部权限,这种嵌套关系称为角色继承。直观地看,创建一个基础角色READ_ONLY_ROLE,只包含对业务表的查询权限,再创建一个REPORT_USER_ROLE,将READ_ONLY_ROLE授予给它,那么任何被授予REPORT_USER_ROLE的用户,理论上就继承了只读能力。但实际使用中有一个关键点:用户登录后,数据库会话并不会自动启用所有间接继承的角色,只有直接授予用户的角色才会默认生效,或者在用户显式执行SET ROLE命令后,间接角色才被激活。

举个最简单的例子,用户SCOTT被直接授予了REPORT_USER_ROLE,而REPORT_USER_ROLE又包含了READ_ONLY_ROLE。当SCOTT登录后,查询数据字典SESSION_ROLES会发现,当前会话中只有REPORT_USER_ROLE处于启用状态,READ_ONLY_ROLE并不在其中。如果SCOTT尝试访问只读角色所覆盖的表,会收到权限不足的报错。这并不意味着角色继承失效,而是Oracle默认只激活直接授予用户的角色。要使用READ_ONLY_ROLE的权限,SCOTT需要执行SET ROLE READ_ONLY_ROLE;,但前提是SCOTT知道该角色的存在,或者通过ALTER USER DEFAULT ROLE语句将间接角色也设置为默认启用。这也是为什么很多开发环境里DBA会对用户设置ALTER USER scott DEFAULT ROLE ALL;,目的就是让所有直接或间接授予的角色在登录后自动激活。
角色链的授权与继承规则
Oracle允许角色授予角色的操作,但有一个硬性限制:不能形成循环授权。比如角色A授予角色B,角色B又授予角色A,Oracle会直接抛出ORA-01934错误,提示检测到循环的角色授权。除此之外,角色继承的层级理论上是没有深度限制的,一条角色链可以从最底层的基础权限角色一路嵌套到业务角色再到应用角色,只要授权语句合法即可。但是在实际设计中,过深的角色嵌套会让权限排查变得非常痛苦,因为DBA需要逐层展开角色才能知道某个用户最终拥有哪些权限。
从数据字典的角度看,角色之间的授予关系记录在DBA_ROLE_PRIVS视图中。例如执行GRANT read_only_role TO report_user_role;之后,查询SELECT * FROM dba_role_privs WHERE grantee='REPORT_USER_ROLE';就能看到被授予的角色是READ_ONLY_ROLE,ADMIN_OPTION列显示NO,DEFAULT_ROLE列显示YES。注意,这里的DEFAULT_ROLE列是针对被授予者而言的,如果被授予者是角色而不是用户,这一列的实际含义会被忽略,因为角色本身不存在登录会话,默认角色的概念只对用户生效。
还有一点容易被忽略:一个角色可以同时被多个角色继承,权限是并集关系。比如角色A包含SELECT权限,角色B包含INSERT权限,角色C同时继承了A和B,那么获得角色C的用户就同时具备SELECT和INSERT。如果A和B中存在同名但相互冲突的权限,Oracle遵循权限只增不减的原则,不会出现负权限。换句话说,角色继承只会叠加权限,不会因为某个角色被回收而导致其它角色中的相同权限也被移除,除非显式执行REVOKE操作。
WITH ADMIN OPTION与权限传递的区别
权限传递在Oracle中有两种典型场景,一种是系统权限的传递,一种是角色的传递。对于系统权限,比如CREATE TABLE,可以在授权时加上WITH ADMIN OPTION,这样被授予者就获得了将CREATE TABLE继续授予其他人的能力。对于角色来说,同样可以使用GRANT report_user_role TO scott WITH ADMIN OPTION;,这意味着SCOTT不仅可以使用REPORT_USER_ROLE,还能把这个角色授予其他用户或角色,甚至可以从其他用户那里回收该角色。这种管理权的传递是完全独立的,它和对象权限的GRANT OPTION有本质区别。
对象权限的GRANT OPTION传递的是对该对象操作权限的授予权,并且这种传递是级联的:如果上游的GRANT OPTION被回收,那么所有通过这条链获得的权限也会被级联回收。而角色的WITH ADMIN OPTION不同,它只传递角色的管理权,不涉及对象本身的依赖链。举个例子,用户A把角色R授予用户B并带上WITH ADMIN OPTION,用户B再把R授予用户C。当A从B回收R时,C的角色R仍然保留,不会因为B失去管理权而自动消失。这是因为角色授权一旦完成,Oracle不会追踪“谁通过谁获得的”这条链,角色权限在授予瞬间就被固化了。这种差异是安全管理中必须清楚的:对象权限的回收是连锁的,角色权限的回收则需要逐个手动处理。
从实践角度看,是否使用WITH ADMIN OPTION要谨慎。如果开发环境允许开发人员自行传递角色,很容易出现角色扩散,最终导致权限边界模糊。推荐的做法是,只让DBA或安全管理员持有角色的管理权,业务用户只获得角色的使用权。如果确实需要某个组长角色拥有权限分配能力,可以单独创建一个管理型角色,并仅对其授予WITH ADMIN OPTION,而不是把所有业务角色都开放传递。
默认角色配置与排查技巧
前面提到过,默认只有直接授予用户的角色会在登录后启用。要查看一个用户拥有哪些角色以及哪些是默认启用的,可以查询DBA_ROLE_PRIVS视图,其中DEFAULT_ROLE列如果为YES表示登录后自动激活,为NO则需要用户手动SET ROLE。修改默认角色的语句是ALTER USER scott DEFAULT ROLE ALL EXCEPT report_user_role;,这样除了REPORT_USER_ROLE之外的所有角色都会自动激活。需要注意的是,这条命令只能调整直接或间接授予给该用户的角色,如果角色没有被授予,设置默认角色并不会自动授予权限。
排查角色继承问题时,我习惯先用SELECT * FROM session_roles;查看当前会话到底激活了哪些角色,再对比SELECT * FROM dba_role_privs WHERE grantee='SCOTT';查看该用户被授予了哪些角色。如果发现某个间接角色没有被激活,原因基本就是默认角色设置的问题。还有一个常见的坑:用户通过PL/SQL块或者存储过程执行动态SQL时,角色权限并不会生效。这是因为Oracle的存储过程默认使用定义者权限或调用者权限,但角色在PL/SQL中始终是被禁用的,只有直接授予用户的系统权限或对象权限才能在存储过程中使用。这是角色继承无法跨越的边界,很多权限问题的根源就在这里。
此外,角色的继承关系也不受口令保护的影响。如果一个角色被设置了口令,那么即使用户直接拥有该角色,也必须通过SET ROLE role_name IDENTIFIED BY password;来激活,且默认角色不会自动启用。口令保护的角色通常在应用层使用,程序在建立连接后显式激活角色,以增强安全性。但在角色链中,如果基础角色设定了口令,上层角色继承时并不会携带口令,使用上层角色的用户仍然需要知道基础角色的口令才能激活它,这会让权限链变得异常繁琐,实际项目中应避免给被继承的基础角色设置口令。

