4A架构- 授权(权限)模块设计
目录
背景和价值
后端二次接口权限验证
原理: 每个资源(菜单,按钮等)会配置权限标识:System:Menu:Create;后端的Controller的接口上通过注解绑定 权限标识; 然后在拦截器判断用户有没有这个接口的权限(根据账号-资源配置)
二、接口-权限映射方案
方案1:注解式声明(推荐)
@RestController
@RequestMapping("/users")
public class UserController {
@DeleteMapping("/{id}")
@RequiresPermission("user:delete") // 声明接口所需权限
public Result deleteUser(@PathVariable Long id) {
// 业务逻辑
}
}
方案2:配置文件集中管理
# permissions-mapping.yml
mappings:
- api_path: /users/*
methods: [DELETE]
perm_key: user:delete
- api_path: /orders/*
methods: [POST]
perm_key: order:create
方案3:自动扫描注册(动态更新)
@PostConstruct
public void scanApis() {
RequestMappingHandlerMapping mapping = context.getBean(RequestMappingHandlerMapping.class);
mapping.getHandlerMethods().forEach((info, method) -> {
if(method.hasMethodAnnotation(RequiresPermission.class)) {
permissionService.registerApiMapping(
info.getPatternsCondition().getPatterns(),
info.getMethodsCondition().getMethods(),
method.getAnnotation(RequiresPermission.class).value()
);
}
});
}
三、权限校验执行流程
sequenceDiagram
participant C as Client
participant G as Gateway
participant S as Service
participant P as PermissionService
C->>G: 请求 /api/v1/users/123 (DELETE)
G->>S: 携带JWT Token
S->>P: 解析请求路径和方法
P->>P: 匹配权限标识(user:delete)
P->>S: 返回所需权限列表
S->>S: 校验用户是否拥有权限
alt 有权限
S->>S: 执行业务逻辑
S->>C: 返回200 OK
else 无权限
S->>C: 返回403 Forbidden
end
四、关键实现代码示例
- 权限拦截器实现
public class PermissionInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) {
// 1. 提取请求特征
String path = request.getRequestURI();
String method = request.getMethod();
// 2. 获取接口对应权限标识
String permKey = permissionService.getRequiredPerm(path, method);
// 3. 获取用户权限集
Set<String> userPerms = getCurrentUserPerms();
// 4. 权限校验
if(!userPerms.contains(permKey)) {
response.sendError(403, "缺少权限: " + permKey);
return false;
}
return true;
}
}
- 动态权限加载(Redis缓存方案)
@Cacheable(value = "UserPerms", key = "#userId")
public Set<String> loadUserPermissions(Long userId) {
return roleDao.findUserPermissions(userId).stream()
.map(Permission::getPermKey)
.collect(Collectors.toSet());
}
五、生产环境最佳实践
-
权限映射维护策略
- 开发阶段:注解声明为主
- 灰度阶段:自动扫描+人工审核
- 生产阶段:接口权限注册中心管理
-
监控报警配置
# 监控日志关键字报警
grep '403 Forbidden' /var/log/app/access.log |
awk '{print $7}' |
sort | uniq -c |
alert.sh --threshold=50 # 同一接口403超过阈值报警
- 自动化测试用例
@Test
@WithMockUser(authorities = "user:query")
public void testDeleteUserWithoutPermission() throws Exception {
mockMvc.perform(delete("/users/123"))
.andExpect(status().isForbidden());
}
六、解决方案对比
| 方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 注解声明 | 开发直观,代码即文档 | 需代码修改 | 新项目/规范严格团队 |
| 配置文件 | 集中管理,修改灵活 | 维护成本高 | 遗留系统改造 |
| 自动注册 | 动态更新,无需重启 | 需完善监控机制 | 微服务架构 |
通过以上方案可实现:
- 接口权限精准映射:建立API与权限标识的强关联
- 动态校验机制:实时响应权限配置变更
- 防御纵深构建:前端控制+后端校验双重保障
- 可审计性增强:完整权限操作日志记录
建议采用注解声明为主+自动注册为辅的混合方案,在保证开发效率的同时满足动态调整需求。对于历史遗留接口,可通过自动化扫描工具生成初始权限配置。
资源表设计
根据业务概念调整后的资源表设计方案如下,结合图片中的字段展示和权限体系需求:
一、调整后的物理表结构(PostgreSQL)
-- 系统资源主表
CREATE TABLE sys_resource (
id SERIAL PRIMARY KEY, -- 主键自增ID
res_type VARCHAR(10) NOT NULL, -- 资源类型(directory/menu/button/iframe/external)
parent_id INT DEFAULT 0, -- 上级资源ID(0表示根节点)
-- 基础信息
res_name VARCHAR(50) NOT NULL, -- 资源名称(唯一标识)
title VARCHAR(50) NOT NULL, -- 显示标题
route_path VARCHAR(255), -- 路由地址(menu/directory类型必填)
component VARCHAR(255), -- 页面组件路径(menu类型必填)
-- 权限控制
perm_ident VARCHAR(100) UNIQUE, -- 权限标识(button类型必填)
-- 图标配置
icon VARCHAR(50), -- 默认图标
active_icon VARCHAR(50), -- 激活状态图标
-- 外链配置
external_link VARCHAR(255), -- 外部链接地址(external类型必填)
-- 状态控制
is_enabled BOOLEAN DEFAULT TRUE, -- 启用状态(true:已启用)
is_hidden BOOLEAN DEFAULT FALSE, -- 隐藏资源
hide_children BOOLEAN DEFAULT FALSE, -- 隐藏子资源
hide_in_breadcrumb BOOLEAN DEFAULT FALSE,-- 面包屑隐藏
hide_in_tab BOOLEAN DEFAULT FALSE, -- 标签栏隐藏
-- 徽标配置
badge_type VARCHAR(20), -- 徽标类型(dot/count)
badge_content VARCHAR(50), -- 徽标内容
badge_style VARCHAR(100), -- 徽标样式(CSS样式)
-- 系统信息
sort_order INT DEFAULT 0, -- 排序序号
created_at TIMESTAMP DEFAULT NOW(), -- 创建时间
updated_at TIMESTAMP DEFAULT NOW() -- 更新时间
);
COMMENT ON TABLE sys_resource IS '系统资源表(包含菜单/按钮/外链等)';
COMMENT ON COLUMN sys_resource.res_type IS '资源类型: directory/menu/button/iframe/external';
四、索引优化方案
-- 核心查询索引
CREATE INDEX idx_res_type ON sys_resource (res_type);
CREATE INDEX idx_parent_id ON sys_resource (parent_id);
CREATE UNIQUE INDEX idx_res_name ON sys_resource (res_name);
CREATE INDEX idx_perm_ident ON sys_resource (perm_ident);
-- 层级路径索引(ltree扩展)
CREATE EXTENSION IF NOT EXISTS ltree;
ALTER TABLE sys_resource ADD COLUMN path ltree;
CREATE INDEX idx_resource_path ON sys_resource USING GIST (path);
五、各资源类型必填字段对照
| 资源类型 | 必填字段 |
|---|---|
| directory | res_name, title, route_path |
| menu | res_name, title, route_path, component |
| button | res_name, title, perm_ident |
| iframe | res_name, title, component |
| external | res_name, title, external_link |
参考资料
admin管理系统
https://www.vben.pro/#/system/menu

浙公网安备 33010602011771号