角色是「权限的包」:把权限发给角色、把人塞进角色,改权限只改一处。
固定服务器角色改不了权限,只能往里塞人;securityadmin 其实接近 sysadmin。
自定义角色能当成员互相嵌套,但最多两层——套深了没人看得懂,排错是灾难。
16.1 讲「怎么建账号」,这一节讲「怎么成批管权限」。 账号一多,逐个授权就没人管得住了。 教材 16.2 分三小节:固定服务器角色、数据库角色、自定义角色。 前两个是「装好就有、改不了内容」的,第三个才是你能自己设计的。 SQLite 没有角色,所以继承关系是用 Python 模型真算的: 「成员 → 角色 → 权限」逐层展开,结果能一眼看出谁行谁不行。
一、为什么要角色:不打包就得一个个发
五个人的权限一样,逐个 GRANT 就是五遍;来第六个人,再发一遍。
角色就是「权限的包」:把权限发给角色,把人塞进角色。
| 好处 | 具体是什么 |
|---|---|
| 加人一次 | 只改成员关系,不动权限 |
| 改权限一处 | 改了角色,所有成员同时生效 |
| 看得懂 | 看角色名就知道这人能干什么,不用翻授权表 |
第三条最被低估。 一个库上百张表、几十个账号,
没有角色的话,「这个人能干什么」这个问题根本答不上来——
有角色就能答:「他在 r_sales_read 里,能读订单和商品」。
二、固定服务器角色:装好就有,改不了权限
| 角色 | 能干什么 |
|---|---|
sysadmin |
服务器上什么都能干(sa 就在这个角色里) |
securityadmin |
管登录名和密码(能给自己提权的角色,慎给) |
dbcreator |
建库、改库、删库 |
bulkadmin |
执行 BULK INSERT 批量导入 |
processadmin |
杀会话(KILL) |
固定角色的权限是写死的,你只能「把人塞进去」,不能改它有什么权限。
这是好事:它不会被误改,也不会因为某个人的需要被放宽。
securityadmin 要特别当心: 它能管登录名,
等于能给自己开任何权限——所以它实际上接近 sysadmin,别当成「只管账号的小角色」。
这是一类典型的误判: 看名字觉得「管安全的人权限应该有限」,
实际上「能改密码」本身就等于「能装成任何人」。
给这个角色之前先想清楚:这个人可以随时变成 sa。
三、数据库角色:库内的一套
| 角色 | 能干什么 |
|---|---|
db_owner |
这个库里的神:建表、删表、授权都行 |
db_datareader |
能读所有表(SELECT) |
db_datawriter |
能写所有表(增删改) |
db_denydatareader |
拒绝读所有表(DENY 优先,会盖掉上面的授权) |
db_ddladmin |
能改表结构,但不能动数据 |
这几个角色是「按库」给的——同一个账号在 A 库是读者、在 B 库可以是写者。
这就是 16.1 说的「用户是数据库级」的另一面。
db_denydatareader 值得单说: 它是「拒绝读」,而DENY 优先于任何 GRANT。
所以把某人塞进这个角色,他连自己表都读不了——
哪怕有人单独给过他 SELECT。 16.3 会实测这个优先级。
注意 db_owner 挡不住 sysadmin。 系统管理员在任何库里都是最高权限,
所以「这个库我只给 db_owner」这句话,在 sysadmin 面前不成立。
四、自定义角色:按业务打包
USE sales;
CREATE ROLE r_sales_read;
GRANT SELECT ON tb_order TO r_sales_read;
GRANT SELECT ON tb_goods TO r_sales_read;
EXEC sp_addrolemember 'r_sales_read', 'u_zhang'; -- 老写法
ALTER ROLE r_sales_read ADD MEMBER u_li; -- 新写法(2012 之后)
GO
复刻模型跑出来的继承结果:
| 谁 | tb_order 的 SELECT |
tb_goods 的 SELECT |
|---|---|---|
| u_zhang(在角色里) | 允许 | 允许 |
| u_li(在角色里) | 允许 | 允许 |
| u_wang(不在角色里) | 拒绝(无授权) | 拒绝(无授权) |
两行「允许」、一行「无授权」——角色就是这么工作的。
注意 u_wang 是「无授权」不是「拒绝」,这个区别在 16.3 非常关键:
「没票」和「禁入」是两件事。
角色名怎么起?按「业务 + 权限」起,别按部门起。
r_sales_read 比 r_销售部 好——人是会调岗的,权限不该跟着部门走。
五、角色能套角色:别套太深
SQL Server 允许角色包含角色(ALTER ROLE r_outer ADD MEMBER r_inner),
权限会一路继承下去。 复刻模型里的 principals() 就是递归展开的。
但套深了没人看得懂。 一个账号能不能查某张表,得顺着三层角色往上翻——排错时这是灾难,
尤其是「明明授了权却读不到」这种问题。
经验值:角色最多两层(业务角色 → 基础角色),再多就该拆。
判断标准很简单: 你能不能一句话说清这个角色的权限范围?说不清就是套太深了。
还有个坑: 成员关系加进去立刻生效,不需要重新登录;
但权限相关的执行计划可能要等下次执行才重新编译——
所以「刚授的权没生效」先别怀疑语句写错,等一两秒再试。
六、动手 Demo:从「成员」到「权限」:继承是怎么算的
这一段用复刻模型演示角色的继承:
建一个 r_sales_read 角色、给它两个表的读权限、塞两个人进去、再检查三个账号能不能查。
关键是最后一行 u_wang:他在角色外,结果是「无授权」而不是「拒绝」——
这两个词在下一篇会分开讲。
class Sec(object):
# ... 16.1 的登录/用户部分省略 ...
def principals(self, name):
"""一个主体直接或间接所属的全部主体(自己 + 递归角色)。"""
out, stack = {name}, [name]
while stack:
cur = stack.pop()
for r, mem in self.roles.items():
if cur in mem and r not in out:
out.add(r)
stack.append(r)
return out
def check(self, uname, obj, action):
who = self.principals(uname)
if any((p, obj, action) in self.deny for p in who):
return u"拒绝(DENY 优先)"
if any((p, obj, action) in self.grant for p in who):
return u"允许"
return u"拒绝(无授权)"
s.roles[u"r_sales_read"] = set()
s.add_member(u"r_sales_read", u"u_zhang")
s.add_member(u"r_sales_read", u"u_li")
s.grant_to(u"r_sales_read", u"tb_order", u"SELECT")
s.grant_to(u"r_sales_read", u"tb_goods", u"SELECT")
for u in (u"u_zhang", u"u_li", u"u_wang"):
print(u, s.check(u, u"tb_order", u"SELECT"))
实跑输出:
--------------------------------------------------------------------
角色管理:把权限打包发给一群人
--------------------------------------------------------------------
① 为什么要角色:不打包就得一个个发
--------------------------------------------------------------------
五个人的权限一样,逐个 GRANT 就是五遍;来第六个人,再发一遍。
角色就是「权限的包」:把权限发给角色,把人塞进角色。
好处有三个: 加人只加一次成员关系;改权限只改一处;看角色就知道这人能干什么。
② 固定服务器角色:装好就有,改不了权限
--------------------------------------------------------------------
| 角色 | 能干什么 |
|---|---|
| sysadmin | 服务器上什么都能干(sa 就在这个角色里) |
| securityadmin | 管登录名和密码(能给自己提权的角色,慎给) |
| dbcreator | 建库、改库、删库 |
| bulkadmin | 执行 BULK INSERT 批量导入 |
| processadmin | 杀会话(KILL) |
固定角色的权限是写死的,你只能「把人塞进去」,不能改它有什么权限。这是好事:它不会被误改。
securityadmin 要特别当心: 它能管登录名,等于能给自己开任何权限——所以它实际上接近 sysadmin,
别当成「只管账号的小角色」。
③ 数据库角色:库内的一套
--------------------------------------------------------------------
| 角色 | 能干什么 |
|---|---|
| db_owner | 这个库里的神:建表、删表、授权都行 |
| db_datareader | 能读所有表(SELECT) |
| db_datawriter | 能写所有表(增删改) |
| db_denydatareader | 拒绝读所有表(DENY 优先,会盖掉上面的授权) |
| db_ddladmin | 能改表结构,但不能动数据 |
这几个角色是「按库」给的,同一个账号在 A 库是读者、在 B 库可以是写者。
这也是为什么「用户」和「登录名」要分开:登录名只有一个,用户可以在每个库里不一样。
④ 自定义角色:按业务打包
--------------------------------------------------------------------
[教材原码 · 本沙箱不执行]
| USE sales;
| CREATE ROLE r_sales_read;
| GRANT SELECT ON tb_order TO r_sales_read;
| GRANT SELECT ON tb_goods TO r_sales_read;
| EXEC sp_addrolemember 'r_sales_read', 'u_zhang'; -- 老写法
| ALTER ROLE r_sales_read ADD MEMBER u_li; -- 新写法(2012 之后)
| GO
复刻模型里跑一遍「成员 → 角色 → 权限」的继承:
| 谁 | 对 tb_order 能 SELECT 吗 | 对 tb_goods 能 SELECT 吗 |
|---|---|---|
| u_zhang(在角色里) | 允许 | 允许 |
| u_li(在角色里) | 允许 | 允许 |
| u_wang(不在角色里) | 拒绝(无授权) | 拒绝(无授权) |
两行「允许」、一行「无授权」——角色就是这么工作的。
注意 u_wang 是「无授权」不是「拒绝」,这个区别在下一段很关键。
⑤ 角色能套角色:别套太深
--------------------------------------------------------------------
SQL Server 允许角色包含角色(ALTER ROLE r_outer ADD MEMBER r_inner),权限会一路继承下去。
复刻模型里也是这么算的——principals() 会递归展开。
但套深了没人看得懂。 一个账号能不能查某张表,得顺着三层角色往上翻——排错时这是灾难。
经验值:角色最多两层(业务角色 → 基础角色),再多就该拆了。
还有一个坑: 角色成员关系是「加进去就生效」的,不需要重新登录;
但权限缓存可能要等一会儿(SQL Server 会在下次执行时重新编译)。
所以「刚授的权没生效」先别怀疑语句写错了。
principals() 那十几行就是「继承」的全部实现:
从一个主体出发,往上找它所属的角色、再找角色所属的角色,直到找不出新的。
SQL Server 内部也是这个思路(它把结果算好缓存起来)。
check() 里的顺序很关键:先查 deny、再查 grant。
这个顺序就是「DENY 优先」的落地方式——只要命中一条拒绝,直接返回,后面的授权看都不看。
「拒绝(无授权)」和「拒绝(DENY 优先)」是两种不同的拒绝。
排错时这个区别决定你往哪查: 前者去找「谁该授权没授」,后者去找「谁挂了个 DENY」。
复刻模型删掉了所有「真实数据库的细节」(权限缓存、schema、所有权链),
留下的是「谁能干什么」这一层语义——理解了这一层,再去记 SQL Server 的具体语法就快得多。
30 秒实验
- 把
u_li从r_sales_read里移除,再检查一次。 他变成「拒绝(无授权)」——这就是「把人从角色里拿出来」的效果,比逐个REVOKE干净。 - 建一个
r_all角色,把它加进r_sales_read的成员,看u_zhang的principals()返回什么。 会多一个r_all——这就是「角色套角色」的展开。 - 想一想:如果一个人既要「读订单」又要「不能看金额列」,你会怎么设计?
别用列级权限硬拦——建一个不含金额列的视图,给他视图的读权限,
干净、好维护、也不会因为
SELECT *报错。
常见误区
- 「
securityadmin是个小角色」。 它能改密码、能建登录名,等于能装成任何人——实际权限接近sysadmin。 - 「把角色套起来更灵活」。 灵活是真的,看不懂也是真的。 三层以上,「这人为什么能读这张表」就没人答得上来。 经验值两层。
- 「给了
db_owner就什么都能干」。 在sysadmin面前不成立。 系统管理员在任何库里都最高, 所以别指望用库角色去限制一个服务器级管理员。
自测三问(点开对答案)
1. 固定服务器角色和数据库角色差在哪? —— **固定服务器角色管整台服务器**(建库、管登录名、杀会话),**权限写死改不了**; **数据库角色只管一个库里的对象**(读、写、改结构)。 2. 为什么说 `securityadmin` 接近 `sysadmin`? —— **因为它能改密码、能建登录名**——**能改别人的密码就等于能装成别人登录**, **等于能给自己开任何权限。** 3. 角色最多套几层比较合适?为什么? —— **两层左右。** 套深了**「这人为什么有权限」要顺着好几层翻**, **排错成本会超过它带来的灵活性。**下一篇 16.3 讲授权语句:
GRANT、REVOKE、DENY 三者到底差在哪,以及为什么「DENY 优先」既是最好用的工具,
也是最常见的事故源头。
- ☐ (本篇完,下一篇 16.3 权限管理)