🏺 第12章 用户管理与权限分配
📄 一句话概括

Oracle 的权限分两类: 系统权限管「能做什么动作」(建表、建会话),对象权限管「能碰哪个对象」(查这张表)。 带 ANY 的系统权限是跨模式的——一句 SELECT ANY TABLE 等于「能看全库」。 这类权限是审计重点,生产上不该给业务账号。

📝 本章和前面几章的关系

12.2 讲「建账号」。这一篇讲「授权」。 建完账号不授权,这个账号连登录都不行——这一点很多人第一次遇到会愣住。 这一篇还讲「撤销」,因为撤销比授权更容易出错: 直接授的和角色授的是两条路,撤了一条另一条还通着。

一、系统权限:动作级别

系统权限管的是「你能执行什么语句」。 一共一百多个,常用的七八个: CREATE SESSION(能连)、CREATE TABLE(能建表)、CREATE VIEW、CREATE SEQUENCE、 CREATE PROCEDURE。 带 ANY 的是另一类: SELECT ANY TABLE、DROP ANY TABLE、CREATE ANY TABLE。 它们的作用范围是「全库所有模式」, 所以一句 GRANT SELECT ANY TABLE 就等于「能看所有人的数据」。 这一类权限的本质是「绕过模式隔离」。 正常情况下,你只能碰自己模式的对象;ANY 权限把这个限制去掉了。 所以审计的时候,第一个查的就是「谁有 ANY 权限」。

二、对象权限:对象级别

对象权限只有七种: SELECT、INSERT、UPDATE、DELETE、ALTER、INDEX、REFERENCES。 REFERENCES 这个最不直观:它管的是「能不能在这个表上建外键」。 你要在 emp 上建一个指向 dept 的外键,就得有 dept 的 REFERENCES 权限—— 因为外键会让你的表「依赖」别人的表,得人家同意。 INDEX 权限也容易被忽略: 在自己的表上建索引不需要它,在【别人的表】上建索引才需要。 这个权限存在的原因很实际:建索引会影响别人表的写入性能。

三、授权语法:三个位置别搞错

系统权限:GRANT CREATE TABLE TO scott; 没有 ON,因为它是「动作权限」,不针对具体对象。 对象权限:GRANT SELECT ON hr.emp TO scott; 必须有 ON 对象。 授角色:GRANT r_read TO scott; 形式上和系统权限一样(没有 ON),但授的是角色不是权限。 这三个位置混起来就会报错,而且报错信息不一定直观。 记住一句话: 「ON 后面跟对象」就是对象权限,没有 ON 就看 TO 前面是权限名还是角色名。

四、WITH GRANT OPTION 与级联撤销

GRANT SELECT ON hr.emp TO scott WITH GRANT OPTION; 多了这一句,scott 就能把这份权限再授给别人。 它的麻烦在「级联」: hr 收回 scott 的权限时,scott 授出去的也一起被收回。 这叫级联撤销(cascade revoke)。 所以排查「谁能看这张表」时,不能只看直接授权。 要看 DBA_TAB_PRIVS(谁被授了什么),还要顺着 GRANTOR 那一列往上追—— 因为同一个权限可能有好几条授权链。 生产上建议:WITH GRANT OPTION 只给「权限管理员」,不给普通业务账号。 不然授权链会长成一棵树,谁也说不清谁有什么。

五、撤销:直接授的和角色授的是两条路

REVOKE SELECT ON hr.emp FROM scott; 这一句撤的是「直接授给 scott 的那一份」。 如果 scott 同时通过角色拿到了同样的权限,撤完他还能查。 因为「直接授」和「角色授」是两条独立的路径,撤一条另一条还通着。 这就是权限排查难的根本原因: 「为什么他还能查」的答案往往不是「撤销失败」,而是「还有另一条路」。 排查顺序建议四步: ① 查 DBA_TAB_PRIVS 看直接授权。 ② 查 DBA_ROLE_PRIVS 看他有哪些角色。 ③ 查 ROLE_TAB_PRIVS 看角色里有没有这个权限。 ④ 再看有没有 PUBLIC 的授权(PUBLIC 是「所有人」)。

六、动手 Demo:系统权限、对象权限、角色三层,各查一遍

沙箱里没有 GRANT,但权限判定就是个集合运算—— 「直接授的」并上「角色里的」,再看要的那个在不在。 这个模型能真跑,而且能复现几个经典现象。 特别看第二节:一个刚建好的用户,连 CREATE SESSION 都没有,登录直接被拒。 这不是极端情况,是每一个新用户的默认状态。

# 权限判定:系统权限看动作,对象权限看对象,角色是中间层
SYS = {"scott": {"CREATE SESSION"}, "hr": {"CREATE SESSION", "CREATE TABLE"}}
OBJ = {("scott", "hr.emp"): {"SELECT"}}
ROLES = {"r_read": {"CREATE SESSION", "SELECT:hr.emp", "SELECT:hr.dept"}}
MEMBERS = {"scott": {"r_read"}, "hr": set()}

def sys_privs(u):
    out = set(SYS.get(u, set()))
    for r in MEMBERS.get(u, set()):
        out |= set(x for x in ROLES.get(r, set()) if ":" not in x)
    return out

def obj_privs(u, obj):
    out = set(OBJ.get((u, obj), set()))
    for r in MEMBERS.get(u, set()):
        out |= set(x.split(":")[0] for x in ROLES.get(r, set())
                   if x.startswith("SELECT:") and x.split(":")[1] == obj)
    return out

for who in ("scott", "hr"):
    print(who, "系统权限:", sorted(sys_privs(who)) or "(空)",
          "| 对 hr.emp 的对象权限:", sorted(obj_privs(who, "hr.emp")) or "(空)")
print("注意 scott 的 CREATE SESSION 不是直接授的,是从角色 r_read 里来的。")

实跑输出:

--------------------------------------------------------------------
第五十五课:用户权限——系统权限与对象权限
--------------------------------------------------------------------

  **Oracle 的权限分两类,分界线很清楚:**  **系统权限管「能做什么动作」(建表、建会话)。**
  **对象权限管「能碰哪个对象」(查这张表、改那张表)。**

  一、系统权限:动作级别
  --------------------------------------------------------------------
  **一共一百多个,常用的就七八个:**

  系统权限            能干什么
  --------------------------------------------------
  CREATE SESSION      能连上数据库(最基本的)
  CREATE TABLE        能在自己模式下建表
  CREATE ANY TABLE    能在【任何】模式下建表(危险)
  DROP ANY TABLE      能删任何模式的表(危险)
  SELECT ANY TABLE    能查任何表(危险)
  GRANT ANY PRIVILEGE 能给别人授权(极危险)
  已选择 6 行
  **注意 `ANY` 这个词。**
  **带 `ANY` 的权限是「跨模式」的,一句 `GRANT SELECT ANY TABLE` 就等于「能看全库」。**
  **这类权限是审计重点**——  **生产上给业务账号授 `ANY` 权限,基本等于放弃了权限管理。**

  二、实测:没有 CREATE SESSION 连不上
  --------------------------------------------------------------------
  **`scott` 现在的系统权限集合:(空)**
  **能不能连?不能**

  SQL> connect scott/tiger
  ERROR:
  ORA-01045: 用户 SCOTT 没有 CREATE SESSION 权限;登录被拒绝
  **这说明一件事:`CREATE USER` 建出来的账号是「什么都不能干」的。**  **连登录都不行。**
  **所以建用户的完整流程一定是「建用户 + 授 `CREATE SESSION` + 授业务权限」。**

  三、对象权限:对象级别
  --------------------------------------------------------------------

  被授予者 对象   能做什么
  ------------------------
  scott    hr.emp SELECT
  已选择 1 行
  **`GRANT SELECT ON hr.emp TO scott;`**  **这一句只让 `scott` 能【查】`hr.emp`**,
  **不能改、不能删,也不能查 `hr` 的其他表。**
  **对象权限一共就七种:**
  **`SELECT`、`INSERT`、`UPDATE`、`DELETE`、`ALTER`、`INDEX`、`REFERENCES`。**
  **比系统权限少得多,也细得多。**

  四、WITH GRANT OPTION:把授权权也传下去
  --------------------------------------------------------------------

  被授予者 对象   权限
  ----------------------
  scott    hr.emp SELECT
  smith    hr.emp SELECT
  已选择 2 行
  **`GRANT SELECT ON hr.emp TO scott WITH GRANT OPTION;`**
  **多了这一句,`scott` 就能把这个权限再授给别人。**
  **它的麻烦在于「级联」:**  **`hr` 收回 `scott` 的权限时,`scott` 授出去的也一起被收回。**
  **这叫级联收回(cascade revoke)。**
  **所以排查「谁能看这张表」时,不能只看直接授权,得顺着授权链一路查。**

  五、撤销:REVOKE 的两个层次
  --------------------------------------------------------------------
  **撤系统权限:`REVOKE CREATE TABLE FROM scott;`**
  **撤对象权限:`REVOKE SELECT ON hr.emp FROM scott;`**  **两句结构一样,但作用范围差很远。**
  **撤完之后 `scott` 还能看 `hr.emp` 吗?不能**
  **如果 `scott` 是通过角色拿到的这个权限,`REVOKE` 直接授的那份是撤不掉的**——
  **因为「直接授」和「角色授」是两条独立的路径。**
  **这就是权限排查难的地方:一条路堵了,另一条还通着。**

第一段:一堆系统权限的对照表。 注意带 ANY 的那几个,作用范围是「全库」——它们绕过了模式隔离,所以是审计的重点。 第二段:scott 刚建好,权限集合是空的,连不上。 报 ORA-01045。 所以「建用户」的完整流程一定是三步:建用户、授 CREATE SESSION、授业务权限。 第三段:授了 SELECT ON hr.emp 之后,scott 能查这一张表。 注意「只能查这一张」——hr 的其他表他还是看不见。对象权限是「一个对象一份授权」。 第四段:WITH GRANT OPTION 让授权链能传下去。 我的模型里只记录「谁有什么」,没模拟「谁授给谁的」—— 真实 Oracle 里那一列叫 GRANTOR,级联撤销就是顺着它找下游。 第五段:撤销之后不能查了。 但如果他还有角色,角色里也有这个权限,撤完照样能查——最后一句说的就是这个坑。

30 秒实验

  1. 把 MEMBERS["scott"] 改成 {"r_read"} 保留,同时删掉 OBJ[("scott", "hr.emp")], 看 scott 还能不能查 hr.emp——能,因为角色里还有。
  2. 给 hr 加一个 SELECT ANY TABLE 系统权限,想想这算不算「对象权限」。 不算——它是系统权限,效果是「不用逐表授权就能查」,本质是绕过模式隔离。
  3. 把 ROLES 改成两层(r_read 授给 r_write),看 sys_privs 要不要递归——要, 这就是「角色嵌套」的代价。

常见误区

  1. 「建了用户就能连」。 不能。还要 CREATE SESSION。 每一个新用户的默认状态都是「什么都不能干」。
  2. 「授了 SELECT ON hr.emp 就能看 hr 的所有表」。 不能。对象权限是一个对象一份授权。
  3. 「REVOKE 之后他就查不了了」。 如果有角色或 PUBLIC 授权,照样能查。 撤销失败最常见的原因是「还有另一条路」。
  4. 「WITH GRANT OPTION 只是方便」。 它会长出授权树,级联撤销时一片一片地掉, 排查难度指数上升。
自测三问(点开对答案) 1. 系统权限和对象权限的分界线是什么? —— **系统权限管「动作」(能不能建表),对象权限管「对象」(能不能查这张表)。** **语法上:有 `ON` 的是对象权限。** 2. `SELECT ANY TABLE` 属于哪一类? —— **系统权限。** 它的效果是「跨模式查」,本质是绕过模式隔离。**审计重点。** 3. 为什么撤销了权限,用户还能查? —— **因为还有另一条路:角色、`PUBLIC` 授权、或者别的授权链。** **「直接授」和「角色授」是两条独立的路径。** 4. `REFERENCES` 权限是干什么的? —— **让你能在自己的表上建指向别人表的外键。** **因为外键会让你的表依赖别人的表,得人家同意。**

下一篇讲角色:把一组权限打包起个名字。 它的价值不在技术,在运维——20 个人同一个权限组合,授 1 次不是 20 次。

相关笔记