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

四、关键实现代码示例

  1. 权限拦截器实现
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;
    }
}
  1. 动态权限加载(Redis缓存方案)
@Cacheable(value = "UserPerms", key = "#userId")
public Set<String> loadUserPermissions(Long userId) {
    return roleDao.findUserPermissions(userId).stream()
                  .map(Permission::getPermKey)
                  .collect(Collectors.toSet());
}

五、生产环境最佳实践

  1. 权限映射维护策略

    • 开发阶段:注解声明为主
    • 灰度阶段:自动扫描+人工审核
    • 生产阶段:接口权限注册中心管理
  2. 监控报警配置

# 监控日志关键字报警
grep '403 Forbidden' /var/log/app/access.log | 
awk '{print $7}' | 
sort | uniq -c | 
alert.sh --threshold=50  # 同一接口403超过阈值报警
  1. 自动化测试用例
@Test
@WithMockUser(authorities = "user:query")
public void testDeleteUserWithoutPermission() throws Exception {
    mockMvc.perform(delete("/users/123"))
           .andExpect(status().isForbidden());
}

六、解决方案对比

方案 优点 缺点 适用场景
注解声明 开发直观,代码即文档 需代码修改 新项目/规范严格团队
配置文件 集中管理,修改灵活 维护成本高 遗留系统改造
自动注册 动态更新,无需重启 需完善监控机制 微服务架构

通过以上方案可实现:

  1. 接口权限精准映射:建立API与权限标识的强关联
  2. 动态校验机制:实时响应权限配置变更
  3. 防御纵深构建:前端控制+后端校验双重保障
  4. 可审计性增强:完整权限操作日志记录

建议采用注解声明为主+自动注册为辅的混合方案,在保证开发效率的同时满足动态调整需求。对于历史遗留接口,可通过自动化扫描工具生成初始权限配置。

资源表设计

根据业务概念调整后的资源表设计方案如下,结合图片中的字段展示和权限体系需求:

一、调整后的物理表结构(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

posted @ 2025-05-11 08:12  向着朝阳  阅读(94)  评论(0)    收藏  举报