CC 咖啡猫的工作空间 Coding Space

权限设计最佳实践

权限系统是企业应用的核心安全组件,合理的权限设计既能保障系统安全,又能提供良好的用户体验。


1. 权限设计基础概念

1.1 权限的定义

权限(Permission):用户或角色对系统资源进行特定操作的许可。

1.2 核心要素

  • 用户(User):系统的使用者
  • 角色(Role):权限的集合,代表用户的职责
  • 资源(Resource):被保护的对象(菜单、按钮、API、数据等)
  • 操作(Action):对资源可执行的动作(查看、编辑、删除等)

1.3 权限粒度

粒度级别 说明 示例
功能级 控制菜单、页面、按钮的可见性 菜单权限、按钮权限
数据级 控制用户能看到的数据范围 只能看自己部门的数据
字段级 控制具体字段的读写权限 敏感字段只读或隐藏
行级 控制具体记录的访问权限 只能操作自己的订单

2. 主流权限模型对比

2.1 ACL(访问控制列表)

原理:直接在资源上维护用户权限列表

// 资源: 订单管理页面
{
    "resource": "order-management",
    "permissions": [
        {"user": "user1", "actions": ["view", "edit"]},
        {"user": "user2", "actions": ["view"]}
    ]
}

优缺点

  • ✅ 简单直观,适合小规模系统
  • ❌ 用户多时维护困难,权限变更成本高

2.2 RBAC(基于角色的访问控制)- 推荐

原理:用户 → 角色 → 权限,通过角色作为中间层

// 用户-角色关系
user_roles = {
    "admin": ["超级管理员"],
    "manager": ["部门经理", "普通员工"]
}

// 角色-权限关系  
role_permissions = {
    "超级管理员": ["user:*", "order:*", "product:*"],
    "部门经理": ["order:view", "order:edit", "product:view"],
    "普通员工": ["order:view"]
}

RBAC模型变种

  • RBAC0:基础模型(用户-角色-权限)
  • RBAC1:支持角色继承(子角色继承父角色权限)
  • RBAC2:支持约束(互斥角色、角色数量限制等)
  • RBAC3:RBAC1 + RBAC2 的组合

2.3 ABAC(基于属性的访问控制)

原理:根据用户属性、资源属性、环境属性动态判断权限

// 策略示例:工作时间才能访问敏感数据
if (user.department == "finance" && 
    resource.sensitivity == "high" && 
    currentTime.hour >= 9 && currentTime.hour <= 18) {
    grantAccess();
}

优缺点

  • ✅ 灵活性极高,适合复杂业务场景
  • ❌ 实现复杂,性能开销大

2.4 模型选择建议

场景 推荐模型 理由
中小型企业应用 RBAC 简单易用,维护成本低
大型企业/政府系统 RBAC + 数据权限 平衡灵活性和复杂度
云原生/微服务 ABAC 动态策略,适应性强
简单单体应用 ACL 快速实现,无需复杂设计

3. RBAC详细实现方案

3.1 数据库设计

基础表结构

-- 用户表
CREATE TABLE sys_user (
    id BIGINT PRIMARY KEY,
    username VARCHAR(50) UNIQUE,
    password VARCHAR(100),
    status TINYINT DEFAULT 1
);

-- 角色表  
CREATE TABLE sys_role (
    id BIGINT PRIMARY KEY,
    role_name VARCHAR(50) UNIQUE,
    role_code VARCHAR(50) UNIQUE,
    description VARCHAR(200)
);

-- 权限表
CREATE TABLE sys_permission (
    id BIGINT PRIMARY KEY,
    permission_name VARCHAR(100),
    permission_code VARCHAR(100) UNIQUE, -- user:create, order:delete
    resource_type VARCHAR(20), -- menu/button/api
    url VARCHAR(200), -- API路径或前端路由
    parent_id BIGINT, -- 菜单层级
    sort_order INT DEFAULT 0
);

-- 用户-角色关联表
CREATE TABLE sys_user_role (
    user_id BIGINT,
    role_id BIGINT,
    PRIMARY KEY (user_id, role_id)
);

-- 角色-权限关联表  
CREATE TABLE sys_role_permission (
    role_id BIGINT,
    permission_id BIGINT,
    PRIMARY KEY (role_id, permission_id)
);

权限编码规范

格式:{resource}:{action}
示例:
- user:create     # 创建用户
- user:update     # 更新用户  
- user:delete     # 删除用户
- user:view       # 查看用户
- order:*         # 订单所有操作(通配符)

3.2 后端实现

Spring Security + RBAC实现

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Autowired
    private CustomUserDetailsService userDetailsService;

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(authz -> authz
                .requestMatchers("/api/admin/**").hasAuthority("admin:user:*")
                .requestMatchers("/api/order/**").hasAnyAuthority("order:view", "order:*")
                .anyRequest().authenticated()
            )
            .userDetailsService(userDetailsService);
        return http.build();
    }
}

// 自定义用户详情服务
@Service
public class CustomUserDetailsService implements UserDetailsService {

    @Autowired
    private UserService userService;
    
    @Override
    public UserDetails loadUserByUsername(String username) {
        // 1. 查询用户基本信息
        User user = userService.findByUsername(username);
        
        // 2. 查询用户所有权限
        List<String> permissions = userService.getUserPermissions(username);
        
        // 3. 构造Spring Security用户对象
        return User.builder()
            .username(user.getUsername())
            .password(user.getPassword())
            .authorities(permissions.stream()
                .map(SimpleGrantedAuthority::new)
                .collect(Collectors.toList()))
            .build();
    }
}

权限验证工具类

@Component
public class PermissionChecker {

    /**
     * 检查用户是否有指定权限
     */
    public boolean hasPermission(String permissionCode) {
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        if (auth == null || !auth.isAuthenticated()) {
            return false;
        }
        
        Collection<? extends GrantedAuthority> authorities = auth.getAuthorities();
        return authorities.stream()
            .anyMatch(granted -> granted.getAuthority().equals(permissionCode) || 
                               granted.getAuthority().equals("*") ||
                               (permissionCode.contains(":") && 
                                granted.getAuthority().equals(permissionCode.split(":")[0] + ":*")));
    }
    
    /**
     * 检查用户是否有任一权限
     */
    public boolean hasAnyPermission(String... permissionCodes) {
        return Arrays.stream(permissionCodes).anyMatch(this::hasPermission);
    }
}

3.3 前端实现

路由权限控制

// Vue Router 权限路由
const routes = [
  {
    path: '/user',
    component: UserLayout,
    meta: { permission: 'user:view' },
    children: [
      {
        path: 'list',
        component: UserList,
        meta: { permission: 'user:view' }
      },
      {
        path: 'create',
        component: UserCreate,
        meta: { permission: 'user:create' }
      }
    ]
  }
];

// 路由守卫
router.beforeEach((to, from, next) => {
  if (to.meta.permission) {
    if (hasPermission(to.meta.permission)) {
      next();
    } else {
      next('/403');
    }
  } else {
    next();
  }
});

组件级权限控制

<!-- 按钮权限指令 -->
<template>
  <div>
    <!-- 方式1: v-if 控制显示 -->
    <button v-if="hasPermission('user:create')" @click="handleCreate">
      新增用户
    </button>
    
    <!-- 方式2: 自定义指令 -->
    <button v-permission="'user:delete'" @click="handleDelete">
      删除用户
    </button>
  </div>
</template>

<script>
// 权限指令
export const permission = {
  mounted(el, binding) {
    const { value } = binding;
    if (value && !hasPermission(value)) {
      el.parentNode && el.parentNode.removeChild(el);
    }
  }
};
</script>

4. 数据权限设计

4.1 数据权限类型

类型 说明 示例
全部数据 可查看系统所有数据 超级管理员
本部门及子部门 可查看本部门和下级部门数据 部门经理
本部门 只能查看本部门数据 部门员工
本人数据 只能查看自己的数据 普通用户

4.2 数据权限实现

注解驱动的数据权限

// 数据权限注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DataScope {
    String deptAlias() default "dept"; // 部门表别名
    String userAlias() default "user"; // 用户表别名
}

// 使用示例
@Service
public class OrderService {
    
    @DataScope(deptAlias = "o.dept_id", userAlias = "o.user_id")
    public List<Order> getOrderList(OrderQuery query) {
        return orderMapper.selectList(query);
    }
}

// MyBatis拦截器实现
@Component
@Intercepts(@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class}))
public class DataScopeInterceptor implements Interceptor {
    
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
        MetaObject metaObject = SystemMetaObject.forObject(statementHandler);
        
        // 获取方法上的@DataScope注解
        DataScope dataScope = getDataScopeAnnotation(metaObject);
        if (dataScope != null) {
            // 构建数据权限SQL片段
            String scopeSql = buildDataScopeSql(dataScope);
            // 修改原始SQL,添加WHERE条件
            modifySql(metaObject, scopeSql);
        }
        return invocation.proceed();
    }
}

SQL层面的数据权限

-- 全部数据权限
SELECT * FROM orders o

-- 本部门及子部门权限  
SELECT * FROM orders o 
WHERE o.dept_id IN (
    SELECT id FROM departments 
    WHERE id = #{currentDeptId} 
       OR parent_id = #{currentDeptId}
)

-- 本人数据权限
SELECT * FROM orders o 
WHERE o.user_id = #{currentUserId}

5. 权限系统最佳实践

5.1 权限设计原则

  1. 最小权限原则:用户只拥有完成工作所需的最小权限
  2. 职责分离原则:关键操作需要多人协作完成
  3. 权限可审计:所有权限变更都要有日志记录
  4. 权限可回收:用户离职或调岗时及时回收权限

5.2 权限管理后台

┌─────────────────────────────────────┐
│           权限管理后台               │
├─────────────────────────────────────┤
│ 用户管理     角色管理     权限管理   │
│                                     │
│ 用户列表 → 分配角色                 │
│ 角色列表 → 配置权限                 │  
│ 权限列表 → 定义资源和操作           │
│                                     │
│ [搜索] [批量操作] [导入/导出]        │
└─────────────────────────────────────┘

5.3 权限缓存策略

@Service
public class PermissionService {
    
    // 权限缓存:用户ID → 权限列表
    private final Cache<String, Set<String>> permissionCache = 
        Caffeine.newBuilder()
            .maximumSize(1000)
            .expireAfterWrite(30, TimeUnit.MINUTES)
            .build();
    
    public Set<String> getUserPermissions(String userId) {
        return permissionCache.get(userId, this::loadPermissionsFromDb);
    }
    
    // 权限变更时清除缓存
    public void updateUserPermissions(String userId) {
        permissionCache.invalidate(userId);
        // 重新加载权限...
    }
}

5.4 权限变更通知

// 权限变更事件
@Component
public class PermissionChangeListener {
    
    @EventListener
    public void handlePermissionChange(PermissionChangeEvent event) {
        // 1. 清除相关用户权限缓存
        cacheService.clearUserPermissionCache(event.getAffectedUsers());
        
        // 2. 发送WebSocket通知(前端实时更新权限)
        webSocketService.notifyPermissionUpdate(event.getAffectedUsers());
        
        // 3. 记录审计日志
        auditLogService.logPermissionChange(event);
    }
}

6. 常见问题与解决方案

6.1 性能问题

问题:权限检查影响接口性能
解决方案

  • 权限结果缓存(Redis/Caffeine)
  • 批量权限预加载
  • 异步权限验证

6.2 权限爆炸

问题:权限项过多,管理困难
解决方案

  • 权限分组(按模块、按功能)
  • 使用通配符权限(如 order:*
  • 建立权限模板

6.3 权限同步

问题:分布式系统中权限不一致
解决方案

  • 权限中心化管理
  • 权限变更广播机制
  • 定期权限同步任务

6.4 权限测试

问题:权限逻辑复杂,测试覆盖困难
解决方案

  • 权限矩阵测试用例
  • 自动化权限测试脚本
  • 权限边界条件测试

7. 微服务架构下的权限设计

7.1 集中式权限服务

┌─────────────┐    ┌─────────────┐    ┌─────────────┐
│  用户服务    │    │  订单服务    │    │  商品服务    │
└──────┬──────┘    └──────┬──────┘    └──────┬──────┘
       │                  │                  │
       └──────────────────┼──────────────────┘
                          │
                  ┌───────▼───────┐
                  │  权限服务      │
                  │ (统一权限中心)  │
                  └───────────────┘

7.2 JWT令牌携带权限

{
  "sub": "user123",
  "roles": ["admin", "user_manager"],
  "permissions": ["user:*", "order:view", "product:view"],
  "dataScope": "DEPT_AND_CHILD",
  "exp": 1715632845
}

7.3 网关层权限验证

// API Gateway权限验证
@Component
public class PermissionGatewayFilter implements GlobalFilter {
    
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        String token = extractToken(request);
        
        if (token != null) {
            JwtClaims claims = parseToken(token);
            List<String> permissions = claims.getPermissions();
            
            // 验证当前请求是否在权限范围内
            if (!hasPermissionForPath(request.getPath().toString(), permissions)) {
                return unauthorizedResponse(exchange);
            }
        }
        return chain.filter(exchange);
    }
}

8. 总结

8.1 权限设计要点

  • 从小到大:先实现基础RBAC,再逐步扩展
  • 前后端配合:后端做强验证,前端做体验优化
  • 性能优先:合理使用缓存,避免频繁数据库查询
  • 安全第一:权限验证必须在服务端实现,前端只是体验优化

8.2 技术选型建议

  • 单体应用:Spring Security + RBAC
  • 微服务架构:OAuth2 + JWT + 权限中心
  • 快速原型:Shiro框架(简单易用)
  • 复杂场景:自研权限引擎 + ABAC策略

8.3 实施步骤

  1. 需求分析:明确权限粒度和业务场景
  2. 模型选择:根据复杂度选择合适的权限模型
  3. 数据库设计:设计合理的权限数据结构
  4. 核心实现:实现权限验证和管理功能
  5. 性能优化:添加缓存和异步处理
  6. 测试验证:全面测试各种权限场景

记住:权限系统不是越复杂越好,而是要恰到好处地满足业务需求和安全要求。