🪟 第17章 数据安全
📄 一句话概括

权限挡「不该进来的」,脱敏和行级安全挡「进来了乱看」,审计负责「事后能证明」。 实测审计触发器:改价 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 秒实验

  1. 把触发器里的 WHEN OLD.price <> NEW.price 删掉,再改一次数据(这次价格不变)。 审计表会多一条「改前改后一样」的记录——说明 WHEN 是「过滤噪音」用的,不是必需的。
  2. 把 mask() 改成「只留后四位」(u"····" + s[-4:]),看输出。 前三位也没了——掩码留多少,取决于业务「怎么核对」,没有统一答案。
  3. 想一想:审计日志本身要不要备份、要不要清理? 要备份(它也是证据),而且要定保留期—— 留太短查不到旧事,留太久又成了「泄露风险」。 一般是半年到两年,按合规要求定。

常见误区

  1. 「有了审计就安全了」。 审计是事后手段——它不阻止任何事情发生,只保证「事后能查清」。 别把它当防线,它是「监控录像」。
  2. 「视图能代替行级权限」。 视图能被绕过(有表权限的人直接查表)。 真正的行级安全在引擎层(SQL Server 的 SECURITY POLICY),视图只是权宜之计。
  3. 「脱敏就是把数据改掉」。 改掉就回不来了,业务也没法核对。脱敏是「展示时掩一部分」,原数据要完整保留。
自测三问(点开对答案) 1. 审计触发器至少该记哪几样东西? —— **谁、什么时候、改了哪一行、改前是多少、改后是多少。** **少了「改前改后」,就只能证明「有人动过」,证明不了「改成了什么」。** 2. 为什么「用视图做行级过滤」不够安全? —— **因为有表权限的人可以绕过视图直接查表。** **要配合「只授视图、不授表」;真正的行级安全要在引擎层做。** 3. 脱敏和加密的区别是什么? —— **脱敏是「展示时掩掉一部分」,数据本身还是完整的**; **加密是「存的就是密文」,要还原得有钥匙。** **前者防误看,后者防拿走。**

下一篇 17.3 讲备份与恢复: 完整、差异、日志三种备份怎么接成一条链,为什么还原顺序错了会「比不做还糟」, 以及 RPO/RTO 怎么决定备份频率。

相关笔记