SaaS系统开发中的权限控制:从后台设计到业务落地
SaaS系统开发中的权限控制:从后台设计到业务落地 核心结论 最终形态 :一套可嵌入CRM系统的多层级权限体系,覆盖菜单、按钮、数据行级与字段级控制,支持租户隔离与临时授权。 核心价值 :让不同角色在同一个CRM系统中只看到自己该看的数据、只操作自己该操作的功能,保障业务安全与合规。 适用场景 :销售团队分级管理、客户资源隔离、合同审批流、多部门协作的CRM
核心结论
- 最终形态:一套可嵌入CRM系统的多层级权限体系,覆盖菜单、按钮、数据行级与字段级控制,支持租户隔离与临时授权。
- 核心价值:让不同角色在同一个CRM系统中只看到自己该看的数据、只操作自己该操作的功能,保障业务安全与合规。
- 适用场景:销售团队分级管理、客户资源隔离、合同审批流、多部门协作的CRM系统,以及任何需要精细化权限的SaaS产品。
一、这个系统最终可以做成什么样
以CRM系统为例,权限控制最终呈现为三个维度的统一:
- 功能权限:不同角色看到的菜单和按钮不同。例如销售人员只能使用客户管理、跟进记录、我的订单;销售经理额外可见团队数据统计、客户分配;管理员拥有系统设置、用户管理入口。
- 数据权限:同一份客户列表,销售员只能看到自己的客户,销售经理能看到本部门所有客户,总监看到全公司客户。合同金额、成本等敏感字段对部分角色隐藏。
- 操作权限:客户资料编辑、删除、转移归属、导出等操作需要受控。例如销售员只能编辑自己的客户,且无法删除已成交客户;只有经理可以执行客户分配与合同审批。
后台管理界面提供可视化配置:权限规则以“角色”为桥梁,将用户、菜单、API接口、数据范围绑定在一起。整个权限系统作为独立模块,通过中间件和注解注入业务服务,不侵入核心业务代码。利用自动化工具,描述清楚实体、动作与规则后,可以直接生成后端API、管理后台页面和移动端权限校验逻辑,将原本需要两周的开发周期压缩到三天以内。
二、核心功能模块拆解
1. 用户与租户管理
- 职责:维护登录账号、密码、状态、所属租户。支持多租户隔离,每个租户下独立管理用户。
- 核心页面:用户列表、用户详情、租户切换、个人信息修改。
- 数据结构:
users表包含tenant_id外键,与租户表关联;密码使用BCrypt加密存储。 - 业务流程:创建用户时绑定一个或多个角色;登录后生成JWT,token中携带用户ID、租户ID、角色列表。
2. 角色管理
- 职责:定义角色名称、编码、描述,并关联菜单权限、API权限、数据权限规则。
- 核心页面:角色列表、角色权限配置树、角色复制。
- 数据结构:
roles表;角色与菜单多对多关系通过role_menu表维护;角色与API接口多对多关系通过role_api表维护。 - 业务流程:管理员创建“销售员”“销售经理”“管理员”等角色,勾选可见菜单和可调用接口,设置数据范围(如仅本人、本部门、全部)。
3. 菜单与按钮权限
- 职责:控制前端展示哪些导航菜单、页面内哪些按钮可见。
- 核心页面:菜单管理(树形结构)、按钮权限标识绑定。
- 数据结构:
menus表,包含parent_id、name、path、permission标识(如customer:delete)。 - 业务流程:前端路由守卫根据用户角色拉取菜单树动态渲染;按钮通过自定义指令
v-permission判断是否显示。
4. API接口权限
- 职责:后端对每个REST接口进行鉴权,确保无权限用户无法调用。
- 核心页面:API列表(自动扫描或手动注册)、接口绑定角色。
- 数据结构:
apis表,记录method、path、service_name。 - 业务流程:网关或拦截器解析JWT,获取用户角色,匹配请求路径和方法是否在允许列表中。也可采用注解方式
@PreAuthorize("hasRole('sales_manager')")。
5. 数据权限(行级与字段级)
- 职责:根据角色配置,自动过滤查询结果,仅返回授权范围内的数据行;对敏感字段脱敏或隐藏。
- 核心页面:数据规则配置,如“查看本人数据”“查看本部门数据”“自定义SQL条件”。
- 数据结构:
data_rules表,存储规则类型、规则体(如{ "dept_id": "${current_user.dept_id}" })。 - 业务流程:在MyBatis拦截器或ORM层统一注入条件,查询客户列表时自动追加
WHERE created_by = ?或WHERE dept_id IN (?)。字段权限通过序列化时过滤或SQL查询时跳过敏感列实现。
6. 操作日志与审计
- 职责:记录谁在什么时间对什么数据做了什么操作,用于合规审计和异常追踪。
- 核心页面:操作日志列表、详情。
- 数据结构:
audit_logs表,包含user_id、action、target_type、target_id、detail、ip。 - 业务流程:通过AOP切面或事件机制异步记录,不影响主业务性能。
三、数据库设计思路
围绕CRM权限控制,核心数据表设计如下:
| 表名 | 作用 | 关键字段 |
|---|---|---|
| tenants | 租户 | id, name, status |
| users | 用户 | id, tenant_id, username, password_hash, status |
| roles | 角色 | id, tenant_id, role_code, role_name |
| user_role | 用户角色关联 | user_id, role_id |
| menus | 菜单/按钮 | id, parent_id, name, path, permission, type |
| role_menu | 角色菜单关联 | role_id, menu_id |
| apis | API接口 | id, method, path, service_name |
| role_api | 角色API关联 | role_id, api_id |
| data_rules | 数据权限规则 | id, role_id, rule_type, rule_body |
| audit_logs | 操作日志 | id, user_id, action, target_type, target_id, detail, created_at |
关联关系:
- 用户与角色多对多,角色与菜单、API多对多。
- 数据规则与角色一对一或一对多,一个角色可配置多条规则(如“查看本部门”和“查看下属”合并)。
- 所有业务表(如
customers、contracts)都需包含tenant_id和owner_id(创建人)字段,以支持数据行级过滤。
业务逻辑:
查询客户时,系统根据当前用户角色加载对应数据规则,动态拼接SQL条件。例如销售员角色规则为 owner_id = {userId},经理角色规则为 dept_id = {deptId}。字段级权限通过在查询时判断角色是否包含敏感字段查看权限,若无则 SELECT 列表中剔除对应列。
四、API设计方案
权限模块对外暴露的典型接口:
POST /api/auth/login # 用户登录,返回JWT
GET /api/auth/menus # 获取当前用户菜单树
GET /api/auth/permissions # 获取当前用户所有权限标识
POST /api/admin/users # 管理员创建用户
PUT /api/admin/users/{id}/roles # 分配角色
GET /api/admin/roles # 角色列表
POST /api/admin/roles # 创建角色
PUT /api/admin/roles/{id}/menus # 配置角色菜单
PUT /api/admin/roles/{id}/apis # 配置角色API
PUT /api/admin/roles/{id}/data-rule # 配置数据规则
GET /api/customers # 客户列表(自动数据过滤)
接口职责:
- 登录接口验证账号密码,签发JWT,token中包含
userId、tenantId、roles。 - 菜单接口根据用户角色返回树形结构,前端据此渲染导航。
- 权限标识接口返回用户拥有的所有按钮/接口权限字符串,用于前端指令判断。
- 管理接口仅管理员可调用,实现角色、菜单、API、数据规则的CRUD。
- 业务接口(如客户列表)无需手动处理权限,通过统一拦截器或AOP自动注入过滤条件。
请求与返回示例(登录):
// 请求
{
"username": "sales1",
"password": "xxx"
}
// 返回
{
"code": 200,
"data": {
"token": "eyJhbGciOiJIUzI1NiIs...",
"userInfo": {
"id": 101,
"tenantId": 1,
"roles": ["sales"]
}
}
}
五、系统架构设计
整体采用前后端分离、微服务友好的架构:
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ 管理后台Vue3│ │ 移动端/小程序 │ │ 官网/门户 │
└──────┬──────┘ └──────┬──────┘ └──────┬──────┘
│ │ │
└───────────────────┼───────────────────┘
│ HTTPS
┌─────▼─────┐
│ API网关 │ (鉴权、限流、路由)
└─────┬─────┘
│
┌────────────┼────────────┐
│ │ │
┌─────▼─────┐ ┌───▼───┐ ┌─────▼─────┐
│ 用户服务 │ │CRM服务│ │ 权限服务 │
└─────┬─────┘ └───┬───┘ └─────┬─────┘
│ │ │
└────────────┼────────────┘
│
┌─────▼─────┐
│ MySQL │
│ Redis │
└───────────┘
- 前端:Vue3 + Element Plus,通过路由守卫和权限指令控制界面。
- API网关:Spring Cloud Gateway 或 Nginx,统一校验JWT,解析角色并传递给下游。
- 权限服务:独立微服务,维护角色、菜单、API、数据规则,提供鉴权接口。业务服务启动时向权限服务注册API列表。
- 业务服务:CRM核心服务,通过拦截器从网关传递的上下文中获取用户角色,调用权限服务获取数据规则,并应用到数据库查询。
- 数据层:MySQL存储业务数据和权限配置;Redis缓存用户权限信息、菜单树,减少数据库压力。
数据流向:
用户请求 → 网关验证JWT → 将用户信息写入请求头 → 业务服务拦截器解析 → 查询Redis权限缓存(未命中则查库) → 执行数据权限过滤 → 返回结果。
六、开发路线规划
MVP版本(1-2周)
- 用户登录、JWT签发
- 角色管理(CRUD)
- 菜单管理、根据角色动态渲染前端菜单
- 后端接口注解鉴权(固定角色)
- 客户列表手动按创建人过滤
商业版(3-4周)
- 完善数据权限:基于规则引擎的自动行级过滤
- 字段级权限控制
- 操作日志记录
- 管理后台可视化配置角色权限
- 支持多租户隔离
企业版(6-8周)
- 临时授权与权限时效
- 审批流中的动态权限(如审批人临时可见某些数据)
- 数据脱敏与审计报表
- 权限变更实时生效(消息队列通知刷新缓存)
- 对接外部LDAP/AD统一身份认证
利用自动化开发工具,可以通过描述实体和动作快速生成基础CRUD接口和管理页面,将权限模板注入生成代码,大幅缩短样板代码编写时间。例如定义“客户”实体和“分配”动作,编译器自动派生对应的控制器、权限校验、归属检查逻辑,开发者只需关注特殊业务规则。
七、关键技术难点
-
数据权限性能
复杂的SQL条件拼接可能导致索引失效。解决方案:尽量使用简单的等值条件,避免子查询;对高频查询字段建立索引;使用缓存存储用户数据范围,避免每次请求都查库。 -
权限实时生效
修改角色权限后,已登录用户的权限缓存需要同步更新。采用Redis发布订阅或消息队列广播权限变更事件,各服务节点收到后清除本地缓存。 -
多租户数据隔离
所有查询必须带上tenant_id,防止跨租户数据泄露。在ORM层统一拦截,强制注入租户条件,避免开发人员遗漏。 -
字段级权限与序列化
返回JSON时动态过滤字段会增加序列化复杂度。可通过自定义Jackson注解或AOP在序列化前根据角色移除敏感字段,或在SQL查询时按角色动态SELECT列。 -
前端权限与后端一致性
前端隐藏按钮只是提升体验,不能作为安全控制。所有操作必须在后端再次校验权限,防止通过API直接调用。
八、FAQ
Q1:CRM系统中,销售员离职后他的客户如何自动转交给其他人?
在用户管理模块设置“离职”状态,触发客户重新分配流程。系统根据管理员预设的分配规则(如按部门、按负载)自动将客户 owner_id 更新为新销售员,并记录操作日志。
Q2:如何实现“销售员只能查看自己客户,但经理可以查看部门所有客户”?
在数据规则中为销售员角色配置 owner_id = {currentUserId},为经理角色配置 dept_id = {currentUserDeptId}。查询客户时,拦截器根据角色加载对应规则,动态拼接WHERE条件。
Q3:权限系统会不会拖慢业务接口响应速度?
合理使用缓存后影响极小。用户权限信息在登录后缓存至Redis,数据权限规则也提前加载。一次业务请求通常只增加1-2次Redis查询和一次简单的SQL条件拼接,延迟在毫秒级。
九、总结
SaaS系统的权限控制绝非简单的“加个角色表”,而是一个贯穿前端、网关、业务服务、数据层的完整体系。以CRM系统为例,从菜单按钮到API再到行级数据,每一层都需要精细设计。借助现代开发工具和自动化代码生成,我们可以将重复的权限样板代码交由编译器处理,把精力集中在业务规则和架构优化上。最终交付的是一套安全、灵活、可扩展的权限基础设施,支撑CRM系统从初创团队到大型企业的平滑演进。