应用账号最小权限落地:读写、报表、运维三层账号的划分与管控
为什么我会开始做账号分层先说清楚这篇文章解决的问题应用访问数据库或其他后端资源时账号权限该怎么分。我见过也参与过不少项目早期为了赶进度直接用一个root或者db_admin账号跑所有业务代码。能跑但一旦出事就很麻烦生产环境某条 SQL 慢查询把库拖垮查半天发现是某个报表任务写的全表扫描但账号和业务是同一个日志里根本分不清谁是谁。某个服务被注入或者依赖被投毒攻击者拿到的账号能DROP TABLE。想做审计发现所有操作都是同一个账号等于没有审计。这三个问题本质是同一个权限边界没有跟职责边界对齐。后来我把账号拆成了三层读写账号、报表账号、运维账号。下面讲具体怎么分、怎么配、怎么验证。这里说的是通用思路数据库、消息队列、对象存储都适用我主要以关系型数据库 Python 应用举例。三层账号各自负责什么先把职责定清楚权限才有依据。账号类型职责权限范围使用方读写账号支撑在线业务增删改查业务表的 SELECT/INSERT/UPDATE/DELETE应用服务进程报表账号只读查询、导出、分析业务表 SELECT部分视图 SELECT报表服务、BI、离线任务运维账号建表、改表、加索引、数据修复DDL 全表 DML人工操作、迁移脚本这里有个原则在线业务不需要 DDL报表不需要写人工操作不跑在业务进程里。读写账号给到 DDL 权限是最常见的错误。业务代码里理论上不该出现ALTER TABLE一旦出现说明迁移流程没做好而不是账号权限给少了。报表账号只读看起来简单但要注意它和读写账号读的是同一份数据。如果报表查询很重只读权限并不能阻止它把库读挂。所以报表账号通常还要配合只读副本read replica或者独立的 OLAP 存储这部分后面单独说。运维账号不应该被应用配置引用。我的做法是运维账号的密码只放在人工可访问的密钥管理里不进应用的配置文件、不进 CI 变量。这一点后面会展开。用 Python 把配置隔离做出来分层的关键不是建几个账号而是让代码在结构上不可能用错账号。如果配置里三个账号的密码都写在一起谁都能随手改分层就是形式主义。我的做法是按用途拆配置每个服务的配置里只出现它需要的那个账号。用pydantic-settings我用的版本是 2.x1.x 的 API 不一样举例# config.pyfrompydantic_settingsimportBaseSettings,SettingsConfigDictclassReadWriteDB(BaseSettings):在线业务读写账号host:strport:int5432db:struser:strpassword:strpool_min:int2pool_max:int10model_configSettingsConfigDict(env_prefixRW_DB_)classReportDB(BaseSettings):报表只读账号host:strport:int5432db:struser:strpassword:strpool_min:int1pool_max:int4model_configSettingsConfigDict(env_prefixREPORT_DB_)对应的环境变量RW_DB_HOSTpg-primary.internalRW_DB_USERapp_rwRW_DB_PASSWORD...REPORT_DB_HOSTpg-replica.internalREPORT_DB_USERapp_reportREPORT_DB_PASSWORD...注意报表账号连的是pg-replica读写账号连的是主库。这不是必须的但报表如果直接打主库早晚会出问题。运维账号完全不出现在这里。它由 DBA 或者运维通过堡垒机使用应用代码里没有它的配置类。这样做的意义是一个服务要越权得先改代码引入另一个配置类而不是改一行环境变量。前者在 code review 里会暴露后者不会。数据库侧的授权语句怎么写以 PostgreSQL 为例具体授权语句大致是这样MySQL 语法不同思路一致-- 读写账号CREATEROLE app_rw LOGIN PASSWORD...;GRANTCONNECTONDATABASEappdbTOapp_rw;GRANTUSAGEONSCHEMApublicTOapp_rw;GRANTSELECT,INSERT,UPDATE,DELETEONALLTABLESINSCHEMApublicTOapp_rw;GRANTUSAGE,SELECTONALLSEQUENCESINSCHEMApublicTOapp_rw;-- 报表账号CREATEROLE app_report LOGIN PASSWORD...;GRANTCONNECTONDATABASEappdbTOapp_report;GRANTUSAGEONSCHEMApublicTOapp_report;GRANTSELECTONALLTABLESINSCHEMApublicTOapp_report;-- 运维账号CREATEROLE app_ops LOGIN PASSWORD...;GRANTALLPRIVILEGESONDATABASEappdbTOapp_ops;这里有个很容易漏的点GRANT ... ON ALL TABLES只对当前已存在的表生效。以后新建的表不会自动带上权限。解决办法是设置默认权限ALTERDEFAULTPRIVILEGESINSCHEMApublicGRANTSELECT,INSERT,UPDATE,DELETEONTABLESTOapp_rw;ALTERDEFAULTPRIVILEGESINSCHEMApublicGRANTSELECTONTABLESTOapp_report;【踩坑提醒】ALTER DEFAULT PRIVILEGES是绑定到执行这条语句的角色的。如果你用app_ops建表那默认权限必须由app_ops来设置如果建表用的是另一个迁移账号那要给那个账号设置。这一点我在实际项目里确认过不同角色建的表默认权限确实不生效。另外读写账号要不要给DELETE这取决于业务。如果业务只做软删除那DELETE其实可以不给。权限收紧到刚好够用才是最小。连接池和账号的绑定Python 里用asyncpg或者SQLAlchemy都可以思路是每个账号一个独立的 engine / pool。# db.pyfromsqlalchemy.ext.asyncioimportcreate_async_engine,async_sessionmakerfromconfigimportReadWriteDB,ReportDB rw_cfgReadWriteDB()report_cfgReportDB()rw_enginecreate_async_engine(fpostgresqlasyncpg://{rw_cfg.user}:{rw_cfg.password}f{rw_cfg.host}:{rw_cfg.port}/{rw_cfg.db},pool_sizerw_cfg.pool_max,pool_pre_pingTrue,)report_enginecreate_async_engine(fpostgresqlasyncpg://{report_cfg.user}:{report_cfg.password}f{report_cfg.host}:{report_cfg.port}/{report_cfg.db},pool_sizereport_cfg.pool_max,pool_pre_pingTrue,)RWSessionasync_sessionmaker(rw_engine,expire_on_commitFalse)ReportSessionasync_sessionmaker(report_engine,expire_on_commitFalse)关键点报表查询走 ReportSession业务写走 RWSession两者不共用连接池。为什么要分开因为连接池大小是有限的。如果报表任务和在线业务共用一个池报表跑一个大查询占满连接在线业务就会排队等连接表现出来就是接口超时。分开之后报表最多拖垮自己的池。这里我没有做的是报表池和业务池的隔离并不是绝对的因为底层是同一个数据库实例报表查询重了还是会消耗数据库的 CPU 和 IO。真正的隔离要靠只读副本或者独立的分析库。连接池分开只是第一步。审计日志怎么落到账号维度账号分层之后审计才变得有意义。因为现在日志里能区分谁干的。PostgreSQL 可以开log_statement但生产环境全开日志量太大。更实用的做法是在应用层记录操作来源importlogging loggerlogging.getLogger(db.audit)asyncdefexecute_write(session,stmt,actor:str):logger.info(db_write actor%s accountapp_rw stmt%s,actor,str(stmt)[:200],)returnawaitsession.execute(stmt)这里actor是业务层面的操作人比如用户 IDaccount是数据库账号。两者结合才能回答哪个用户的哪个操作用了哪个账号改了什么。如果只记录数据库账号那你只能知道app_rw 改了这一行但不知道是哪个请求改的。如果只记录用户你不知道用的是哪个权限。两个都要。【注意】日志里不要打印完整的 SQL 参数尤其是包含用户隐私或密钥的字段。我在实际项目里是把参数单独做脱敏只记录表名和操作类型。这一点没有统一标准按合规要求来。分层之后我遇到的几个实际问题第一个迁移脚本用哪个账号。建表、加字段这类操作应该用运维账号而且不应该在应用启动时自动执行。我见过一些项目启动时跑create_all这等于让应用进程有了 DDL 权限分层就白做了。正确做法是迁移由独立的发布流程执行用运维账号应用只负责读写。Alembic 这类工具可以配置但迁移用的连接串和应用的连接串要分开。第二个报表账号读视图视图背后的权限。如果报表查询走视图要注意视图的执行权限。PostgreSQL 里视图默认以视图创建者的权限运行所以即使报表账号对底表没有权限只要对视图有权限也能查到数据。这既是便利也是风险——视图可能绕过你精心设的底表权限。如果希望视图也按调用者权限校验需要用security_invokerPostgreSQL 15 支持我确认过这个参数存在但不同小版本行为我建议自己测一下。第三个只读账号能不能真的只读。某些数据库的只读账号在特定情况下仍会写临时表、写 WAL。这里的只读是逻辑层面的不是物理层面。所以报表账号连只读副本时副本本身是只读的这层保护更实在。这套方案适合什么、不适合什么分层不是没有成本的。账号多了密码轮换、权限变更的运维成本上升。如果团队规模很小可能两套业务 运维就够了报表合到业务里先接受一部分风险。报表和业务分开连接池代码上要显式区分新人容易用错。这个靠 code review 和类型标注来兜。真正的资源隔离CPU、IO不是账号分层能解决的还得靠副本或独立存储。所以我的判断是账号分层解决的是权限边界和审计可追溯不解决资源隔离。这两件事经常被混为一谈。如果你现在只有一个高权限账号我的建议是先拆出运维账号把 DDL 从业务里拿掉这一步收益最大、改动最小。读写和报表的拆分可以放到后面做。关于密码轮换和多环境开发/测试/生产的账号管理涉及密钥管理服务的选型这块我没有在本文展开因为它和账号分层是两个相对独立的问题混在一起讲反而说不清楚。备用标题别再让应用用 root 连数据库了读写、报表、运维账号分层实践数据库账号最小权限怎么落地三层账号划分与 Python 配置隔离从共用一个高权限账号到三层账号一次权限收窄的完整思路应用账号权限分层为什么报表账号不该和业务共用连接池最小权限不是口号读写/报表/运维账号的边界、授权与审计

相关新闻

Altium Designer从原理图到PCB的工程化设计通关指南

Altium Designer从原理图到PCB的工程化设计通关指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/7 8:19:07 阅读更多 →
嵌入式求职全攻略:从技术栈准备到面试谈薪的完整指南

嵌入式求职全攻略:从技术栈准备到面试谈薪的完整指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/7 8:19:07 阅读更多 →
STM32F1为何仍是嵌入式开发的首选入门平台

STM32F1为何仍是嵌入式开发的首选入门平台

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/7 8:19:07 阅读更多 →

最新新闻

iOS开发核心指南:开发者模式、真机调试与系统兼容全攻略

iOS开发核心指南:开发者模式、真机调试与系统兼容全攻略

如果你最近在刷 iOS 开发相关的技术社区,大概率会注意到这样一个现象:各种“章节式更新”的教程、专栏和视频正在批量出现。它们的名字很像,都是为了把 iOS 开发这条又长又深的链路拆成一节一节的课程,降低学习者的上手门槛。而这…

2026/10/7 9:00:45 阅读更多 →
FARO优化器:将神经网络训练重构为收益-风险决策过程

FARO优化器:将神经网络训练重构为收益-风险决策过程

1. 这不是又一个“调参技巧”,而是一套把神经网络训练重新定义为投资决策的建模框架你有没有试过这样想:训练一个神经网络,本质上和基金经理管理一只股票基金,逻辑上其实高度相似?不是在堆算力、不是在调学习率、更不是…

2026/10/7 9:00:44 阅读更多 →
ponytail 插件与 skill 形态解析:聚合编排机制及配置实践指南

ponytail 插件与 skill 形态解析:聚合编排机制及配置实践指南

1. 从“ponytail”这个热词说起:它到底是什么第一次看到“ponytail”被当成一个技术词条来搜,我其实愣了一下。马尾辫?发型?但结合“ponytail skill”“ponytail 插件”“插件 ponytail 如何使用”这几个热搜词一起看,…

2026/10/7 9:00:44 阅读更多 →
RSA破解神器yafu使用全攻略:从大整数分解到CTF实战

RSA破解神器yafu使用全攻略:从大整数分解到CTF实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/7 9:00:44 阅读更多 →
多驱动时钟SDC约束实战:从时钟MUX到STA收敛

多驱动时钟SDC约束实战:从时钟MUX到STA收敛

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/7 9:00:44 阅读更多 →
LiveKit+阿里云语音交互:实时语音对话Agent搭建实战

LiveKit+阿里云语音交互:实时语音对话Agent搭建实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/7 8:59:44 阅读更多 →

日新闻

ROS2机械臂仿真与运动控制:从URDF建模到Gazebo实战全解析

ROS2机械臂仿真与运动控制:从URDF建模到Gazebo实战全解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/7 1:01:58 阅读更多 →
用浏览器直接改ESP32的WiFi密码:NVS键值配置工具设计与实现

用浏览器直接改ESP32的WiFi密码:NVS键值配置工具设计与实现

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/7 1:02:00 阅读更多 →
芯片封装缺陷检测:扫描声学显微镜(SAT)原理与实操指南

芯片封装缺陷检测:扫描声学显微镜(SAT)原理与实操指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/7 1:02:00 阅读更多 →

周新闻

KT148A语音芯片外挂8002D功放的工程实践指南

KT148A语音芯片外挂8002D功放的工程实践指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/6 7:15:40 阅读更多 →
LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/6 5:29:09 阅读更多 →
ARM架构深度解析:从RISC设计理念到交叉编译实战

ARM架构深度解析:从RISC设计理念到交叉编译实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/6 6:26:51 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/6 8:21:32 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/6 4:21:51 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/6 1:18:13 阅读更多 →