ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

数据库DCL权限管理实战:GRANT、REVOKE与DENY的核心原理与最佳实践

数据库DCL权限管理实战:GRANT、REVOKE与DENY的核心原理与最佳实践 提到SQL很多开发者脑子里第一反应是SELECT、INSERT、UPDATE、DELETE这些DML语句再往前一步是CREATE TABLE这类DDL。但我做了多年数据库运维和架构工作发现真正容易出事的环节往往集中在被大多数人一带而过的DCL上。DCL就是Data Control Language数据控制语言核心命令就三个GRANT、REVOKE、DENY。它不碰表结构也不碰具体数据干的全是权限的授予、回收和拒绝。这篇文章不讲教科书式的语法手册只讲DCL在真实生产环境里怎么用、怎么坑、怎么排查。适合刚接手数据库权限的DBA、天天被业务方催着“开个只读账号”的后端开发以及需要规范数据权限的数据平台负责人。不管你是用MySQL、SQL Server还是Oracle这套思路都通用细节差异我会在对应位置说清楚。1. 先搞清楚DCL到底管什么1.1 权限管理要解决的三个问题数据库从来不是一个“能连上就行”的东西。你拿root或者sa账号连上数据库那一刻确实很爽但爽完之后所有风险都压在你头上。权限管理本质上是在回答三个问题谁能登录数据库登录之后能看到哪些数据看到之后还能不能改、能不能删、能不能执行存储过程第一个问题叫身份认证第二个问题叫对象可见性第三个问题叫操作授权。你可能听说过Authentication和Authorization这两个英文词很多人经常搞混。身份认证处理的是“你是不是你”对应登录名和密码那一层操作授权处理的是“你能干什么”对应数据库内部的权限体系。DCL主要管的是后面这层也就是操作授权。我见过不少团队给新同事创建了登录名却忘了在数据库里映射用户结果对方连接是成功的但什么都查不到第一反应以为是数据库坏了实际上就是权限没给全。这种情况在SQL Server里特别常见后面我会专门展开。权限管理不只是安全部门的事它直接影响日常开发效率。权限给粗了应用出故障时难以追溯权限给细了业务方天天提工单烦你。DCL的价值就是让你在安全和效率之间找到一个可控的平衡点。1.2 DCL与DDL、DML的边界SQL按功能划分通常被分成四类。DDL管结构DML管数据DCL管权限TCL管事务。这几种语句在语法上长得像而且经常成对出现很多人容易混淆。SQL类别典型语句作用对象风险等级DDLCREATE、ALTER、DROP库、表、索引、视图等结构高误操作会直接破坏结构DMLSELECT、INSERT、UPDATE、DELETE表、视图中的数据中高误操作影响数据DCLGRANT、REVOKE、DENY用户、角色、对象权限高配置不当会造成越权或可用性事故TCLBEGIN、COMMIT、ROLLBACK事务中影响数据一致性DCL和DDL/DML的边界在于它不直接操作数据也不修改表结构它决定的是“谁可以操作哪些数据、哪些结构”。打一个比方DML是开车DDL是造车DCL是发驾照和制定限行政策。驾照发错了后果往往比一次违章严重得多。2. 权限体系背后的“人”与“角色”2.1 登录名与用户不是一回事这是我在实际工作里遇到最多人踩坑的知识点。在SQL Server里登录名Login和数据库用户User是两个完全不同的概念。登录名负责让你连接SQL Server实例用户负责让你在指定数据库里有操作身份。你创建了一个登录名只能说明你能进大门你还得在每一个需要访问的数据库里把这个登录名映射成一个数据库用户并给它授权。MySQL里稍微简化了一些创建用户时会把连接来源和权限绑定在一起比如CREATE USER report% IDENTIFIED BY password这个report用户能在哪些主机登录由后面的host控制。但不管哪种数据库都要理解“连接身份”和“库内授权”是两个层面。打个比方登录名是门禁卡数据库用户是门牌。你有门禁卡只能进大楼但进不了某个房间只有门牌号和房间门禁都匹配你才能坐下干活。很多权限问题追根溯源就是卡在这一层。2.2 角色权限的批处理工具如果你有100个业务账号每个账号都要手工授权那基本是一场灾难。角色的出现就是为了解决这个问题。角色是一组权限的集合你可以把权限授予角色再把角色授予用户用户就间接拥有了这组权限。比如你维护一个只读角色包含所有业务表的SELECT权限。新同事入职直接把他的账号加入这个角色立刻能查数离职了把账号移出角色权限立刻收回。这个流程看起来平淡无奇但在几十个账号、十几套环境的场景下能省掉你大量的重复操作。角色还有一个好处权限变更时可以集中管理。某个表从敏感变成公开你只需要调整角色拥有的权限所有属于该角色的账号会同步生效不用逐个账号去改。这就是批处理的艺术。2.3 最小权限原则最小权限原则听起来像安全教材里的口号但实际用起来是真能救命的。它的含义很简单每个用户、每个应用账号只拥有完成自己工作所必需的最小权限不多给一秒。我自己经历过一次教训。某次内部系统联调图省事给测试账号授予了全部权限后来这个账号在自动巡检任务里误触发了DELETE操作直接把配置表清空了几万行。好在有备份但那次恢复花了两个小时。如果当时遵守最小权限原则只给SELECT和INSERT事故根本不会发生。最小权限原则还能限制攻击面。即使应用层有SQL注入漏洞如果数据库账号只拥有最小权限攻击者最坏也只能读取他应该读的那部分数据无法删除全库、无法改表结构。权限越界一步风险就放大一个数量级。这句话我建议贴在工位上。3. GRANT授权从入门到写稳3.1 GRANT的标准语法与参数GRANT是DCL里最常用的命令结构很清晰GRANT 权限 ON 对象 TO 接受者。不同数据库的语法有差异但核心思路一致。MySQL标准写法GRANT SELECT, INSERT, UPDATE ON mydb.* TO app%; GRANT SELECT ON mydb.orders TO report192.168.10.%;SQL Server标准写法GRANT SELECT ON OBJECT::mydb.dbo.orders TO [domain\report]; GRANT EXECUTE ON OBJECT::mydb.dbo.proc_order_summary TO [domain\app];注意MySQL里%表示任意主机localhost表示本机。生产环境建议按需限制来源主机比如报表账号只允许从跳板机IP登录不要图省事全部用%。这个习惯能挡住一部分来自外部的连接尝试。3.2 实操场景报表用户只读最常见的需求来了业务方过来说“给我开一个只读账号我要查报表。”这时候你脑海里要立刻浮现出“只读”两个字对应到数据库操作就是SELECT最多再给点视图、函数的权限绝对不要顺手把UPDATE、DELETE都给了。真实操作CREATE USER report192.168.10.% IDENTIFIED BY Report2024#Secure; GRANT SELECT ON mydb.* TO report192.168.10.%; FLUSH PRIVILEGES;第一行创建账号第二行授权第三行刷新权限。这里顺便回答一个高频疑问MySQL授权之后到底要不要FLUSH PRIVILEGES实际上MySQL在正常授权后权限会立即生效FLUSH PRIVILEGES更多是清理缓存或者在你直接用SQL语句修改了mysql.user表之后才需要执行。不过很多团队的运维脚本里依然保留了这条命令倒也无伤大雅但你要知道它不是每次授权后的必需品。另外授权范围mydb.*表示mydb库下所有表。如果只想让报表用户看部分表就写成mydb.orders这种表级授权权限越小越稳。3.3 实操场景开发人员部分权限开发环境又是另一种需求。开发同学需要测试INSERT、UPDATE可能还需要ALTER TABLE来加字段。但和报表账号只读的逻辑一样你要控制范围和环境。CREATE USER dev% IDENTIFIED BY Dev2024#Secure; GRANT SELECT, INSERT, UPDATE, DELETE, ALTER ON devdb.* TO dev%;这里有个我反复强调的教训生产环境不建议给开发账号ALTER权限除非有严格的变更审批流程。我在客户现场见过开发账号直接ALTER生产表把字段类型改错导致应用大面积报错回滚。开发权限和生产权限一定要分开同一套账号体系管两个环境迟早会出事。3.4 WITH GRANT OPTION一把双刃剑GRANT里有一个容易被忽视的参数WITH GRANT OPTION。它的意思是被授权的用户可以把自己拥有的权限再授予别人。看起来很方便实际上是把授权能力外放容易出现不可控的权限蔓延。我一般的建议是只有DBA或专门负责权限管理的账号可以使用这个选项普通业务账号一律不给。原因很简单权限链条一旦延伸你很难追踪谁在什么时候给谁开了什么权限。权限审计会变成一团乱麻。如果团队里确有必要务必定期拉取授权清单检查。3.5 权限粒度库、表、列、行GRANT的权限粒度是分层的。最粗的是服务器级然后是数据库级、表级、列级甚至行级。粒度越细管理成本越高但安全性也越好。库级GRANT SELECT ON mydb.* TO userhost;一次性给整个库的读权限。表级GRANT SELECT ON mydb.orders TO userhost;只给这一张表。列级GRANT SELECT (customer_name, email) ON mydb.customers TO userhost;只允许查看指定字段。行级MySQL 8.0没有原生行级授权但可以通过创建带WHERE条件的视图然后授权视图来实现行级限制。SQL Server可以通过行级安全Row-Level Security实现。很多人一上来就给库级权限图省事。但如果面对的是用户身份证号、手机号这类敏感数据库级权限就意味着所有人都能看所有字段这时候至少要做到表级或列级。4. REVOKE与DENY收回与拒绝的权力平衡4.1 REVOKE收回已授权的权限有授就有收。REVOKE是GRANT的反向操作把之前授予的某个权限拿回来。语法也很对称。REVOKE SELECT ON mydb.* FROM report192.168.10.%;执行前我一般会先查一遍这个账号的完整权限清单再决定收回哪些。最怕一种情况你想收回某张表的权限结果用户通过角色依然拥有这张表的SELECT权限REVOKE对象权限收了个寂寞。所以授权和收权都要站在角色和对象两个维度去看。如果用户已经不存在了REVOKE执行会报错。这很合理你不可能从一个不存在的人身上收回权限。遇到这种情况直接确认账号已经删除即可不用纠结。4.2 DENY硬性拒绝DENY是SQL Server特有的命令MySQL没有对应概念。它的含义很直接禁止这个用户做某个操作而且优先级非常高。哪怕用户通过角色已经间接获得了权限只要存在一个DENY最终结果就是无权限。DENY DELETE ON OBJECT::mydb.dbo.orders TO [domain\app];有人会问这跟REVOKE有什么区别我把两者放在一起对比记忆。REVOKE是“收回已有的授权”收回之后用户可能因为其他途径角色、组成员关系再次获得权限DENY则是硬性拒绝就像你在门禁系统里把这张卡拉黑了无论它通过什么方式进入最终都会被拦住。4.3 权限冲突时DENY优先SQL Server权限判断有一个重要规则当同一权限同时存在GRANT和DENY时DENY生效。这个设计是为了保证安全但也经常让人头疼。比如你把某个角色授予了用户角色包含SELECT权限但你又在某个对象上对用户单独做了DENY SELECT那这个用户就是查不了那个对象。过去我在项目里踩过这个坑。当时为了应急处置对一个账号DENY了某张表的SELECT后来业务方反馈看不到数据排查了半天才发现是这条历史DENY在作怪。所以用DENY要慎重最好在变更记录里注明原因和日期方便后人排查。4.4 现实案例误授权的回滚有一次我接手一个项目前任DBA为了省事把db_owner角色直接授予了十几个应用账号。这意味着所有应用账号都能修改表结构。后来某个自动化脚本误改了生产表字段幸好回滚脚本写得及时。处理分三步走。第一步把误操作账号从db_owner角色中移除。第二步按业务需要重新授予应该有的权限通常是db_datareader和db_datawriter。第三步用查询脚本把权限清单导出来邮件发给各应用负责人确认。这样既完成了收权又留下了审计记录后续再出问题也有据可查。5. 角色设计一劳永逸的权限规划5.1 固定服务器角色与固定数据库角色SQL Server提供了很多固定角色官方帮你打包好了权限集合。服务器级有sysadmin、securityadmin、dbcreator等数据库级有db_owner、db_datareader、db_datawriter等。MySQL 8.0也开始原生支持角色体系通过CREATE ROLE创建自定义角色。只读需求在SQL Server里可以直接用db_datareader这个角色拥有读取当前数据库所有表的权限。在MySQL里没有这个固定角色需要自己创建。MySQL官方文档也建议通过角色来管理权限而不是对每个用户单独授权。这个趋势很明确不管用什么数据库角色都应该成为权限分配的核心。5.2 自定义角色的设计与分工角色设计的原则是按职责拆分而不是按数据库结构拆分。我常用的设计大致分三类只读角色只有SELECT权限供BI分析师、外部查询使用。应用读写角色SELECT、INSERT、UPDATE、DELETE供后端服务使用。开发变更角色额外带上ALTER、CREATE等DDL权限只用于开发测试环境。角色权限范围适用对象风险app_readonlySELECTBI报表、运营取数低app_rwSELECT、INSERT、UPDATE、DELETE后端应用账号中dev_ddl读写权限加ALTER/CREATE/INDEX开发测试环境高生产环境的角色权限要保持相对稳定不要今天给这个角色加删除权限明天又去掉时间长了没人记得清楚到底谁拥有什么权限。角色一旦变成“技术债”权限审计就失去了意义。5.3 实战创建只读角色并批量授权MySQL 8.0操作示例CREATE ROLE readonly_role; GRANT SELECT ON mydb.* TO readonly_role; GRANT readonly_role TO report192.168.10.%; SET DEFAULT ROLE readonly_role TO report192.168.10.%;这里有两个关键步骤值得注意。第一步把权限给角色。第二步把角色给用户。如果有多个报表账号只需要重复最后两行把角色分配给其他账号非常方便。设置DEFAULT ROLE是为了让用户登录后默认启用这个角色否则即使角色授权了不激活也查不了数据。SQL Server对应操作CREATE ROLE [app_readonly]; GRANT SELECT ON SCHEMA::dbo TO [app_readonly]; ALTER ROLE [app_readonly] ADD MEMBER [domain\report];SQL Server的架构Schema是天然的对象分组方式在架构级别授权可以一次性覆盖该架构下所有对象比一张表一张表授权高效得多。6. 对象级权限表、视图、存储过程的精细控制6.1 表与视图权限视图是权限控制的好帮手。很多时候业务方只想看到部分字段比如客户表里只有姓名、城市、注册时间不能看到身份证号和手机号。这时候你不应该把整张表的权限交出去而是创建一个包含必要字段的视图再授权视图。CREATE VIEW v_customer_basic AS SELECT id, name, city, signup_date FROM customers; GRANT SELECT ON mydb.v_customer_basic TO report192.168.10.%;视图背后虽然查的是原表但用户只能访问视图暴露的字段原表权限没有给。这个方案比列级授权更直观也更容易维护。以后敏感字段布控变了只需要改视图定义不需要重新执行授权。6.2 存储过程的EXECUTE权限存储过程是应用访问数据库的重要入口。规范的架构里应用不应该直接对表做增删改而是通过存储过程完成业务操作。这时候你只需要给应用账号EXECUTE权限统一的表级权限一概不给。GRANT EXECUTE ON OBJECT::mydb.dbo.proc_order_create TO [domain\app];但是要注意所有权链问题。如果存储过程的拥有者和底表的拥有者是同一个用户那么执行存储过程的用户不需要对表有任何权限SQL Server会自动放行。如果拥有者不是同一个用户执行用户可能还是需要拥有底表的相应权限否则会报权限不足。这个坑排查起来非常费劲我把它列进后面的常见问题。6.3 列级权限与行级安全列级权限在SQL Server里支持得很好可以精确到字段。MySQL也支持列级GRANT适合保护敏感字段。GRANT SELECT (card_no, amount) ON mydb.transactions TO risk%; GRANT SELECT (status, created_at) ON mydb.transactions TO risk%;这个示例表示risk用户只能查看transactions表里的card_no、amount、status、created_at字段其他字段一概拒绝。列级权限看起来挺好但维护成本高字段一变你就要跟着改授权。我的习惯是核心敏感表用列级授权其余场景用视图方案。行级安全在SQL Server中可以通过RLS实现MySQL 8.0没有原生的行级授权但可以创建过滤视图比如只让某个账号看到状态为“已生效”的记录。视图的本质就是把行级过滤封装在SQL语句里。7. 权限管理实战从零搭建安全的数据库用户7.1 设定目标与场景为了把前面的内容串起来我用一个典型场景来演示完整流程新入职一个BI分析师需要读取业务库mydb所有表的SELECT权限不需要写权限同时有一个后端应用需要对接订单表和支付表做增删改查。分别在MySQL和SQL Server里把这两个用户创建出来。7.2 MySQL完整操作流程-- 第一步创建只读用户 CREATE USER bi_analyst192.168.10.% IDENTIFIED BY Bi2024#Secure; -- 第二步授予只读权限 GRANT SELECT ON mydb.* TO bi_analyst192.168.10.%; -- 第三步创建应用用户 CREATE USER order_app192.168.20.% IDENTIFIED BY App2024#Secure; -- 第四步授予订单表和支付表的增删改查权限 GRANT SELECT, INSERT, UPDATE, DELETE ON mydb.orders TO order_app192.168.20.%; GRANT SELECT, INSERT, UPDATE, DELETE ON mydb.payments TO order_app192.168.20.%;创建完成之后建议马上做两件事一是用SHOW GRANTS FOR bi_analyst192.168.10.%;确认授权结果二是实际用该用户连接一次验证能查到什么、不能查到什么。授权脚本写完就扔是最危险的用法。7.3 SQL Server完整操作流程-- 第一步创建登录名 CREATE LOGIN [bi_analyst] WITH PASSWORD Bi2024#Secure; -- 第二步在mydb库创建用户并映射登录名 USE mydb; CREATE USER [bi_analyst] FOR LOGIN [bi_analyst]; -- 第三步加入只读角色 ALTER ROLE [db_datareader] ADD MEMBER [bi_analyst]; -- 第四步创建应用登录名 CREATE LOGIN [order_app] WITH PASSWORD App2024#Secure; -- 第五步在mydb库创建用户并授权 USE mydb; CREATE USER [order_app] FOR LOGIN [order_app]; GRANT SELECT, INSERT, UPDATE, DELETE ON OBJECT::dbo.orders TO [order_app]; GRANT SELECT, INSERT, UPDATE, DELETE ON OBJECT::dbo.payments TO [order_app];注意看SQL Server固定角色db_datareader相当于只读集合省去对每张表授权。但这个角色只在当前数据库内生效换一个库就要重新添加成员。7.4 审计与权限回顾权限给出去不是终点定期审计才是。MySQL用SHOW GRANTS查看用户权限SQL Server可以查询系统视图。SELECT dp.name AS principal, dp.type_desc, perm.permission_name, perm.state_desc, obj.name AS object_name FROM sys.database_principals dp LEFT JOIN sys.database_permissions perm ON dp.principal_id perm.grantee_principal_id LEFT JOIN sys.objects obj ON perm.major_id obj.object_id WHERE dp.principal_id 4;把权限清单拉出来按账号维度整理成表格每季度过一遍。谁还在这项目里谁还需要这个权限这两个问题问完权限列表至少能瘦身三分之一。8. 常见权限问题与排查实录8.1 报表用户能登录但查不到数据场景重现用户能登录但一执行SELECT就报错提示对象不存在或者权限被拒绝。排查思路按顺序走三步。第一步确认用户是否映射到了正确的数据库。SQL Server经常出现登录名建了但数据库用户没建的情况相当于你有门禁卡但没进门的资格。第二步确认权限是不是授给了角色但角色没激活MySQL的DEFAULT ROLE没有设置就会这样。第三步检查有没有历史DENY在作怪SQL Server里DENY优先级高于GRANT哪怕角色给了权限也会被DENY拦住。8.2 存储过程执行报权限不足应用调用存储过程报权限不足常见原因有三个。一是没给EXECUTE权限这个最直接。二是所有权链断裂存储过程拥有者和底表拥有者不一致导致执行者需要直接访问底表权限SQL Server才放行。三是存储过程内部使用了动态SQL动态SQL不会继承存储过程的权限上下文需要单独授权依赖对象。我最常遇到的是第二种。排查方法很简单查看存储过程的schema和底表的schema是否一致。如果不一致要么统一schema归属要么给执行者补上底表权限。这两种方案要根据业务安全要求来选前者更推荐。8.3 用户被误删或者权限冲突运维过程中误删用户或者把用户从角色移除后忘记加回来这类问题很常见。我的建议是每次操作前把用户的当前权限导出存档再执行变更。数据库不像文件系统权限删了没有回收站。留一份变更前的快照真出问题还能比对差异快速恢复。8.4 授权后不生效有人问我MySQL授权之后应用还是报权限不足。首先确认是不是新连接MySQL权限是按连接生效的已经建立的旧连接在授权前建立不会自动刷新必须重连。其次确认授权对象是否写对mydb.*和mydb.orders分别是库级和表级差一个点结果完全不同。最后确认host限制授权时如果写了localhost应用从远程IP连接永远匹配不上。8.5 权限变更记录与监控最后分享一个习惯权限变更属于高危操作至少要留变更记录。我通常用一个简单表格记录变更时间、变更人、账号、权限变更内容、变更原因。半年后回看每一行都是清晰的审计轨迹。有条件的话开启数据库审计功能把GRANT和REVOKE操作自动记录下来这样即使有人手动改权限也留下可追踪的痕迹。就我个人体会来说数据库权限这件事真正难的不是GRANT和REVOKE的语法而是在混乱的业务需求里保持清晰的授权边界。每多给一份权限就多背一个风险。给权限前多想一步这是成本最低的安全投入也是DCL能带来的最大价值。
返回列表