权限挡「不该进来的」,脱敏和行级安全挡「进来了乱看」,审计负责「事后能证明」。 实测审计触发器:改价 4999→4599,改前改后都留下来了——只记「谁改了」不够。 视图做行级过滤能挡住查询,但挡不住「绕过视图直接查表」,还得配合授权。
17.1 讲「威胁在哪」,这一节讲「拿什么挡」。
教材分三小节:数据安全管理、SQL Server 数据安全、备份与权限之外的第三层。
「第三层」这个说法很准: 第 16 章的权限是第一层,备份是第二层,
这一节讲的审计、脱敏、行级安全是第三层——它们不阻止「访问」,而是限制「看到了什么」。
审计触发器、脱敏、视图过滤都是真跑的;
SQL Server 独有的 SQL Server Audit、Dynamic Data Masking、行级安全策略照贴原码。
一、三层防线,各挡一段
| 层 | 挡什么 | 挡不住什么 |
|---|---|---|
| 权限(16 章) | 不该进来的人 | 能进来的人乱看 |
| 脱敏、行级安全 | 进来了也只能看该看的 | 能导出的数据仍然能导出 |
| 审计 | 事后能查、能追责 | 事情已经发生了 |
| 备份 | 丢了能回来 | 泄露了追不回来 |
这四层不是「选一个」,是「都得有」。 少哪一层,出事时就从那个缺口漏出去。 注意第三行那句话:审计挡不住事故,它只保证「事后能查清」。 但这一条已经很值钱了——没有审计,你连「丢了哪些数据、谁干的」都不知道。
二、审计:先能证明「谁改的」
给价格加一个审计触发器(12.2 讲过写法):
CREATE TRIGGER tr_goods_audit AFTER UPDATE ON tb_goods
FOR EACH ROW
WHEN OLD.price <> NEW.price
BEGIN
INSERT INTO tb_audit (who, act, gid, old_price, new_price, at)
VALUES ('app_user', 'UPDATE', OLD.id, OLD.price, NEW.price, datetime('now'));
END
| gid | old_price | new_price | act |
|---|---|---|---|
| 1001 | 4999 | 4599 | UPDATE |
改前 4999、改后 4599 都留下了,这才叫「能证明」。
只记「谁在几点改了什么表」不够——出事时要的是「改成了多少」。
SQL Server 上有现成的工具: SQL Server Audit,
或者 Change Tracking/Change Data Capture,不用自己写触发器。
但原理一样:把「变化」单独存一份。
最后那句话最容易被忽略: 审计表如果跟业务表在同一个库、
同一个账号能改,那它就不是审计,是「另一个可以被篡改的日志」。
所以审计数据要单独放、单独授权、只允许写不允许改。
三、脱敏:能看业务,看不见隐私
需求:客服要按手机号找人,但不能看到完整号码。 做法是「掩码」:
| id | uname | 原手机号 | 掩码后 |
|---|---|---|---|
| 1 | member001 | 13800000001 | 138····0001 |
| 2 | member002 | 13800000002 | 138····0002 |
掩码保留了「能核对」的信息(前三位 + 后四位),丢掉了「能骚扰」的部分。
这就是脱敏的核心:不是「删掉」,是「留够用、藏要紧」。
但它不能靠视图里的 substr 硬写。 因为业务要求「能按手机号精确查」,
所以正确做法是存两列: 一列密文(展示用)、一列哈希(查询用)(16.4 讲过)。
SQL Server 2016 之后有 Dynamic Data Masking,
一行 MASKED WITH (FUNCTION='partial(...)') 就能给列加掩码,
但它只对「没有高权限的人」生效——sysadmin 看到的还是明文。
所以它是「防误看」,不是「防拿走」。
四、行级安全:每个人只能看自己那几行
权限管的是「能不能碰这张表」,管不了「能碰表里的哪几行」。 「销售只能看自己客户」这种需求,就得靠行级过滤。 SQLite 没有行级安全,用视图复刻(真的建了视图,真的只返回自己的行):
| id | owner | total |
|---|---|---|
| 1 | zhang | 5088.9 |
| 3 | zhang | 399.0 |
zhang 通过这个视图只看到自己的两单——li 的那一单根本不在结果里。
但要看清这是「视图过滤」,不是「权限」:
视图挡不住「绕过视图直接查表」的人,所以还得配合「不给表的 SELECT、只给视图的权限」。
SQL Server 2016 之后有真正的行级安全(CREATE SECURITY POLICY + 内联表值函数),
它在引擎层过滤,绕不过去,比视图可靠。
五、数据安全管理:制度那一半
教材讲「数据安全管理」时列了制度层面的东西:分级分类、访问审批、定期复查、离职回收、应急响应。 这些听着像「走流程」,但它们才是主力。
| 制度 | 解决什么问题 |
|---|---|
| 数据分级(公开/内部/机密) | 知道哪些要重点保护,别一把抓 |
| 访问审批 | 权不是「顺手给的」,是「申请来的」 |
| 定期复查 | 离职、调岗留下的权限,靠复查清掉 |
| 应急响应预案 | 出事时知道先做什么,而不是先开会 |
第三条最容易被跳过,也最容易出事。 一个人调岗了、权限没回收,他还能看原来那摊数据—— 而这种事只有「定期复查」能发现,任何技术手段都发现不了。 第四条更实在: 真出事的时候,「先断网、先保现场、先评估影响」这个顺序 如果没提前定好,现场一定乱。 预案不是形式,是把决策提前做完。
六、动手 Demo:审计、脱敏、行级安全:第三层长什么样
这一段跑三件事:给价格加审计触发器看它记了什么、把手机号掩码、再用视图做一次行级过滤。 三者都不是「挡住访问」,而是「限制看到了什么、留下什么痕迹」——这就是「权限之外」的意思。
import sqlite3
c = sqlite3.connect(u"audit.db", isolation_level=None)
c.execute(u"CREATE TABLE tb_goods (id INTEGER PRIMARY KEY, gname TEXT, price REAL)")
c.execute(u"CREATE TABLE tb_audit (id INTEGER PRIMARY KEY AUTOINCREMENT,"
u" who TEXT, act TEXT, gid INT, old_price REAL, new_price REAL, at TEXT)")
c.execute(u"""
CREATE TRIGGER tr_goods_audit AFTER UPDATE ON tb_goods
FOR EACH ROW WHEN OLD.price <> NEW.price
BEGIN
INSERT INTO tb_audit (who, act, gid, old_price, new_price, at)
VALUES ('app_user', 'UPDATE', OLD.id, OLD.price, NEW.price, datetime('now'));
END
""")
c.execute(u"UPDATE tb_goods SET price = 4599 WHERE id = 1001")
print(c.execute(u"SELECT gid, old_price, new_price FROM tb_audit").fetchall())
# 脱敏:留前三位 + 后四位
def mask(s):
return s[:3] + u"····" + s[-4:] if len(s) >= 8 else s
# 行级过滤:用视图
c.execute(u"CREATE VIEW v_zhang_order AS"
u" SELECT * FROM tb_order WHERE owner = 'zhang'")
实跑输出:
--------------------------------------------------------------------
权限和备份之外:审计、脱敏、行级安全
--------------------------------------------------------------------
① 审计:先能证明「谁改的」
--------------------------------------------------------------------
给价格加了审计触发器(12.2 讲过写法)。改一次价,看它记了什么:
1> UPDATE tb_goods SET price = 4599 WHERE id = 1001
1> SELECT gid, old_price, new_price, act FROM tb_audit
gid old_price new_price act
----------------------------------
1001 4999 4599 UPDATE
(1 行受影响)
改前 4999、改后 4599 都留下了。 这才叫「能证明」。
只记「谁在几点改了什么表」不够——出事时要的是「改成了多少」。
SQL Server 上有现成的工具: SQL Server Audit 或 Change Tracking/Change Data Capture,
不用自己写触发器。但原理一样:把「变化」单独存一份,而且这份东西不能被改数据的人改掉。
最后那句话最容易被忽略: 审计表如果跟业务表在同一个库、同一个账号能改,那它就不是审计,
是「另一个可以被篡改的日志」。
② 脱敏:能看业务,看不见隐私
--------------------------------------------------------------------
需求:客服要按手机号找人,但不能看到完整号码。 做法是「掩码」:
| id | uname | 原手机号 | 掩码后 |
|---|---|---|---|
| 1 | member001 | 13800000001 | 138····0001 |
| 2 | member002 | 13800000002 | 138····0002 |
掩码保留了「能核对」的信息(前三位 + 后四位),丢掉了「能骚扰」的部分。
它不能靠视图里的 substr 硬写——因为要能「按手机号精确查」,所以正确做法是「存两列」:
一列密文(展示用)、一列哈希(查询用)(16.4 讲过)。
SQL Server 2016 之后有 Dynamic Data Masking,
一行 MASKED WITH (FUNCTION='partial(...)') 就能给列加掩码,
但它只对「没有高权限的人」生效——sysadmin 看到的还是明文。所以它是「防误看」,不是「防拿走」。
③ 行级安全:每个人只能看自己那几行
--------------------------------------------------------------------
权限管的是「能不能碰这张表」,管不了「能碰表里的哪几行」。
「销售只能看自己客户」这种需求,就得靠行级过滤。
SQLite 没有行级安全,用视图复刻(真的建了视图,真的只返回自己的行):
| id | owner | total |
|---|---|---:|
| 1 | zhang | 5088.9 |
| 3 | zhang | 399.0 |
zhang 通过这个视图只能看到自己的两单——li 的那一单根本不在结果里。
注意这是「视图过滤」,不是「权限」: 视图挡不住「绕过视图直接查表」的人,
所以还得配合「不给表的 SELECT 权限、只给视图的权限」。
SQL Server 2016 之后有真正的行级安全(CREATE SECURITY POLICY + 内联表值函数),它在引擎层过滤,
绕不过去,比视图可靠。
④ 三层防线,各挡一段
--------------------------------------------------------------------
| 层 | 挡什么 | 挡不住什么 |
|---|---|---|
| 权限(16 章) | 不该进来的人 | 能进来的人乱看 |
| 脱敏、行级安全 | 进来了也只能看该看的 | 能导出的数据仍然能导出 |
| 审计 | 事后能查、能追责 | 事情已经发生了 |
| 备份 | 丢了能回来 | 泄露了追不回来 |
这四层不是「选一个」,是「都得有」。 少哪一层,出事时就从那个缺口漏出去。
审计那条输出是 (1001, 4999.0, 4599.0)。 三个数就是全部价值:
谁改的、改前多少、改后多少。 少了任何一个,追责和回溯都无从谈起。
脱敏那三行只是「展示层」的处理,真正的难点在「还能不能查」。
如果业务要按手机号搜索,只掩码是不够的——得另存哈希列(16.4 讲过)。
视图那两行说明它确实只返回了 zhang 的单子。
但视图不是权限: 有表权限的人一句 SELECT * FROM tb_order 就绕过它了。
所以「视图 + 只授视图不授表」要一起做。
这三样东西的共同点是「不阻止访问,只限制视野」。
也正因为如此,它们不能替代权限和备份——四层是叠加关系,不是替代关系。
30 秒实验
- 把触发器里的
WHEN OLD.price <> NEW.price删掉,再改一次数据(这次价格不变)。 审计表会多一条「改前改后一样」的记录——说明WHEN是「过滤噪音」用的,不是必需的。 - 把
mask()改成「只留后四位」(u"····" + s[-4:]),看输出。 前三位也没了——掩码留多少,取决于业务「怎么核对」,没有统一答案。 - 想一想:审计日志本身要不要备份、要不要清理? 要备份(它也是证据),而且要定保留期—— 留太短查不到旧事,留太久又成了「泄露风险」。 一般是半年到两年,按合规要求定。
常见误区
- 「有了审计就安全了」。 审计是事后手段——它不阻止任何事情发生,只保证「事后能查清」。 别把它当防线,它是「监控录像」。
- 「视图能代替行级权限」。
视图能被绕过(有表权限的人直接查表)。
真正的行级安全在引擎层(SQL Server 的
SECURITY POLICY),视图只是权宜之计。 - 「脱敏就是把数据改掉」。 改掉就回不来了,业务也没法核对。脱敏是「展示时掩一部分」,原数据要完整保留。
自测三问(点开对答案)
1. 审计触发器至少该记哪几样东西? —— **谁、什么时候、改了哪一行、改前是多少、改后是多少。** **少了「改前改后」,就只能证明「有人动过」,证明不了「改成了什么」。** 2. 为什么「用视图做行级过滤」不够安全? —— **因为有表权限的人可以绕过视图直接查表。** **要配合「只授视图、不授表」;真正的行级安全要在引擎层做。** 3. 脱敏和加密的区别是什么? —— **脱敏是「展示时掩掉一部分」,数据本身还是完整的**; **加密是「存的就是密文」,要还原得有钥匙。** **前者防误看,后者防拿走。**下一篇 17.3 讲备份与恢复: 完整、差异、日志三种备份怎么接成一条链,为什么还原顺序错了会「比不做还糟」, 以及 RPO/RTO 怎么决定备份频率。
- ☐ (本篇完,下一篇 17.3 数据备份与恢复)