Spring Boot & Node.js 接口权限校验实战指南

从基础拦截到高级策略,构建企业级安全API

📋 目录

🚨 前言:为什么需要权限控制?

接口权限校验是后端开发的安全生命线。没有完善的权限控制,系统将面临:

  • 越权访问:普通用户访问管理员功能
  • 数据泄露:用户查看不属于自己的敏感数据
  • 恶意操作:攻击者破坏系统功能

本文将带你从Spring Boot拦截器RBAC/ABAC模型,循序渐进构建企业级安全API。


一、Spring Boot 拦截器权限控制(基础篇)

🔧 核心原理

Spring Boot拦截器通过HandlerInterceptor接口,在请求处理链中插入权限检查点,实现统一的权限验证。

💻 Spring Boot 权限拦截器完整实现

这个类做什么用?

  • 作为所有API请求的"安检员"
  • 在业务逻辑执行前统一检查用户token和权限
  • 适用场景:中小型项目、权限规则相对简单的应用
/**
 * Spring Boot 权限拦截器
 * 功能:统一拦截所有API请求,进行token验证和权限检查
 * 使用场景:适用于权限规则固定的中小型应用
 * 优点:实现简单、集中管理权限逻辑、对业务代码无侵入
 * 配置:需要在WebConfig中注册并设置拦截规则
 */
@Component
public class AuthInterceptor implements HandlerInterceptor {
    
    @Autowired
    private UserService userService;
    
    @Override
    public boolean preHandle(HttpServletRequest request, 
                           HttpServletResponse response, 
                           Object handler) throws Exception {
        
        // 1. 从请求头获取Authorization token
        String token = request.getHeader("Authorization");
        if (StringUtils.isEmpty(token)) {
            response.setStatus(401);
            response.getWriter().write("未授权访问,请提供有效token");
            return false;
        }
        
        // 2. 验证token是否有效并获取用户信息
        User user = userService.validateToken(token);
        if (user == null) {
            response.setStatus(401);
            response.getWriter().write("token无效或已过期");
            return false;
        }
        
        // 3. 根据请求路径检查用户权限
        String requestURI = request.getRequestURI();
        if (!hasPermission(user, requestURI)) {
            response.setStatus(403);
            response.getWriter().write("权限不足,无法访问此接口");
            return false;
        }
        
        // 4. 权限验证通过,将用户信息存入请求上下文供后续使用
        request.setAttribute("currentUser", user);
        return true;
    }
    
    /**
     * 检查用户是否有访问指定接口的权限
     * 实现方式:硬编码的权限映射表
     * 缺点:不够灵活,修改权限需要重新部署
     * 优化方向:改为从数据库或配置文件读取
     */
    private boolean hasPermission(User user, String uri) {
        // 权限映射表:接口路径 -> 允许访问的角色列表
        Map<String, List<String>> permissionMap = new HashMap<>();
        permissionMap.put("/api/user/profile", Arrays.asList("USER", "ADMIN"));
        permissionMap.put("/api/admin/dashboard", Arrays.asList("ADMIN"));
        permissionMap.put("/api/order/list", Arrays.asList("USER", "ADMIN"));
        permissionMap.put("/api/order/create", Arrays.asList("USER", "ADMIN"));
        
        List<String> allowedRoles = permissionMap.get(uri);
        if (allowedRoles == null) {
            // 白名单原则:未配置的接口默认拒绝访问
            return false;
        }
        
        return allowedRoles.contains(user.getRole());
    }
}

⚙️ 拦截器配置与注册

这个配置类做什么用?

  • 将权限拦截器注册到Spring MVC框架中
  • 配置拦截规则:哪些URL需要权限验证,哪些可以放行
  • 相当于给拦截器设置"工作范围"和"免检通道"
/**
 * Spring MVC 配置类
 * 功能:注册权限拦截器并配置拦截规则
 * 作用:告诉Spring哪些请求需要经过权限检查
 * 拦截范围:所有 /api/** 路径的请求
 * 放行范围:登录、注册等公开接口
 * 使用场景:Spring Boot应用的安全配置入口
 */
@Configuration
public class WebConfig implements WebMvcConfigurer {
    
    @Autowired
    private AuthInterceptor authInterceptor;
    
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(authInterceptor)
                .addPathPatterns("/api/**")                    // 拦截所有API请求
                .excludePathPatterns(
                    "/api/auth/login",                          // 放行登录接口
                    "/api/auth/register",                       // 放行注册接口  
                    "/api/public/**",                           // 放行公共资源
                    "/swagger-ui/**",                           // 放行API文档
                    "/v3/api-docs/**",                          // 放行Swagger文档
                    "/error"                                     // 放行错误页面
                );
    }
}

🟢 Node.js Express 权限中间件对比

这个中间件做什么用?

  • Node.js版本的权限控制实现
  • 使用JWT token进行用户认证
  • 展示不同技术栈的权限控制思路
  • 适用场景:Node.js + Express后端项目
/**
 * Express.js 权限中间件
 * 功能:验证JWT token并检查用户访问权限
 * 使用方式:在需要权限的路由前加上 authMiddleware
 * 示例:app.get('/api/users', authMiddleware, userController.list)
 * 优势:集成简单、与Express生态无缝结合
 * 对比:与Spring Boot拦截器功能相似,实现方式不同
 */
const jwt = require('jsonwebtoken');
const User = require('../models/User');

const authMiddleware = async (req, res, next) => {
    try {
        // 1. 从Authorization头提取token
        const token = req.header('Authorization')?.replace('Bearer ', '');
        
        if (!token) {
            return res.status(401).json({ 
                success: false,
                message: '访问被拒绝,请在请求头中提供有效token' 
            });
        }
        
        // 2. 使用JWT密钥验证token并解码
        const decoded = jwt.verify(token, process.env.JWT_SECRET);
        const user = await User.findById(decoded.userId);
        
        if (!user) {
            return res.status(401).json({ 
                success: false,
                message: 'token对应的用户不存在' 
            });
        }
        
        // 3. 根据请求路径获取所需角色权限
        const requiredRole = getRequiredRole(req.originalUrl);
        if (!checkRole(user.role, requiredRole)) {
            return res.status(403).json({ 
                success: false,
                message: `权限不足,此接口需要角色: ${requiredRole?.join(', ') || '未知'}` 
            });
        }
        
        // 4. 将用户信息添加到请求对象,供后续中间件和路由使用
        req.user = user;
        next();
        
    } catch (error) {
        // JWT验证失败(token过期、伪造等)
        res.status(401).json({ 
            success: false,
            message: 'token无效或已过期,请重新登录' 
        });
    }
};

/**
 * 获取指定URL所需的角色权限
 * 返回:所需角色数组或null(无权限要求)
 * 扩展性:可以改从数据库或配置文件读取
 * 匹配规则:支持路径前缀匹配
 */
function getRequiredRole(url) {
    const roleMap = {
        '/api/user/profile': ['user', 'admin'],
        '/api/admin/dashboard': ['admin'],
        '/api/order/create': ['user', 'admin'],
        '/api/order/list': ['user', 'admin'],
        '/api/admin/users': ['admin']
    };
    
    // 支持路径前缀匹配
    for (const [path, roles] of Object.entries(roleMap)) {
        if (url.startsWith(path)) {
            return roles;
        }
    }
    
    return null; // 默认无权限要求
}

/**
 * 检查用户角色是否符合要求
 * userRole: 当前用户角色
 * requiredRoles: 所需角色列表
 * 返回:true表示有权限,false表示无权限
 */
function checkRole(userRole, requiredRoles) {
    if (!requiredRoles) return true; // 无权限要求
    return requiredRoles.includes(userRole);
}

module.exports = authMiddleware;

✅ 拦截器模式优缺点分析

优点:

  • 🎯 实现简单:代码逻辑清晰,容易理解和维护
  • 🔧 集中管理:所有权限逻辑统一在拦截器中
  • 📦 无侵入性:业务代码无需修改权限逻辑
  • 性能较好:单次检查,无复杂查询

缺点:

  • 📝 硬编码配置:权限规则写死在代码中,修改需要重新部署
  • 🔄 扩展性差:难以支持动态权限和复杂场景
  • 🚫 功能有限:无法支持数据级权限控制
  • 🛠️ 维护困难:权限分散在代码中,难以统一管理

二、RBAC角色权限模型(进阶篇)

🔧 RBAC核心原理

RBAC(Role-Based Access Control)通过用户-角色-权限三层映射关系实现灵活的权限控制,是企业级应用的主流方案。

🔧 实现原理

RBAC(Role-Based Access Control)基于角色的访问控制,通过给用户分配角色,给角色分配权限来实现权限管理。

🏗️ RBAC数据库设计方案

这套表结构解决什么问题?

  • 实现灵活的权限分配:用户可以拥有多个角色,角色可以拥有多个权限
  • 支持权限的动态调整:无需修改代码即可调整权限
  • 提供清晰的权限追溯:可以追踪每个用户的权限来源
  • 适用场景:中大型企业级应用、需要细粒度权限控制的系统
-- ========================================
-- 用户基础信息表
-- 存储系统用户的基本信息
-- ========================================
CREATE TABLE `users` (
    `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '用户ID,主键',
    `username` varchar(50) NOT NULL COMMENT '用户名,唯一',
    `password` varchar(100) NOT NULL COMMENT '密码(加密存储)',
    `email` varchar(100) NOT NULL COMMENT '邮箱地址',
    `phone` varchar(20) DEFAULT NULL COMMENT '手机号码',
    `status` tinyint(1) DEFAULT 1 COMMENT '状态:1-正常,0-禁用',
    `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
    PRIMARY KEY (`id`),
    UNIQUE KEY `username` (`username`),
    UNIQUE KEY `email` (`email`),
    KEY `idx_status` (`status`)
) COMMENT='用户表';

-- ========================================
-- 角色定义表
-- 定义系统中的各种角色
-- ========================================
CREATE TABLE `roles` (
    `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '角色ID,主键',
    `role_name` varchar(50) NOT NULL COMMENT '角色名称(显示用)',
    `role_code` varchar(50) NOT NULL COMMENT '角色编码(程序用)',
    `description` varchar(200) DEFAULT NULL COMMENT '角色描述',
    `sort_order` int(11) DEFAULT 0 COMMENT '排序顺序',
    `status` tinyint(1) DEFAULT 1 COMMENT '状态:1-启用,0-禁用',
    `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    PRIMARY KEY (`id`),
    UNIQUE KEY `role_code` (`role_code`),
    KEY `idx_status` (`status`)
) COMMENT='角色表';

-- ========================================
-- 权限定义表
-- 定义系统中的各种权限
-- ========================================
CREATE TABLE `permissions` (
    `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '权限ID,主键',
    `perm_name` varchar(100) NOT NULL COMMENT '权限名称(显示用)',
    `perm_code` varchar(100) NOT NULL COMMENT '权限编码(程序用)',
    `perm_type` varchar(20) NOT NULL COMMENT '权限类型:MENU-菜单,BUTTON-按钮,API-接口',
    `resource_path` varchar(200) DEFAULT NULL COMMENT '资源路径(接口URL或菜单路径)',
    `parent_id` bigint(20) DEFAULT 0 COMMENT '父权限ID(用于权限层级)',
    `description` varchar(200) DEFAULT NULL COMMENT '权限描述',
    `sort_order` int(11) DEFAULT 0 COMMENT '排序顺序',
    `status` tinyint(1) DEFAULT 1 COMMENT '状态:1-启用,0-禁用',
    `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    PRIMARY KEY (`id`),
    UNIQUE KEY `perm_code` (`perm_code`),
    KEY `idx_type` (`perm_type`),
    KEY `idx_parent` (`parent_id`),
    KEY `idx_status` (`status`)
) COMMENT='权限表';

-- ========================================
-- 用户角色关联表
-- 实现用户与角色的多对多关系
-- ========================================
CREATE TABLE `user_roles` (
    `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '关联ID',
    `user_id` bigint(20) NOT NULL COMMENT '用户ID',
    `role_id` bigint(20) NOT NULL COMMENT '角色ID',
    `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '关联时间',
    `created_by` bigint(20) DEFAULT NULL COMMENT '创建人ID',
    PRIMARY KEY (`id`),
    UNIQUE KEY `user_role` (`user_id`, `role_id`),
    KEY `idx_user` (`user_id`),
    KEY `idx_role` (`role_id`),
    CONSTRAINT `fk_user_roles_user` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE,
    CONSTRAINT `fk_user_roles_role` FOREIGN KEY (`role_id`) REFERENCES `roles` (`id`) ON DELETE CASCADE
) COMMENT='用户角色关联表';

-- ========================================
-- 角色权限关联表
-- 实现角色与权限的多对多关系
-- ========================================
CREATE TABLE `role_permissions` (
    `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '关联ID',
    `role_id` bigint(20) NOT NULL COMMENT '角色ID',
    `perm_id` bigint(20) NOT NULL COMMENT '权限ID',
    `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '关联时间',
    `created_by` bigint(20) DEFAULT NULL COMMENT '创建人ID',
    PRIMARY KEY (`id`),
    UNIQUE KEY `role_perm` (`role_id`, `perm_id`),
    KEY `idx_role` (`role_id`),
    KEY `idx_perm` (`perm_id`),
    CONSTRAINT `fk_role_perms_role` FOREIGN KEY (`role_id`) REFERENCES `roles` (`id`) ON DELETE CASCADE,
    CONSTRAINT `fk_role_perms_perm` FOREIGN KEY (`perm_id`) REFERENCES `permissions` (`id`) ON DELETE CASCADE
) COMMENT='角色权限关联表';

💻 代码示例

💻 RBAC权限服务核心实现

这个服务类做什么用?

  • 作为RBAC权限模型的核心业务逻辑层
  • 实现用户权限的查询、验证和缓存
  • 支持多种权限检查方式:编码检查、接口检查
  • 适用场景:需要动态权限管理的企业级应用
/**
 * RBAC权限服务
 * 功能:提供完整的权限查询和验证逻辑
 * 职责:处理用户-角色-权限三层关系,实现细粒度权限控制
 * 优化:支持权限缓存,提高频繁权限检查的性能
 * 使用方式:通过AOP切面或直接调用进行权限验证
 */
@Service
public class PermissionService {
    
    @Autowired
    private UserMapper userMapper;
    @Autowired
    private RoleMapper roleMapper;
    @Autowired
    private PermissionMapper permissionMapper;
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    private static final String USER_PERMISSIONS_CACHE = "user:permissions:";
    private static final long CACHE_EXPIRE_MINUTES = 30;
    
    /**
     * 获取用户的所有权限
     * 流程:用户ID -> 角色列表 -> 权限列表 -> 权限编码集合
     * 优化:添加Redis缓存,避免频繁数据库查询
     * 返回:权限编码集合,如["user:view", "order:create", "admin:dashboard"]
     */
    @Cacheable(value = "userPermissions", key = "#userId", unless = "#result == null")
    public Set<String> getUserPermissions(Long userId) {
        String cacheKey = USER_PERMISSIONS_CACHE + userId;
        
        // 尝试从缓存获取
        Set<String> cachedPermissions = (Set<String>) redisTemplate.opsForValue().get(cacheKey);
        if (cachedPermissions != null) {
            return cachedPermissions;
        }
        
        // 1. 获取用户的所有角色
        List<Role> roles = userMapper.getUserRoles(userId);
        if (roles.isEmpty()) {
            return Collections.emptySet();
        }
        
        // 2. 获取角色对应的所有权限
        Set<String> permissions = new HashSet<>();
        List<Long> roleIds = roles.stream().map(Role::getId).collect(Collectors.toList());
        
        // 批量查询角色权限,优化数据库性能
        List<Permission> allPerms = roleMapper.getPermissionsByRoleIds(roleIds);
        for (Permission perm : allPerms) {
            if (perm.getStatus() == 1) { // 只启用有效权限
                permissions.add(perm.getPermCode());
            }
        }
        
        // 3. 缓存权限集合
        redisTemplate.opsForValue().set(cacheKey, permissions, 
                CACHE_EXPIRE_MINUTES, TimeUnit.MINUTES);
        
        return permissions;
    }
    
    /**
     * 检查用户是否有指定权限
     * 用途:在业务逻辑中进行细粒度权限控制
     * 参数:userId - 用户ID,permissionCode - 权限编码(如"user:edit")
     * 返回:true表示有权限,false表示无权限
     */
    public boolean hasPermission(Long userId, String permissionCode) {
        if (StringUtils.isEmpty(permissionCode)) {
            return false;
        }
        
        Set<String> userPermissions = getUserPermissions(userId);
        return userPermissions.contains(permissionCode);
    }
    
    /**
     * 检查用户是否有多个权限(AND关系)
     * 用途:需要同时满足多个权限的场景
     */
    public boolean hasAllPermissions(Long userId, String... permissionCodes) {
        Set<String> userPermissions = getUserPermissions(userId);
        return Arrays.stream(permissionCodes)
                .allMatch(userPermissions::contains);
    }
    
    /**
     * 检查用户是否有其中一个权限(OR关系)
     * 用途:满足任一权限即可的场景
     */
    public boolean hasAnyPermission(Long userId, String... permissionCodes) {
        Set<String> userPermissions = getUserPermissions(userId);
        return Arrays.stream(permissionCodes)
                .anyMatch(userPermissions::contains);
    }
    
    /**
     * 检查用户是否有访问指定接口的权限
     * 流程:接口路径 -> 查找对应权限 -> 检查用户权限
     * 参数:apiPath - 接口路径,httpMethod - HTTP方法
     * 返回:true表示可以访问,false表示拒绝访问
     */
    public boolean hasApiPermission(Long userId, String apiPath, String httpMethod) {
        // 1. 根据接口路径和方法查找对应权限
        Permission requiredPerm = permissionMapper.findByApiPathAndMethod(apiPath, httpMethod);
        if (requiredPerm == null) {
            // 如果接口未配置权限,默认拒绝访问(安全原则)
            return false;
        }
        
        if (requiredPerm.getStatus() != 1) {
            return false; // 权限被禁用
        }
        
        // 2. 检查用户是否有该权限
        return hasPermission(userId, requiredPerm.getPermCode());
    }
    
    /**
     * 清除用户权限缓存
     * 用途:用户角色或权限发生变化时,清除缓存确保数据一致性
     */
    public void clearUserPermissionCache(Long userId) {
        String cacheKey = USER_PERMISSIONS_CACHE + userId;
        redisTemplate.delete(cacheKey);
    }
    
    /**
     * 批量清除用户权限缓存
     * 用途:权限配置变更时,批量清除受影响用户的缓存
     */
    public void clearUserPermissionCacheByRole(Long roleId) {
        // 获取拥有该角色的所有用户
        List<Long> userIds = userMapper.getUserIdsByRole(roleId);
        
        List<String> keys = userIds.stream()
                .map(userId -> USER_PERMISSIONS_CACHE + userId)
                .collect(Collectors.toList());
        
        if (!keys.isEmpty()) {
            redisTemplate.delete(keys);
        }
    }
}

🏷️ 权限注解与AOP切面设计

权限校验注解定义

这个注解做什么用?

  • 声明式的权限控制,让代码更加优雅
  • 支持多种权限验证模式:AND/OR逻辑
  • 可以在方法或类级别使用
  • 配合AOP切面实现自动权限检查
/**
 * 权限校验注解
 * 功能:声明式权限控制,标记需要权限验证的方法或类
 * 使用方式:@RequirePermission("user:edit") 或 @RequirePermission(value={"user:edit","admin"}, logical=Logical.OR)
 * 验证时机:AOP切面在方法执行前自动进行权限检查
 * 优势:代码简洁、可读性强、维护方便
 */
@Target({ElementType.METHOD, ElementType.TYPE}) // 支持方法和类级别
@Retention(RetentionPolicy.RUNTIME) // 运行时生效,AOP可以获取
@Documented // 生成文档时包含此注解
public @interface RequirePermission {
    
    /**
     * 所需权限编码数组
     * 支持格式:["user:view", "order:create", "admin:dashboard"]
     * 必填项:至少需要一个权限编码
     */
    String[] value() default {};
    
    /**
     * 权限验证模式
     * AND: 用户需要拥有所有指定权限(默认)
     * OR: 用户只需要拥有其中一个权限即可
     * 使用场景:AND用于严格权限控制,OR用于灵活权限验证
     */
    Logical logical() default Logical.AND;
    
    /**
     * 权限验证失败时的错误消息
     * 支持国际化:可以通过messageSource获取本地化消息
     * 默认值:"权限不足"
     */
    String message() default "权限不足";
    
    /**
     * 是否启用权限检查
     * 开发调试时可以设置为false临时关闭权限检查
     * 生产环境应该保持true
     */
    boolean enabled() default true;
}

/**
 * 权限逻辑枚举
 * 定义权限验证的逻辑关系
 */
public enum Logical {
    /**
     * 逻辑与:需要满足所有权限条件
     * 应用场景:需要多重权限验证的严格场景
     * 示例:用户既要有"编辑权限"又要有"审核权限"
     */
    AND,
    
    /**
     * 逻辑或:满足任一权限条件即可
     * 应用场景:权限灵活配置的场景
     * 示例:用户有"管理员权限"或"超级管理员权限"都可以访问
     */
    OR
}
AOP权限切面实现

这个切面类做什么用?

  • 自动拦截所有带有@RequirePermission注解的方法
  • 在方法执行前进行权限验证,无需手动调用
  • 支持灵活的权限验证逻辑(AND/OR)
  • 统一处理权限验证异常,保持代码一致性
/**
 * AOP权限校验切面
 * 功能:自动拦截并验证带有@RequirePermission注解的方法
 * 原理:使用Spring AOP在方法执行前进行权限检查
 * 优势:无侵入式权限控制,业务代码与权限逻辑完全分离
 * 性能:支持权限缓存,避免重复数据库查询
 */
@Aspect
@Component
@Slf4j
@Order(1) // 设置切面执行顺序,确保在其他切面之前执行
public class PermissionAspect {
    
    @Autowired
    private PermissionService permissionService;
    
    /**
     * 权限验证核心方法
     * 拦截所有带有@RequirePermission注解的方法
     * 在方法执行前进行权限检查,验证通过后继续执行,验证失败则抛出异常
     */
    @Around("@annotation(requirePermission)")
    public Object checkPermission(ProceedingJoinPoint joinPoint, 
                                 RequirePermission requirePermission) throws Throwable {
        
        long startTime = System.currentTimeMillis();
        String methodName = joinPoint.getSignature().getName();
        String className = joinPoint.getTarget().getClass().getSimpleName();
        
        try {
            // 1. 检查权限验证是否启用
            if (!requirePermission.enabled()) {
                log.debug("权限验证已禁用,跳过方法:{}.{}", className, methodName);
                return joinPoint.proceed();
            }
            
            // 2. 获取当前登录用户
            User currentUser = SecurityContextHolder.getCurrentUser();
            if (currentUser == null) {
                log.warn("用户未登录,拒绝访问方法:{}.{}", className, methodName);
                throw new UnauthorizedException("用户未登录,请先登录");
            }
            
            // 3. 获取所需权限
            String[] requiredPermissions = requirePermission.value();
            if (requiredPermissions.length == 0) {
                log.debug("方法 {} 没有指定权限要求,允许访问", methodName);
                return joinPoint.proceed();
            }
            
            // 4. 执行权限验证
            boolean hasPermission = validatePermissions(currentUser.getId(), 
                    requiredPermissions, requirePermission.logical());
            
            // 5. 处理验证结果
            if (!hasPermission) {
                String permStr = Arrays.toString(requiredPermissions);
                log.warn("用户 {} 权限验证失败,需要权限:{},验证模式:{}", 
                        currentUser.getUsername(), permStr, requirePermission.logical());
                throw new ForbiddenException(requirePermission.message());
            }
            
            // 6. 权限验证通过,执行目标方法
            log.debug("用户 {} 权限验证通过,执行方法:{}.{}", 
                    currentUser.getUsername(), className, methodName);
            
            return joinPoint.proceed();
            
        } finally {
            // 记录权限验证耗时(用于性能监控)
            long endTime = System.currentTimeMillis();
            log.debug("权限验证耗时:{} ms", (endTime - startTime));
        }
    }
    
    /**
     * 执行具体的权限验证逻辑
     * userId: 用户ID
     * requiredPermissions: 所需权限数组
     * logical: 验证逻辑(AND/OR)
     * 返回:true表示验证通过,false表示验证失败
     */
    private boolean validatePermissions(Long userId, String[] requiredPermissions, Logical logical) {
        if (logical == Logical.AND) {
            // AND逻辑:用户需要拥有所有指定权限
            return permissionService.hasAllPermissions(userId, requiredPermissions);
        } else {
            // OR逻辑:用户只需要拥有其中一个权限
            return permissionService.hasAnyPermission(userId, requiredPermissions);
        }
    }
    
    /**
     * 类级别权限验证
     * 如果类上有@RequirePermission注解,类中所有方法都会进行权限验证
     */
    @Around("@within(requirePermission)")
    public Object checkClassPermission(ProceedingJoinPoint joinPoint, 
                                      RequirePermission requirePermission) throws Throwable {
        // 获取方法上是否有更具体的权限注解
        Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
        RequirePermission methodPermission = method.getAnnotation(RequirePermission.class);
        
        // 如果方法上有更具体的权限配置,优先使用方法级别的配置
        if (methodPermission != null && methodPermission.enabled()) {
            return checkPermission(joinPoint, methodPermission);
        }
        
        // 否则使用类级别的权限配置
        return checkPermission(joinPoint, requirePermission);
    }
}

🎯 控制器实际应用示例

这个控制器展示什么?

  • RBAC权限模型在实际业务中的完整应用
  • 不同权限验证模式的使用场景(单个权限、AND逻辑、OR逻辑)
  • 数据级权限与接口级权限的结合使用
  • 生产环境中的最佳实践代码示例
/**
 * 用户管理控制器
 * 功能:演示RBAC权限注解在实际业务中的应用
 * 展示:不同权限验证模式的具体使用方法
 * 特点:声明式权限控制,代码简洁且可维护性强
 * 安全:结合数据级权限检查,防止越权访问
 */
@RestController
@RequestMapping("/api/users")
@Slf4j
@Validated
public class UserController {
    
    @Autowired
    private UserService userService;
    @Autowired
    private PermissionService permissionService;
    
    /**
     * 用户列表查询 - 单个权限验证
     * 权限要求:只需要 user:view 权限
     * 使用场景:基础数据查看功能
     * 安全策略:接口级权限控制,防止普通用户查看敏感数据
     */
    @GetMapping("/list")
    @RequirePermission("user:view")
    public Result<PageResult<User>> listUsers(
            @RequestParam(defaultValue = "1") int page,
            @RequestParam(defaultValue = "10") int size,
            @RequestParam(required = false) String keyword) {
        
        log.info("用户 {} 查询用户列表,页码:{},大小:{}", 
                SecurityContextHolder.getCurrentUser().getUsername(), page, size);
        
        PageResult<User> users = userService.findUsers(page, size, keyword);
        return Result.success(users);
    }
    
    /**
     * 创建用户 - 基础权限验证
     * 权限要求:user:create 权限
     * 使用场景:系统管理员或HR创建新用户
     * 安全增强:添加数据验证和敏感信息保护
     */
    @PostMapping("/create")
    @RequirePermission("user:create")
    public Result<User> createUser(@Valid @RequestBody UserCreateDTO dto) {
        
        User currentUser = SecurityContextHolder.getCurrentUser();
        log.info("管理员 {} 创建新用户:{}", currentUser.getUsername(), dto.getUsername());
        
        // 业务逻辑:检查用户名是否已存在
        if (userService.existsByUsername(dto.getUsername())) {
            throw new BusinessException("用户名已存在");
        }
        
        // 创建用户(密码会自动加密)
        User user = userService.createUser(dto);
        
        // 敏感信息处理:返回时移除密码等敏感字段
        user.setPassword(null);
        
        return Result.success(user);
    }
    
    /**
     * 删除用户 - AND逻辑权限验证
     * 权限要求:同时需要 user:delete 和 admin:access 两个权限
     * 使用场景:需要多重验证的高危操作
     * 安全策略:严格权限控制,防止误删重要用户
     */
    @DeleteMapping("/{id}")
    @RequirePermission(value = {"user:delete", "admin:access"}, logical = Logical.AND)
    public Result<Void> deleteUser(@PathVariable Long id) {
        
        User currentUser = SecurityContextHolder.getCurrentUser();
        User targetUser = userService.findById(id);
        
        if (targetUser == null) {
            throw new NotFoundException("用户不存在");
        }
        
        // 数据级权限检查:防止删除超级管理员或自己
        if (targetUser.getRoleCode().equals("SUPER_ADMIN")) {
            throw new ForbiddenException("不能删除超级管理员");
        }
        
        if (targetUser.getId().equals(currentUser.getId())) {
            throw new ForbiddenException("不能删除自己");
        }
        
        log.warn("管理员 {} 删除用户:{}", currentUser.getUsername(), targetUser.getUsername());
        userService.deleteUser(id);
        
        return Result.success();
    }
    
    /**
     * 修改用户信息 - OR逻辑权限验证
     * 权限要求:拥有 user:edit 或 user:edit_self 其中一个权限即可
     * 使用场景:管理员修改任意用户信息,用户修改自己信息
     * 安全策略:结合数据级权限检查,确保只能修改有权限的用户
     */
    @PutMapping("/{id}")
    @RequirePermission(value = {"user:edit", "user:edit_self"}, logical = Logical.OR)
    public Result<User> updateUser(@PathVariable Long id, @Valid @RequestBody UserUpdateDTO dto) {
        
        User currentUser = SecurityContextHolder.getCurrentUser();
        User targetUser = userService.findById(id);
        
        if (targetUser == null) {
            throw new NotFoundException("用户不存在");
        }
        
        // 数据级权限检查
        if (!canEditUser(currentUser, targetUser)) {
            throw new ForbiddenException("无权修改此用户信息");
        }
        
        // 记录修改日志
        log.info("用户 {} 修改用户 {} 的信息:{}", 
                currentUser.getUsername(), targetUser.getUsername(), dto.toString());
        
        // 限制普通用户修改的字段
        if (!permissionService.hasPermission(currentUser.getId(), "user:edit")) {
            // 只有编辑自己信息的用户,限制可修改字段
            dto = limitEditableFields(dto);
        }
        
        User updatedUser = userService.updateUser(id, dto);
        updatedUser.setPassword(null); // 移除敏感信息
        
        return Result.success(updatedUser);
    }
    
    /**
     * 批量分配角色 - 复杂权限验证
     * 权限要求:user:role_assign 权限
     * 安全策略:需要额外检查目标用户是否可以被分配角色
     */
    @PostMapping("/{id}/roles")
    @RequirePermission("user:role_assign")
    public Result<Void> assignRoles(@PathVariable Long id, @RequestBody RoleAssignDTO dto) {
        
        User currentUser = SecurityContextHolder.getCurrentUser();
        User targetUser = userService.findById(id);
        
        if (targetUser == null) {
            throw new NotFoundException("用户不存在");
        }
        
        // 检查是否可以给该用户分配角色
        if (!canAssignRoleToUser(currentUser, targetUser, dto.getRoleIds())) {
            throw new ForbiddenException("无权给该用户分配指定角色");
        }
        
        log.info("管理员 {} 给用户 {} 分配角色:{}", 
                currentUser.getUsername(), targetUser.getUsername(), dto.getRoleIds());
        
        userService.assignRoles(id, dto.getRoleIds());
        return Result.success();
    }
    
    /**
     * 检查当前用户是否可以修改目标用户
     * 数据级权限控制的核心逻辑
     */
    private boolean canEditUser(User currentUser, User targetUser) {
        // 1. 有 user:edit 权限的用户可以修改任意用户
        if (permissionService.hasPermission(currentUser.getId(), "user:edit")) {
            return true;
        }
        
        // 2. 只有 user:edit_self 权限的用户只能修改自己
        if (permissionService.hasPermission(currentUser.getId(), "user:edit_self")) {
            return currentUser.getId().equals(targetUser.getId());
        }
        
        return false;
    }
    
    /**
     * 检查是否可以给用户分配角色
     * 防止低权限管理员给高权限用户分配不当角色
     */
    private boolean canAssignRoleToUser(User currentUser, User targetUser, List<Long> roleIds) {
        // 不能给超级管理员分配角色
        if (targetUser.getRoleCode().equals("SUPER_ADMIN")) {
            return false;
        }
        
        // 不能给用户分配比自己更高的权限
        int currentUserLevel = getUserMaxLevel(currentUser.getId());
        int maxAssignLevel = getUserMaxLevelByRoleIds(roleIds);
        
        return currentUserLevel >= maxAssignLevel;
    }
    
    /**
     * 限制普通用户可编辑的字段
     * 防止用户修改敏感字段
     */
    private UserUpdateDTO limitEditableFields(UserUpdateDTO original) {
        UserUpdateDTO limited = new UserUpdateDTO();
        limited.setEmail(original.getEmail());
        limited.setPhone(original.getPhone());
        limited.setAvatar(original.getAvatar());
        // 不允许修改角色、状态等敏感字段
        return limited;
    }
    
    /**
     * 获取用户的最大权限级别
     */
    private int getUserMaxLevel(Long userId) {
        // 实现逻辑:根据用户的最高角色确定权限级别
        return userService.getUserMaxLevel(userId);
    }
    
    /**
     * 根据角色ID列表获取最大权限级别
     */
    private int getUserMaxLevelByRoleIds(List<Long> roleIds) {
        return userService.getMaxLevelByRoleIds(roleIds);
    }
}

✅ 优点

  • 权限管理灵活,易于扩展
  • 支持复杂的权限组合
  • 权限与业务逻辑解耦
  • 支持动态权限分配

❌ 缺点

  • 实现复杂度较高
  • 数据库查询较多,性能需要优化
  • 需要完善的权限管理界面

三、ABAC属性访问控制(高级篇)

🧠 ABAC核心原理

ABAC(Attribute-Based Access Control)基于属性的访问控制,通过用户属性 + 资源属性 + 环境属性 + 动作属性四维矩阵实现超细粒度的动态权限控制。

🎯 ABAC vs RBAC 对比
维度RBAC(基于角色)ABAC(基于属性)
粒度粗粒度(角色级别)超细粒度(属性级别)
灵活性静态配置动态实时计算
扩展性需要预定义角色规则可动态扩展
复杂度简单易懂复杂但强大
适用场景大部分业务系统金融、军工、政府等高安全场景
🏗️ ABAC四大核心组件
  1. Subject(主体):访问者的属性(用户ID、角色、部门、安全级别等)
  2. Resource(资源):被访问对象的属性(数据敏感度、所属部门、访问频率等)
  3. Action(动作):执行的操作(读、写、删除、审批等)
  4. Environment(环境):上下文环境(时间、IP、设备、地理位置等)

🏗️ ABAC策略引擎架构设计

这个引擎解决什么问题?

  • 实现基于多属性维度的动态权限控制
  • 支持复杂的业务规则和安全策略
  • 提供灵活的权限决策机制
  • 适用于金融、政府等高安全要求的场景
/**
 * ABAC策略引擎核心接口
 * 功能:定义ABAC权限决策的标准接口
 * 作用:提供统一的权限评估框架
 * 扩展:可以基于此接口实现不同的策略引擎
 * 性能:支持策略缓存和批量决策优化
 */
public interface PolicyEngine {
    
    /**
     * 评估访问请求是否被允许
     * 这是ABAC的核心方法,通过多属性分析做出权限决策
     * 
     * @param request 完整的访问请求,包含主体、资源、动作、环境四大维度
     * @return 决策结果,包含是否允许、决策原因、匹配策略等信息
     */
    PolicyDecision evaluate(AccessRequest request);
    
    /**
     * 批量评估多个访问请求
     * 用于提高批量权限检查的性能
     */
    List<PolicyDecision> evaluateBatch(List<AccessRequest> requests);
    
    /**
     * 添加新的访问策略
     * 支持运行时动态添加策略,无需重启系统
     */
    void addPolicy(Policy policy);
    
    /**
     * 移除指定的访问策略
     */
    void removePolicy(String policyId);
    
    /**
     * 更新现有策略
     */
    void updatePolicy(Policy policy);
    
    /**
     * 获取所有活跃策略
     */
    List<Policy> getAllPolicies();
    
    /**
     * 策略执行统计
     * 用于监控和优化策略性能
     */
    PolicyStatistics getStatistics();
}

/**
 * ABAC访问请求实体
 * 封装权限评估所需的完整上下文信息
 * 四大维度:主体、资源、动作、环境
 */
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class AccessRequest {
    
    /**
     * 访问主体 - 请求访问的用户或系统
     * 包含属性:用户ID、角色、部门、安全级别、职位等
     */
    private Subject subject;      
    
    /**
     * 访问资源 - 被访问的对象
     * 包含属性:资源ID、类型、敏感度、所有者、所属部门等
     */
    private Resource resource;    
    
    /**
     * 执行动作 - 对资源执行的操作
     * 包含属性:动作类型(读/写/删除)、操作级别、是否审批等
     */
    private Action action;        
    
    /**
     * 环境上下文 - 访问时的环境信息
     * 包含属性:时间、IP地址、设备信息、地理位置、网络环境等
     */
    private Environment environment; 
    
    /**
     * 请求元数据
     */
    private String requestId;          // 请求唯一标识
    private LocalDateTime timestamp;   // 请求时间戳
    private String clientIp;          // 客户端IP
    private String userAgent;          // 用户代理
    
    /**
     * 获取请求的完整描述
     */
    public String getDescription() {
        return String.format("用户%s在%s通过%s从%s对%s执行%s操作", 
                subject.getId(), timestamp, userAgent, clientIp, 
                resource.getId(), action.getType());
    }
}

/**
 * ABAC策略决策结果
 * 包含决策过程和结果的详细信息
 */
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class PolicyDecision {
    
    /**
     * 决策结果:true=允许访问,false=拒绝访问
     */
    private boolean allowed;
    
    /**
     * 决策原因
     * 提供详细的决策说明,便于审计和调试
     */
    private String reason;
    
    /**
     * 匹配的策略ID
     * 记录哪个策略触发了这个决策
     */
    private String matchedPolicyId;
    
    /**
     * 决策耗时(毫秒)
     */
    private long decisionTimeMs;
    
    /**
     * 决策时间
     */
    private LocalDateTime decisionTime;
    
    /**
     * 决策详情
     * 记录各个属性的匹配情况
     */
    private Map<String, Object> decisionDetails;
    
    /**
     * 是否有警告
     * 比如在边界条件下允许访问,但需要监控
     */
    private boolean hasWarning;
    
    /**
     * 警告信息
     */
    private String warningMessage;
    
    /**
     * 获取决策结果的可读描述
     */
    public String getResultDescription() {
        String result = allowed ? "允许访问" : "拒绝访问";
        String extraInfo = hasWarning ? "(有警告)" : "";
        return result + extraInfo + ":" + reason;
    }
}

/**
 * 策略执行统计信息
 * 用于监控策略引擎的运行状况
 */
@Data
public class PolicyStatistics {
    private long totalRequests;          // 总请求数
    private long allowedRequests;        // 允许的请求数
    private long deniedRequests;         // 拒绝的请求数
    private double averageDecisionTime;   // 平均决策时间
    private Map<String, Long> policyUsage; // 各策略使用次数
    private List<String> topUsedPolicies; // 最常用的策略
}

⚙️ ABAC策略引擎具体实现

这个实现类做什么用?

  • 实现具体的ABAC权限决策逻辑
  • 支持多种策略类型的综合评估
  • 提供高性能的并行策略匹配
  • 包含完整的决策日志和统计功能
/**
 * 基于规则的ABAC策略引擎实现
 * 功能:高性能的权限决策引擎,支持复杂业务规则
 * 特点:线程安全、支持策略缓存、并行评估优化
 * 性能:平均决策时间 < 10ms,支持高并发场景
 * 扩展:支持热加载策略,无需重启服务
 */
@Component
@Slf4j
public class RuleBasedPolicyEngine implements PolicyEngine {
    
    // 策略存储:使用ConcurrentHashMap保证线程安全
    private final Map<String, Policy> policies = new ConcurrentHashMap<>();
    
    // 策略优先级排序缓存
    private volatile List<Policy> sortedPolicies = new ArrayList<>();
    
    // 决策统计
    private final AtomicLong totalRequests = new AtomicLong(0);
    private final AtomicLong allowedRequests = new AtomicLong(0);
    private final AtomicLong deniedRequests = new AtomicLong(0);
    private final Map<String, AtomicLong> policyUsageCount = new ConcurrentHashMap<>();
    
    // 线程池:用于并行策略评估
    private final ExecutorService executorService;
    
    // 策略决策缓存
    private final Cache<String, PolicyDecision> decisionCache;
    
    public RuleBasedPolicyEngine() {
        // 创建固定大小的线程池
        this.executorService = Executors.newFixedThreadPool(
                Runtime.getRuntime().availableProcessors(),
                new ThreadFactoryBuilder()
                        .setNameFormat("policy-engine-%d")
                        .setDaemon(true)
                        .build()
        );
        
        // 配置决策缓存
        this.decisionCache = Caffeine.newBuilder()
                .maximumSize(10000) // 最大缓存数量
                .expireAfterWrite(Duration.ofMinutes(5)) // 5分钟过期
                .recordStats() // 记录统计信息
                .build();
    }
    
    /**
     * 核心方法:评估访问请求
     * 使用并行策略评估提高性能
     * 支持决策缓存减少重复计算
     */
    @Override
    public PolicyDecision evaluate(AccessRequest request) {
        long startTime = System.currentTimeMillis();
        totalRequests.incrementAndGet();
        
        try {
            // 1. 检查决策缓存
            String cacheKey = buildCacheKey(request);
            PolicyDecision cachedDecision = decisionCache.getIfPresent(cacheKey);
            if (cachedDecision != null) {
                log.debug("使用缓存的决策结果:{}", cachedDecision.getResultDescription());
                return cachedDecision;
            }
            
            // 2. 并行评估所有策略
            PolicyDecision decision = evaluatePoliciesParallel(request);
            
            // 3. 缓存决策结果
            if (!decision.hasWarning()) { // 只缓存没有警告的决策
                decisionCache.put(cacheKey, decision);
            }
            
            // 4. 更新统计
            updateStatistics(decision);
            
            long decisionTime = System.currentTimeMillis() - startTime;
            decision.setDecisionTimeMs(decisionTime);
            
            log.info("策略决策完成:{},耗时:{}ms", decision.getResultDescription(), decisionTime);
            
            return decision;
            
        } catch (Exception e) {
            log.error("策略评估异常:{}", request.getDescription(), e);
            
            // 安全原则:异常时默认拒绝
            return PolicyDecision.builder()
                    .allowed(false)
                    .reason("系统异常,拒绝访问")
                    .decisionTimeMs(System.currentTimeMillis() - startTime)
                    .build();
        }
    }
    
    /**
     * 批量评估访问请求
     * 用于提高批量权限检查的性能
     */
    @Override
    public List<PolicyDecision> evaluateBatch(List<AccessRequest> requests) {
        return requests.parallelStream()
                .map(this::evaluate)
                .collect(Collectors.toList());
    }
    
    /**
     * 并行策略评估
     * 使用线程池并行评估所有匹配的策略
     */
    private PolicyDecision evaluatePoliciesParallel(AccessRequest request) {
        List<Policy> applicablePolicies = sortedPolicies.stream()
                .filter(policy -> policy.matches(request))
                .collect(Collectors.toList());
        
        if (applicablePolicies.isEmpty()) {
            return PolicyDecision.builder()
                    .allowed(false)
                    .reason("没有匹配的访问策略,默认拒绝")
                    .decisionTime(LocalDateTime.now())
                    .build();
        }
        
        // 按优先级排序后评估
        applicablePolicies.sort(Comparator.comparingInt(Policy::getPriority));
        
        for (Policy policy : applicablePolicies) {
            long startTime = System.currentTimeMillis();
            
            try {
                boolean allowed = policy.evaluate(request);
                long evaluationTime = System.currentTimeMillis() - startTime;
                
                // 更新策略使用统计
                policyUsageCount.computeIfAbsent(policy.getId(), k -> new AtomicLong(0))
                        .incrementAndGet();
                
                if (allowed) {
                    // 找到允许的策略,立即返回
                    PolicyDecision decision = PolicyDecision.builder()
                            .allowed(true)
                            .reason(String.format("策略【%s】匹配,允许访问", policy.getName()))
                            .matchedPolicyId(policy.getId())
                            .decisionTime(LocalDateTime.now())
                            .decisionDetails(policy.getEvaluationDetails())
                            .build();
                    
                    // 记录策略使用日志
                    log.info("策略【{}】允许访问,评估耗时:{}ms", policy.getName(), evaluationTime);
                    return decision;
                }
                
            } catch (Exception e) {
                log.error("策略【{}】评估异常:{}", policy.getName(), e.getMessage());
            }
        }
        
        // 所有策略都拒绝或异常
        return PolicyDecision.builder()
                .allowed(false)
                .reason("所有匹配的策略都拒绝访问")
                .decisionTime(LocalDateTime.now())
                .build();
    }
    
    /**
     * 构建缓存键
     * 基于请求的关键属性生成唯一标识
     */
    private String buildCacheKey(AccessRequest request) {
        return String.format("%s:%s:%s:%s:%s",
                request.getSubject().getId(),
                request.getResource().getId(),
                request.getAction().getType(),
                request.getResource().getType(),
                request.getEnvironment().getClientIp());
    }
    
    /**
     * 更新统计信息
     */
    private void updateStatistics(PolicyDecision decision) {
        if (decision.isAllowed()) {
            allowedRequests.incrementAndGet();
        } else {
            deniedRequests.incrementAndGet();
        }
        
        if (decision.getMatchedPolicyId() != null) {
            policyUsageCount.computeIfAbsent(decision.getMatchedPolicyId(), k -> new AtomicLong(0))
                    .incrementAndGet();
        }
    }
    
    @Override
    public void addPolicy(Policy policy) {
        if (policy == null || StringUtils.isEmpty(policy.getId())) {
            throw new IllegalArgumentException("策略或策略ID不能为空");
        }
        
        policies.put(policy.getId(), policy);
        refreshPolicyCache();
        
        log.info("添加策略:{} - {}", policy.getId(), policy.getName());
    }
    
    @Override
    public void removePolicy(String policyId) {
        Policy removed = policies.remove(policyId);
        if (removed != null) {
            refreshPolicyCache();
            log.info("移除策略:{} - {}", policyId, removed.getName());
        }
    }
    
    @Override
    public void updatePolicy(Policy policy) {
        if (policy == null || StringUtils.isEmpty(policy.getId())) {
            throw new IllegalArgumentException("策略或策略ID不能为空");
        }
        
        policies.put(policy.getId(), policy);
        refreshPolicyCache();
        
        log.info("更新策略:{} - {}", policy.getId(), policy.getName());
    }
    
    @Override
    public List<Policy> getAllPolicies() {
        return new ArrayList<>(policies.values());
    }
    
    @Override
    public PolicyStatistics getStatistics() {
        return PolicyStatistics.builder()
                .totalRequests(totalRequests.get())
                .allowedRequests(allowedRequests.get())
                .deniedRequests(deniedRequests.get())
                .averageDecisionTime(decisionCache.stats().averageLoadTime())
                .policyUsage(policyUsageCount.entrySet().stream()
                        .collect(Collectors.toMap(
                                Map.Entry::getKey,
                                entry -> entry.getValue().get()
                        )))
                .topUsedPolicies(getTopUsedPolicies())
                .build();
    }
    
    /**
     * 刷新策略缓存
     * 当策略发生变化时重新排序策略列表
     */
    private void refreshPolicyCache() {
        sortedPolicies = policies.values().stream()
                .sorted(Comparator.comparingInt(Policy::getPriority))
                .collect(Collectors.toList());
        
        // 清除决策缓存,因为策略可能已变化
        decisionCache.invalidateAll();
    }
    
    /**
     * 获取最常用的策略
     */
    private List<String> getTopUsedPolicies() {
        return policyUsageCount.entrySet().stream()
                .sorted(Map.Entry.<String, AtomicLong>comparingByValue(
                        Comparator.comparingLong(AtomicLong::get)).reversed())
                .limit(5)
                .map(Map.Entry::getKey)
                .collect(Collectors.toList());
    }
    
    /**
     * 销毁资源
     */
    @PreDestroy
    public void destroy() {
        executorService.shutdown();
        try {
            if (!executorService.awaitTermination(5, TimeUnit.SECONDS)) {
                executorService.shutdownNow();
            }
        } catch (InterruptedException e) {
            executorService.shutdownNow();
            Thread.currentThread().interrupt();
        }
        
        log.info("策略引擎已关闭,统计信息:{}", getStatistics());
    }
}
策略定义
/**
 * 访问策略抽象类
 */
public abstract class Policy {
    protected String id;
    protected String name;
    protected String description;
    
    public abstract boolean matches(AccessRequest request);
    public abstract boolean evaluate(AccessRequest request);
    
    // getters and setters...
}

/**
 * 基于规则的访问策略
 */
public class RuleBasedPolicy extends Policy {
    private List<Rule> rules = new ArrayList<>();
    
    @Override
    public boolean matches(AccessRequest request) {
        // 检查资源类型是否匹配
        if (!matchesResource(request.getResource())) {
            return false;
        }
        
        // 检查动作是否匹配
        if (!matchesAction(request.getAction())) {
            return false;
        }
        
        return true;
    }
    
    @Override
    public boolean evaluate(AccessRequest request) {
        for (Rule rule : rules) {
            if (!rule.evaluate(request)) {
                return false;
            }
        }
        return true;
    }
    
    private boolean matchesResource(Resource resource) {
        // 实现资源匹配逻辑
        return true;
    }
    
    private boolean matchesAction(Action action) {
        // 实现动作匹配逻辑
        return true;
    }
}

/**
 * 访问规则
 */
public interface Rule {
    boolean evaluate(AccessRequest request);
}

/**
 * 时间访问规则
 * 只允许在指定时间段内访问
 */
public class TimeAccessRule implements Rule {
    private int startHour;
    private int endHour;
    
    public TimeAccessRule(int startHour, int endHour) {
        this.startHour = startHour;
        this.endHour = endHour;
    }
    
    @Override
    public boolean evaluate(AccessRequest request) {
        int currentHour = LocalTime.now().getHour();
        return currentHour >= startHour && currentHour <= endHour;
    }
}

/**
 * IP地址访问规则
 * 限制IP访问范围
 */
public class IpAccessRule implements Rule {
    private List<String> allowedIps;
    
    public IpAccessRule(List<String> allowedIps) {
        this.allowedIps = allowedIps;
    }
    
    @Override
    public boolean evaluate(AccessRequest request) {
        String clientIp = request.getEnvironment().getClientIp();
        return allowedIps.contains(clientIp) || allowedIps.contains("*");
    }
}

/**
 * 数据敏感性规则
 * 根据用户级别和数据敏感度控制访问
 */
public class DataSensitivityRule implements Rule {
    
    @Override
    public boolean evaluate(AccessRequest request) {
        Subject subject = request.getSubject();
        Resource resource = request.getResource();
        
        // 获取用户级别
        int userLevel = (int) subject.getAttribute("level");
        
        // 获取数据敏感度
        int sensitivityLevel = (int) resource.getAttribute("sensitivity");
        
        return userLevel >= sensitivityLevel;
    }
}
权限注解和AOP
/**
 * ABAC权限校验注解
 */
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface AbacPermission {
    
    /**
     * 资源类型
     */
    String resourceType() default "";
    
    /**
     * 动作类型
     */
    String action() default "";
    
    /**
     * 是否启用环境检查
     */
    boolean checkEnvironment() default true;
}

/**
 * ABAC权限切面
 */
@Aspect
@Component
public class AbacPermissionAspect {
    
    @Autowired
    private PolicyEngine policyEngine;
    
    @Around("@annotation(abacPermission)")
    public Object checkPermission(ProceedingJoinPoint joinPoint, 
                                 AbacPermission abacPermission) throws Throwable {
        
        // 1. 构建访问请求
        AccessRequest request = buildAccessRequest(joinPoint, abacPermission);
        
        // 2. 策略评估
        PolicyDecision decision = policyEngine.evaluate(request);
        
        // 3. 决策处理
        if (!decision.isAllowed()) {
            throw new ForbiddenException("访问被拒绝: " + decision.getReason());
        }
        
        // 4. 记录访问日志
        logAccess(request, decision);
        
        return joinPoint.proceed();
    }
    
    private AccessRequest buildAccessRequest(ProceedingJoinPoint joinPoint, 
                                           AbacPermission abacPermission) {
        AccessRequest request = new AccessRequest();
        
        // 设置访问主体
        Subject subject = buildSubject();
        request.setSubject(subject);
        
        // 设置访问资源
        Resource resource = buildResource(joinPoint, abacPermission);
        request.setResource(resource);
        
        // 设置执行动作
        Action action = buildAction(abacPermission);
        request.setAction(action);
        
        // 设置环境上下文
        Environment environment = buildEnvironment();
        request.setEnvironment(environment);
        
        return request;
    }
    
    private Subject buildSubject() {
        User currentUser = SecurityContextHolder.getCurrentUser();
        Subject subject = new Subject();
        subject.setId(currentUser.getId());
        subject.setAttribute("username", currentUser.getUsername());
        subject.setAttribute("roles", currentUser.getRoles());
        subject.setAttribute("level", currentUser.getLevel());
        return subject;
    }
    
    private Resource buildResource(ProceedingJoinPoint joinPoint, 
                                  AbacPermission abacPermission) {
        Resource resource = new Resource();
        resource.setType(abacPermission.resourceType());
        
        // 从方法参数中提取资源ID
        Object[] args = joinPoint.getArgs();
        if (args.length > 0) {
            for (Object arg : args) {
                if (arg instanceof Long || arg instanceof Integer) {
                    resource.setId(Long.parseLong(arg.toString()));
                    break;
                }
            }
        }
        
        // 设置资源属性
        switch (abacPermission.resourceType()) {
            case "user":
                resource.setAttribute("sensitivity", 1);
                break;
            case "financial_record":
                resource.setAttribute("sensitivity", 3);
                break;
            case "system_config":
                resource.setAttribute("sensitivity", 5);
                break;
        }
        
        return resource;
    }
    
    private Action buildAction(AbacPermission abacPermission) {
        Action action = new Action();
        action.setType(abacPermission.action());
        return action;
    }
    
    private Environment buildEnvironment() {
        Environment environment = new Environment();
        HttpServletRequest request = getCurrentRequest();
        environment.setClientIp(getClientIp(request));
        environment.setTimestamp(LocalDateTime.now());
        environment.setUserAgent(request.getHeader("User-Agent"));
        return environment;
    }
    
    private void logAccess(AccessRequest request, PolicyDecision decision) {
        // 记录访问日志
        System.out.println(String.format("访问日志 - 用户:%s, 资源:%s, 动作:%s, 决策:%s", 
                request.getSubject().getId(),
                request.getResource().getType(),
                request.getAction().getType(),
                decision.isAllowed() ? "允许" : "拒绝"));
    }
}
控制器使用示例
/**
 * 高级权限控制器
 * 演示ABAC权限控制
 */
@RestController
@RequestMapping("/api/advanced")
public class AdvancedController {
    
    /**
     * 查看用户详情
     * 应用ABAC策略:根据用户级别和数据敏感度控制访问
     */
    @GetMapping("/users/{userId}")
    @AbacPermission(resourceType = "user", action = "view")
    public Result<User> getUserDetail(@PathVariable Long userId) {
        User user = userService.findById(userId);
        return Result.success(user);
    }
    
    /**
     * 访问财务记录
     * 高敏感度数据,需要更严格的权限控制
     */
    @GetMapping("/financial/{recordId}")
    @AbacPermission(resourceType = "financial_record", action = "view")
    public Result<FinancialRecord> getFinancialRecord(@PathVariable Long recordId) {
        FinancialRecord record = financialService.findById(recordId);
        return Result.success(record);
    }
    
    /**
     * 修改系统配置
     * 最高权限要求,只能在工作时间访问
     */
    @PutMapping("/system/config")
    @AbacPermission(resourceType = "system_config", action = "edit")
    public Result<Void> updateSystemConfig(@RequestBody SystemConfigDTO dto) {
        systemService.updateConfig(dto);
        return Result.success();
    }
}
策略初始化
/**
 * 策略初始化配置
 */
@Component
public class PolicyInitializer {
    
    @Autowired
    private PolicyEngine policyEngine;
    
    @PostConstruct
    public void initPolicies() {
        // 用户查看策略
        Policy userViewPolicy = createUserViewPolicy();
        policyEngine.addPolicy(userViewPolicy);
        
        // 财务记录访问策略
        Policy financialPolicy = createFinancialPolicy();
        policyEngine.addPolicy(financialPolicy);
        
        // 系统配置策略
        Policy systemConfigPolicy = createSystemConfigPolicy();
        policyEngine.addPolicy(systemConfigPolicy);
    }
    
    private Policy createUserViewPolicy() {
        RuleBasedPolicy policy = new RuleBasedPolicy();
        policy.setId("user_view_policy");
        policy.setName("用户查看策略");
        policy.setDescription("控制用户信息的访问权限");
        
        // 数据敏感性规则
        policy.getRules().add(new DataSensitivityRule());
        
        return policy;
    }
    
    private Policy createFinancialPolicy() {
        RuleBasedPolicy policy = new RuleBasedPolicy();
        policy.setId("financial_policy");
        policy.setName("财务数据访问策略");
        policy.setDescription("严格控制财务数据的访问");
        
        // 数据敏感性规则(要求更高权限)
        policy.getRules().add(new DataSensitivityRule());
        
        // 时间限制规则(只能在工作时间访问)
        policy.getRules().add(new TimeAccessRule(9, 18));
        
        // IP限制规则
        policy.getRules().add(new IpAccessRule(Arrays.asList("192.168.1.*", "10.0.0.*")));
        
        return policy;
    }
    
    private Policy createSystemConfigPolicy() {
        RuleBasedPolicy policy = new RuleBasedPolicy();
        policy.setId("system_config_policy");
        policy.setName("系统配置策略");
        policy.setDescription("系统配置修改的权限控制");
        
        // 数据敏感性规则
        policy.getRules().add(new DataSensitivityRule());
        
        // 时间限制规则
        policy.getRules().add(new TimeAccessRule(9, 17));
        
        // IP限制规则(更严格的IP控制)
        policy.getRules().add(new IpAccessRule(Arrays.asList("192.168.1.100")));
        
        return policy;
    }
}

✅ ABAC方案优缺点分析

优点:

  • 🎯 超细粒度控制:基于多属性精确控制,可以精确到每个数据字段
  • 🔄 动态决策:根据实时环境和上下文动态调整权限
  • 🧠 智能策略:支持复杂的业务规则和安全策略
  • 🚀 高度灵活:无需预定义角色,规则可随时调整

缺点:

  • 🧩 实现复杂:需要深入理解业务场景和权限模型
  • ⏱️ 性能开销:多属性判断和策略匹配需要更多计算资源
  • 📚 配置复杂:策略编写需要专业的安全知识
  • 🔍 调试困难:决策路径复杂,问题排查需要专业工具

四、电商与SaaS实战案例(应用篇)

🛒 案例1:电商系统RBAC权限设计

这个案例解决什么问题?

  • 电商平台多角色权限管理(买家、卖家、管理员)
  • 复杂的订单权限控制(数据级权限)
  • 灵活的商品管理权限分配
  • 实际生产环境的最佳实践
🎯 业务需求分析
角色权限范围核心功能数据权限
买家个人订单相关下单、支付、查看个人订单只能访问自己的订单和地址
卖家商品和订单管理商品上架、订单处理、销售统计只能管理自己店铺的商品和订单
管理员全平台管理用户管理、系统配置、数据统计可访问所有数据,但有限制
超级管理员系统完全控制所有功能、角色权限管理无限制访问权限

🗃️ 电商权限数据库设计

这套表结构解决什么问题?

  • 支持电商平台的复杂角色权限需求
  • 实现数据级权限隔离(买家只能看自己的订单)
  • 提供灵活的权限扩展机制
  • 支持店铺级别的权限控制
-- ========================================
-- 电商角色表
-- 扩展基础角色表,增加电商特有字段
-- ========================================
INSERT INTO roles (role_name, role_code, description, sort_order) VALUES
('买家', 'BUYER', '普通买家用户,可以下单购买商品', 1),
('卖家', 'SELLER', '商家用户,可以管理店铺和商品', 2),
('管理员', 'ADMIN', '平台管理员,负责用户管理和系统配置', 3),
('超级管理员', 'SUPER_ADMIN', '系统超级管理员,拥有所有权限', 4);

-- ========================================
-- 电商权限表
-- 涵盖电商平台的所有业务功能
-- ========================================
INSERT INTO permissions (perm_name, perm_code, perm_type, resource_path, parent_id, description) VALUES

-- 商品相关权限
('查看商品列表', 'product:list', 'API', '/api/products', 0, '查看商品列表和详情'),
('创建商品', 'product:create', 'API', '/api/products', 0, '发布新商品'),
('编辑商品', 'product:edit', 'API', '/api/products/*', 0, '修改商品信息'),
('删除商品', 'product:delete', 'API', '/api/products/*', 0, '删除商品'),
('商品上架/下架', 'product:toggle_status', 'API', '/api/products/*/status', 0, '控制商品上架状态'),
('设置商品价格', 'product:set_price', 'API', '/api/products/*/price', 0, '修改商品价格'),

-- 订单相关权限
('查看订单列表', 'order:list', 'API', '/api/orders', 0, '查看订单列表'),
('创建订单', 'order:create', 'API', '/api/orders', 0, '用户下单'),
('查看订单详情', 'order:view', 'API', '/api/orders/*', 0, '查看订单详细信息'),
('处理订单', 'order:process', 'API', '/api/orders/*/process', 0, '发货、完成订单'),
('取消订单', 'order:cancel', 'API', '/api/orders/*/cancel', 0, '取消订单'),
('订单退款', 'order:refund', 'API', '/api/orders/*/refund', 0, '处理订单退款'),

-- 店铺相关权限
('查看店铺信息', 'shop:view', 'API', '/api/shops/*', 0, '查看店铺基本信息'),
('管理店铺设置', 'shop:manage', 'API', '/api/shops/*', 0, '修改店铺设置和配置'),
('查看店铺统计', 'shop:statistics', 'API', '/api/shops/*/stats', 0, '查看店铺销售统计数据'),

-- 用户管理权限
('查看用户列表', 'user:list', 'API', '/api/admin/users', 0, '管理员查看所有用户'),
('编辑用户信息', 'user:edit', 'API', '/api/admin/users/*', 0, '管理员修改用户信息'),
('禁用/启用用户', 'user:toggle_status', 'API', '/api/admin/users/*/status', 0, '禁用或启用用户账号'),
('重置用户密码', 'user:reset_password', 'API', '/api/admin/users/*/password', 0, '管理员重置用户密码'),

-- 系统管理权限
('查看系统统计', 'system:stats', 'API', '/api/admin/stats', 0, '查看平台整体统计数据'),
('管理角色权限', 'system:role_manage', 'API', '/api/admin/roles', 0, '管理系统角色和权限'),
('系统配置管理', 'system:config', 'API', '/api/admin/config', 0, '管理系统配置参数'),
('查看审计日志', 'system:audit_log', 'API', '/api/admin/audit', 0, '查看系统操作审计日志');

-- ========================================
-- 角色权限分配
-- 根据电商业务规则分配权限
-- ========================================

-- 买家权限:可以查看商品、下单、管理自己的订单
INSERT INTO role_permissions (role_id, perm_id)
SELECT r.id, p.id FROM roles r, permissions p 
WHERE r.role_code = 'BUYER' 
AND p.perm_code IN (
    'product:list', 'order:list', 'order:create', 'order:view', 'order:cancel'
);

-- 卖家权限:商品管理、订单处理、店铺管理
INSERT INTO role_permissions (role_id, perm_id)
SELECT r.id, p.id FROM roles r, permissions p 
WHERE r.role_code = 'SELLER' 
AND p.perm_code IN (
    'product:list', 'product:create', 'product:edit', 'product:delete', 
    'product:toggle_status', 'product:set_price',
    'order:list', 'order:view', 'order:process', 'order:refund',
    'shop:view', 'shop:manage', 'shop:statistics'
);

-- 管理员权限:用户管理、查看数据、基础系统功能
INSERT INTO role_permissions (role_id, perm_id)
SELECT r.id, p.id FROM roles r, permissions p 
WHERE r.role_code = 'ADMIN' 
AND p.perm_code IN (
    'product:list', 'order:list', 'order:view',
    'shop:view', 'shop:statistics',
    'user:list', 'user:edit', 'user:toggle_status', 'user:reset_password',
    'system:stats', 'system:audit_log'
);

-- 超级管理员:拥有所有权限
INSERT INTO role_permissions (role_id, perm_id)
SELECT r.id, p.id FROM roles r, permissions p 
WHERE r.role_code = 'SUPER_ADMIN';

-- ========================================
-- 权限验证结果查询
-- 确认权限分配是否正确
-- ========================================

-- 查询各角色的权限数量
SELECT 
    r.role_name,
    r.role_code,
    COUNT(rp.perm_id) as permission_count
FROM roles r
LEFT JOIN role_permissions rp ON r.id = rp.role_id
GROUP BY r.id, r.role_name, r.role_code
ORDER BY r.sort_order;

-- 查询买家角色的具体权限
SELECT 
    p.perm_name,
    p.perm_code,
    p.resource_path
FROM roles r
JOIN role_permissions rp ON r.id = rp.role_id
JOIN permissions p ON rp.perm_id = p.id
WHERE r.role_code = 'BUYER'
ORDER BY p.perm_code;

💻 电商订单控制器完整实现

这个控制器展示什么?

  • 电商订单系统的完整权限控制逻辑
  • 接口级权限 + 数据级权限的双重保护
  • 复杂业务场景下的权限判断
  • 生产环境的安全最佳实践
/**
 * 电商订单控制器
 * 功能:演示电商订单系统的完整权限控制
 * 特点:接口权限验证 + 数据级权限检查
 * 安全:防止越权访问、保护用户隐私数据
 * 业务:支持买家、卖家、管理员不同的操作权限
 */
@RestController
@RequestMapping("/api/orders")
@Slf4j
@Validated
public class OrderController {
    
    @Autowired
    private OrderService orderService;
    @Autowired
    private UserService userService;
    @Autowired
    private ShopService shopService;
    @Autowired
    private PermissionService permissionService;
    
    /**
     * 查看订单详情 - 多角色数据权限控制
     * 权限要求:order:view 接口权限
     * 数据权限:买家看自己的订单,卖家看店铺的订单,管理员看所有订单
     * 安全策略:严格的数据隔离,防止越权查看他人订单
     */
    @GetMapping("/{orderId}")
    @RequirePermission("order:view")
    public Result<OrderDetailVO> getOrder(@PathVariable Long orderId) {
        
        User currentUser = SecurityContextHolder.getCurrentUser();
        log.info("用户 {} 查询订单详情:{}", currentUser.getUsername(), orderId);
        
        // 1. 获取订单信息
        Order order = orderService.findById(orderId);
        if (order == null) {
            throw new NotFoundException("订单不存在");
        }
        
        // 2. 数据级权限检查
        if (!canViewOrder(currentUser, order)) {
            log.warn("用户 {} 尝试越权查看订单:{}", currentUser.getUsername(), orderId);
            throw new ForbiddenException("无权查看此订单");
        }
        
        // 3. 根据用户角色过滤敏感信息
        OrderDetailVO orderVO = buildOrderVO(currentUser, order);
        
        return Result.success(orderVO);
    }
    
    /**
     * 创建订单 - 买家专属功能
     * 权限要求:order:create 接口权限 + BUYER角色
     * 业务逻辑:验证商品库存、价格、用户收货地址等
     * 安全策略:防止非买家角色下单
     */
    @PostMapping
    @RequirePermission("order:create")
    public Result<Order> createOrder(@Valid @RequestBody OrderCreateDTO dto) {
        
        User currentUser = SecurityContextHolder.getCurrentUser();
        log.info("用户 {} 创建订单:{}", currentUser.getUsername(), dto.toString());
        
        // 1. 角色检查:只有买家可以创建订单
        if (!hasRole(currentUser, "BUYER")) {
            log.warn("非买家用户 {} 尝试创建订单", currentUser.getUsername());
            throw new ForbiddenException("只有买家可以创建订单");
        }
        
        // 2. 业务验证:检查商品状态、库存、价格等
        validateOrderCreation(dto);
        
        // 3. 创建订单
        Order order = orderService.createOrder(dto, currentUser.getId());
        
        // 4. 记录操作日志
        log.info("订单创建成功:订单ID={},用户={},金额={}", 
                order.getId(), currentUser.getUsername(), order.getTotalAmount());
        
        return Result.success(order);
    }
    
    /**
     * 处理订单 - 卖家专属功能
     * 权限要求:order:process 接口权限 + 店铺所有权
     * 业务逻辑:发货、完成订单、确认收货等操作
     * 安全策略:只能处理自己店铺的订单
     */
    @PutMapping("/{orderId}/process")
    @RequirePermission("order:process")
    public Result<Void> processOrder(@PathVariable Long orderId, 
                                   @Valid @RequestBody OrderProcessDTO dto) {
        
        User currentUser = SecurityContextHolder.getCurrentUser();
        Order order = orderService.findById(orderId);
        
        if (order == null) {
            throw new NotFoundException("订单不存在");
        }
        
        log.info("卖家 {} 处理订单:{},操作:{}", 
                currentUser.getUsername(), orderId, dto.getAction());
        
        // 1. 角色检查:只有卖家可以处理订单
        if (!hasRole(currentUser, "SELLER")) {
            throw new ForbiddenException("只有卖家可以处理订单");
        }
        
        // 2. 店铺权限检查:只能处理自己店铺的订单
        if (!isOrderSeller(currentUser, order)) {
            log.warn("卖家 {} 尝试处理其他店铺的订单:{}", 
                    currentUser.getUsername(), orderId);
            throw new ForbiddenException("只能处理自己店铺的订单");
        }
        
        // 3. 业务逻辑:检查订单状态是否允许当前操作
        if (!order.canProcess(dto.getAction())) {
            throw new BusinessException("订单当前状态不允许此操作");
        }
        
        // 4. 执行订单处理
        orderService.processOrder(orderId, dto, currentUser.getId());
        
        // 5. 记录处理日志
        log.info("订单处理完成:订单ID={},操作={},操作人={}", 
                orderId, dto.getAction(), currentUser.getUsername());
        
        return Result.success();
    }
    
    /**
     * 取消订单 - 多角色操作权限
     * 权限要求:order:cancel 接口权限
     * 数据权限:买家取消自己的订单,卖家取消店铺订单
     * 业务规则:已发货订单不能取消,需要申请退款
     */
    @PutMapping("/{orderId}/cancel")
    @RequirePermission("order:cancel")
    public Result<Void> cancelOrder(@PathVariable Long orderId, 
                                   @RequestBody(required = false) OrderCancelDTO dto) {
        
        User currentUser = SecurityContextHolder.getCurrentUser();
        Order order = orderService.findById(orderId);
        
        if (order == null) {
            throw new NotFoundException("订单不存在");
        }
        
        log.info("用户 {} 取消订单:{}", currentUser.getUsername(), orderId);
        
        // 1. 数据级权限检查
        if (!canCancelOrder(currentUser, order)) {
            log.warn("用户 {} 尝试取消无权限的订单:{}", 
                    currentUser.getUsername(), orderId);
            throw new ForbiddenException("无权取消此订单");
        }
        
        // 2. 业务规则检查
        if (!order.canCancel()) {
            throw new BusinessException("订单当前状态不允许取消");
        }
        
        // 3. 执行取消操作
        String cancelReason = dto != null ? dto.getReason() : "用户主动取消";
        orderService.cancelOrder(orderId, currentUser.getId(), cancelReason);
        
        // 4. 记录取消日志
        log.info("订单取消成功:订单ID={},操作人={},原因={}", 
                orderId, currentUser.getUsername(), cancelReason);
        
        return Result.success();
    }
    
    /**
     * 订单列表查询 - 角色数据权限控制
     * 权限要求:order:list 接口权限
     * 数据范围:买家看自己的订单,卖家看店铺订单,管理员看所有订单
     * 功能:支持分页、搜索、状态筛选
     */
    @GetMapping("/list")
    @RequirePermission("order:list")
    public Result<PageResult<OrderListVO>> listOrders(
            @RequestParam(defaultValue = "1") int page,
            @RequestParam(defaultValue = "10") int size,
            @RequestParam(required = false) String status,
            @RequestParam(required = false) String keyword) {
        
        User currentUser = SecurityContextHolder.getCurrentUser();
        
        // 根据用户角色构建查询条件
        OrderQueryDTO queryDTO = buildOrderQuery(currentUser, status, keyword);
        
        PageResult<Order> orders = orderService.findOrders(queryDTO, page, size);
        PageResult<OrderListVO> orderVOs = convertToOrderVOList(orders, currentUser);
        
        return Result.success(orderVOs);
    }
    
    /**
     * 订单退款申请 - 买家功能
     * 权限要求:order:refund 接口权限 + 买家角色
     * 业务规则:只能申请退款自己的订单,且符合退款条件
     */
    @PostMapping("/{orderId}/refund")
    @RequirePermission("order:refund")
    public Result<Void> applyRefund(@PathVariable Long orderId, 
                                  @Valid @RequestBody RefundApplicationDTO dto) {
        
        User currentUser = SecurityContextHolder.getCurrentUser();
        Order order = orderService.findById(orderId);
        
        // 1. 基础验证
        if (order == null) {
            throw new NotFoundException("订单不存在");
        }
        
        // 2. 权限检查:只能为自己的订单申请退款
        if (!hasRole(currentUser, "BUYER") || !order.getBuyerId().equals(currentUser.getId())) {
            throw new ForbiddenException("无权为此订单申请退款");
        }
        
        // 3. 业务规则检查
        if (!order.canApplyRefund()) {
            throw new BusinessException("订单当前状态不支持退款申请");
        }
        
        // 4. 执行退款申请
        orderService.applyRefund(orderId, dto, currentUser.getId());
        
        return Result.success();
    }
    
    // ==================== 私有方法:权限检查和业务逻辑 ====================
    
    /**
     * 数据级权限检查:是否可以查看订单
     * 实现严格的订单数据隔离
     */
    private boolean canViewOrder(User user, Order order) {
        // 管理员和超级管理员可以查看所有订单
        if (hasAnyRole(user, "ADMIN", "SUPER_ADMIN")) {
            return true;
        }
        
        // 买家只能查看自己的订单
        if (hasRole(user, "BUYER") && order.getBuyerId().equals(user.getId())) {
            return true;
        }
        
        // 卖家可以查看自己店铺的订单
        if (hasRole(user, "SELLER") && isOrderSeller(user, order)) {
            return true;
        }
        
        return false;
    }
    
    /**
     * 数据级权限检查:是否可以取消订单
     * 根据角色和订单归属判断取消权限
     */
    private boolean canCancelOrder(User user, Order order) {
        // 买家只能取消自己的订单
        if (hasRole(user, "BUYER") && order.getBuyerId().equals(user.getId())) {
            return true;
        }
        
        // 卖家可以取消自己店铺的订单(如缺货等情况)
        if (hasRole(user, "SELLER") && isOrderSeller(user, order)) {
            return true;
        }
        
        // 管理员可以取消所有订单
        if (hasAnyRole(user, "ADMIN", "SUPER_ADMIN")) {
            return true;
        }
        
        return false;
    }
    
    /**
     * 检查用户是否为订单商品的卖家
     * 通过店铺ID进行验证
     */
    private boolean isOrderSeller(User user, Order order) {
        if (!hasRole(user, "SELLER")) {
            return false;
        }
        
        // 查询用户拥有的店铺
        List<Shop> userShops = shopService.getShopsByOwnerId(user.getId());
        return userShops.stream()
                .anyMatch(shop -> shop.getId().equals(order.getShopId()));
    }
    
    /**
     * 检查用户角色
     */
    private boolean hasRole(User user, String roleCode) {
        return user.getRoles().stream()
                .anyMatch(role -> role.getRoleCode().equals(roleCode));
    }
    
    /**
     * 检查用户是否拥有任一指定角色
     */
    private boolean hasAnyRole(User user, String... roleCodes) {
        Set<String> userRoles = user.getRoles().stream()
                .map(Role::getRoleCode)
                .collect(Collectors.toSet());
        
        return Arrays.stream(roleCodes).anyMatch(userRoles::contains);
    }
    
    /**
     * 根据用户角色构建订单查询条件
     * 实现数据范围权限控制
     */
    private OrderQueryDTO buildOrderQuery(User user, String status, String keyword) {
        OrderQueryDTO queryDTO = new OrderQueryDTO();
        queryDTO.setStatus(status);
        queryDTO.setKeyword(keyword);
        
        if (hasAnyRole(user, "ADMIN", "SUPER_ADMIN")) {
            // 管理员可以查看所有订单
            queryDTO.setScope(OrderQueryScope.ALL);
        } else if (hasRole(user, "SELLER")) {
            // 卖家只能查看自己店铺的订单
            List<Long> shopIds = shopService.getShopIdsByOwnerId(user.getId());
            queryDTO.setShopIds(shopIds);
            queryDTO.setScope(OrderQueryScope.SHOP);
        } else if (hasRole(user, "BUYER")) {
            // 买家只能查看自己的订单
            queryDTO.setBuyerId(user.getId());
            queryDTO.setScope(OrderQueryScope.USER);
        }
        
        return queryDTO;
    }
    
    /**
     * 构建订单详情VO,根据用户角色过滤敏感信息
     */
    private OrderDetailVO buildOrderVO(User user, Order order) {
        OrderDetailVO vo = new OrderDetailVO();
        // 基础信息
        BeanUtils.copyProperties(order, vo);
        
        // 根据角色决定是否显示敏感信息
        if (hasAnyRole(user, "ADMIN", "SUPER_ADMIN")) {
            // 管理员可以看到所有信息
            vo.setSellerProfit(order.getSellerProfit());
            vo.setPlatformFee(order.getPlatformFee());
        } else if (hasRole(user, "SELLER")) {
            // 卖家可以看到成本信息
            vo.setSellerProfit(order.getSellerProfit());
        }
        // 买家只能看到基本信息,成本信息被过滤
        
        return vo;
    }
    
    /**
     * 验证订单创建的业务规则
     */
    private void validateOrderCreation(OrderCreateDTO dto) {
        // 1. 检查商品状态
        for (OrderItemDTO item : dto.getItems()) {
            Product product = productService.findById(item.getProductId());
            if (product == null || !product.isOnSale()) {
                throw new BusinessException("商品不存在或已下架:" + item.getProductId());
            }
            
            if (product.getStock() < item.getQuantity()) {
                throw new BusinessException("商品库存不足:" + product.getName());
            }
            
            // 价格验证(防止前端篡改价格)
            if (!product.getPrice().equals(item.getPrice())) {
                log.warn("订单价格异常,商品:{},正确价格:{},提交价格:{}", 
                        product.getName(), product.getPrice(), item.getPrice());
                throw new BusinessException("商品价格异常,请重新下单");
            }
        }
        
        // 2. 检查收货地址
        Address address = addressService.findById(dto.getAddressId());
        if (address == null || !address.getUserId().equals(
                SecurityContextHolder.getCurrentUser().getId())) {
            throw new BusinessException("收货地址无效");
        }
    }
}

🏢 案例2:多租户SaaS系统ABAC权限设计

这个案例解决什么问题?

  • 多租户数据隔离和权限控制
  • 基于属性的动态权限决策
  • 复杂的组织结构权限管理
  • 高安全要求的SaaS系统设计
🎯 多租户权限挑战
挑战描述解决方案
数据隔离不同租户数据严格隔离租户ID + 数据库/表级隔离
权限继承组织结构权限继承层级权限计算算法
动态权限根据用户、资源、环境动态判断ABAC属性引擎
性能要求高并发下的权限验证性能缓存 + 并行计算优化
需求分析
  • 租户隔离:不同租户数据严格隔离
  • 角色层级:租户管理员、项目管理员、普通用户
  • 资源权限:项目级、部门级资源权限控制
权限模型设计
-- 租户表
CREATE TABLE `tenants` (
    `id` bigint(20) NOT NULL AUTO_INCREMENT,
    `tenant_name` varchar(100) NOT NULL,
    `tenant_code` varchar(50) NOT NULL,
    `status` tinyint(1) DEFAULT 1,
    `create_time` datetime DEFAULT CURRENT_TIMESTAMP,
    PRIMARY KEY (`id`),
    UNIQUE KEY `tenant_code` (`tenant_code`)
);

-- 租户用户关联表
CREATE TABLE `tenant_users` (
    `id` bigint(20) NOT NULL AUTO_INCREMENT,
    `tenant_id` bigint(20) NOT NULL,
    `user_id` bigint(20) NOT NULL,
    `join_time` datetime DEFAULT CURRENT_TIMESTAMP,
    PRIMARY KEY (`id`),
    UNIQUE KEY `tenant_user` (`tenant_id`, `user_id`),
    FOREIGN KEY (`tenant_id`) REFERENCES `tenants`(`id`),
    FOREIGN KEY (`user_id`) REFERENCES `users`(`id`)
);

-- 租户角色表(每个租户独立的一套角色)
CREATE TABLE `tenant_roles` (
    `id` bigint(20) NOT NULL AUTO_INCREMENT,
    `tenant_id` bigint(20) NOT NULL,
    `role_name` varchar(50) NOT NULL,
    `role_code` varchar(50) NOT NULL,
    `description` varchar(200),
    PRIMARY KEY (`id`),
    UNIQUE KEY `tenant_role_code` (`tenant_id`, `role_code`),
    FOREIGN KEY (`tenant_id`) REFERENCES `tenants`(`id`)
);

-- 租户用户角色关联表
CREATE TABLE `tenant_user_roles` (
    `id` bigint(20) NOT NULL AUTO_INCREMENT,
    `tenant_id` bigint(20) NOT NULL,
    `user_id` bigint(20) NOT NULL,
    `role_id` bigint(20) NOT NULL,
    PRIMARY KEY (`id`),
    UNIQUE KEY `tenant_user_role` (`tenant_id`, `user_id`, `role_id`),
    FOREIGN KEY (`tenant_id`) REFERENCES `tenants`(`id`),
    FOREIGN KEY (`user_id`) REFERENCES `users`(`id`),
    FOREIGN KEY (`role_id`) REFERENCES `tenant_roles`(`id`)
);
多租户权限服务
/**
 * 多租户权限服务
 */
@Service
public class MultiTenantPermissionService {
    
    @Autowired
    private TenantMapper tenantMapper;
    @Autowired
    private TenantRoleMapper tenantRoleMapper;
    @Autowired
    private TenantUserMapper tenantUserMapper;
    
    /**
     * 检查用户是否属于指定租户
     */
    public boolean belongsToTenant(Long userId, Long tenantId) {
        return tenantUserMapper.existsByUserIdAndTenantId(userId, tenantId);
    }
    
    /**
     * 获取用户在指定租户中的角色
     */
    public List<TenantRole> getUserRolesInTenant(Long userId, Long tenantId) {
        return tenantRoleMapper.findByUserIdAndTenantId(userId, tenantId);
    }
    
    /**
     * 检查用户在租户中是否有指定权限
     */
    public boolean hasPermissionInTenant(Long userId, Long tenantId, String permissionCode) {
        if (!belongsToTenant(userId, tenantId)) {
            return false;
        }
        
        List<TenantRole> roles = getUserRolesInTenant(userId, tenantId);
        return roles.stream()
                .flatMap(role -> role.getPermissions().stream())
                .anyMatch(perm -> perm.getPermCode().equals(permissionCode));
    }
    
    /**
     * 检查用户是否有权限访问指定资源
     * 包含租户隔离和资源所有权检查
     */
    public boolean canAccessResource(Long userId, Long tenantId, 
                                   String resourceType, Long resourceId, 
                                   String action) {
        // 1. 检查租户权限
        if (!belongsToTenant(userId, tenantId)) {
            return false;
        }
        
        // 2. 检查操作权限
        String permissionCode = resourceType + ":" + action;
        if (!hasPermissionInTenant(userId, tenantId, permissionCode)) {
            return false;
        }
        
        // 3. 检查资源所有权(如果不是管理员)
        if (!isTenantAdmin(userId, tenantId)) {
            return ownsResource(userId, resourceType, resourceId);
        }
        
        return true;
    }
    
    /**
     * 检查用户是否为租户管理员
     */
    private boolean isTenantAdmin(Long userId, Long tenantId) {
        List<TenantRole> roles = getUserRolesInTenant(userId, tenantId);
        return roles.stream()
                .anyMatch(role -> role.getRoleCode().equals("TENANT_ADMIN"));
    }
    
    /**
     * 检查用户是否拥有资源
     */
    private boolean ownsResource(Long userId, String resourceType, Long resourceId) {
        // 根据资源类型检查所有权
        switch (resourceType) {
            case "project":
                return projectService.isProjectOwner(userId, resourceId);
            case "document":
                return documentService.isDocumentOwner(userId, resourceId);
            default:
                return false;
        }
    }
}
多租户权限切面
/**
 * 多租户权限切面
 */
@Aspect
@Component
public class MultiTenantPermissionAspect {
    
    @Autowired
    private MultiTenantPermissionService permissionService;
    
    @Around("@annotation(tenantPermission)")
    public Object checkPermission(ProceedingJoinPoint joinPoint, 
                                 TenantPermission tenantPermission) throws Throwable {
        
        // 1. 获取当前用户
        User currentUser = SecurityContextHolder.getCurrentUser();
        Long userId = currentUser.getId();
        
        // 2. 获取租户ID(从参数或当前上下文获取)
        Long tenantId = extractTenantId(joinPoint);
        if (tenantId == null) {
            throw new BadRequestException("租户ID不能为空");
        }
        
        // 3. 获取资源信息
        String resourceType = tenantPermission.resourceType();
        Long resourceId = extractResourceId(joinPoint);
        String action = tenantPermission.action();
        
        // 4. 权限验证
        boolean hasPermission = permissionService.canAccessResource(
                userId, tenantId, resourceType, resourceId, action);
        
        if (!hasPermission) {
            throw new ForbiddenException("无权访问该资源");
        }
        
        // 5. 设置租户上下文
        TenantContext.setCurrentTenant(tenantId);
        
        try {
            return joinPoint.proceed();
        } finally {
            TenantContext.clear();
        }
    }
    
    private Long extractTenantId(ProceedingJoinPoint joinPoint) {
        // 从方法参数中提取租户ID
        Object[] args = joinPoint.getArgs();
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        String[] paramNames = signature.getParameterNames();
        
        for (int i = 0; i < paramNames.length; i++) {
            if ("tenantId".equals(paramNames[i]) && args[i] instanceof Long) {
                return (Long) args[i];
            }
        }
        
        // 从当前上下文获取
        return TenantContext.getCurrentTenant();
    }
    
    private Long extractResourceId(ProceedingJoinPoint joinPoint) {
        // 从方法参数中提取资源ID
        Object[] args = joinPoint.getArgs();
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        String[] paramNames = signature.getParameterNames();
        
        for (int i = 0; i < paramNames.length; i++) {
            if (("id".equals(paramNames[i]) || paramNames[i].endsWith("Id")) 
                    && args[i] instanceof Long) {
                return (Long) args[i];
            }
        }
        
        return null;
    }
}

五、安全漏洞防护(安全篇)

🚫 1. 水平越权漏洞

漏洞示例
// ❌ 错误示例:存在水平越权漏洞
@GetMapping("/orders/{orderId}")
public Result<Order> getOrder(@PathVariable Long orderId) {
    // 没有验证订单所属用户,任何用户都能查看任意订单
    Order order = orderService.findById(orderId);
    return Result.success(order);
}
修复方案
// ✅ 正确示例:添加数据权限检查
@GetMapping("/orders/{orderId}")
public Result<Order> getOrder(@PathVariable Long orderId) {
    User currentUser = SecurityContextHolder.getCurrentUser();
    Order order = orderService.findById(orderId);
    
    // 检查订单是否属于当前用户
    if (!order.getBuyerId().equals(currentUser.getId())) {
        throw new ForbiddenException("无权查看此订单");
    }
    
    return Result.success(order);
}

⬆️ 2. 垂直越权漏洞

漏洞示例
// ❌ 错误示例:前端隐藏不如后端控制
// 前端代码
{isAdmin && <button onClick={deleteUser}>删除用户</button>}

// 后端代码 - 缺少权限检查
app.delete('/api/users/:id', (req, res) => {
    // 没有检查用户是否为管理员
    userService.deleteUser(req.params.id);
    res.json({ success: true });
});
修复方案
// ✅ 正确示例:后端严格权限控制
app.delete('/api/users/:id', authMiddleware, (req, res) => {
    // 检查用户是否为管理员
    if (!req.user.roles.includes('admin')) {
        return res.status(403).json({ message: '权限不足' });
    }
    
    // 防止删除自己
    if (req.user.id === parseInt(req.params.id)) {
        return res.status(400).json({ message: '不能删除自己' });
    }
    
    userService.deleteUser(req.params.id);
    res.json({ success: true });
});

🧠 3. 业务逻辑越权

漏洞示例
// ❌ 错误示例:业务逻辑存在漏洞
@PostMapping("/orders/{orderId}/refund")
public Result<Void> refundOrder(@PathVariable Long orderId) {
    Order order = orderService.findById(orderId);
    
    // 只检查了订单状态,没有检查用户身份和时间限制
    if (order.getStatus() != OrderStatus.COMPLETED) {
        throw new BadRequestException("订单状态不允许退款");
    }
    
    orderService.refundOrder(orderId);
    return Result.success();
}
修复方案
// ✅ 正确示例:完整的业务逻辑检查
@PostMapping("/orders/{orderId}/refund")
@RequirePermission("order:refund")
public Result<Void> refundOrder(@PathVariable Long orderId) {
    User currentUser = SecurityContextHolder.getCurrentUser();
    Order order = orderService.findById(orderId);
    
    // 1. 检查订单所属
    if (!order.getBuyerId().equals(currentUser.getId())) {
        throw new ForbiddenException("只能退款自己的订单");
    }
    
    // 2. 检查订单状态
    if (!order.canRefund()) {
        throw new BadRequestException("订单状态不允许退款");
    }
    
    // 3. 检查退款时间限制
    if (order.isRefundExpired()) {
        throw new BadRequestException("已超过退款时限");
    }
    
    // 4. 检查是否已经退款过
    if (order.hasRefunded()) {
        throw new BadRequestException("订单已经退款");
    }
    
    orderService.refundOrder(orderId, currentUser.getId());
    return Result.success();
}

🔗 4. IDOR(不安全的直接对象引用)

漏洞示例
// ❌ 错误示例:直接使用用户输入的ID
@GetMapping("/documents/{docId}")
public Result<Document> getDocument(@PathVariable Long docId) {
    // 直接使用用户提供的ID,没有验证访问权限
    Document doc = documentService.findById(docId);
    return Result.success(doc);
}
修复方案
// ✅ 正确示例:使用映射表或权限验证
@GetMapping("/documents/{docId}")
public Result<Document> getDocument(@PathVariable Long docId) {
    User currentUser = SecurityContextHolder.getCurrentUser();
    
    // 方案1:验证文档所有权
    Document doc = documentService.findById(docId);
    if (!doc.getOwnerId().equals(currentUser.getId())) {
        throw new ForbiddenException("无权访问此文档");
    }
    
    // 方案2:使用UUID而非自增ID
    // Document doc = documentService.findByUuid(docUuid);
    
    return Result.success(doc);
}

📊 权限系统性能优化

🚀 1. 权限系统性能优化

/**
 * 权限缓存服务
 */
@Service
public class PermissionCacheService {
    
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    @Autowired
    private PermissionService permissionService;
    
    private static final String USER_PERMISSIONS_KEY = "user:permissions:";
    private static final long CACHE_EXPIRE_HOURS = 24;
    
    /**
     * 获取用户权限(带缓存)
     */
    @Cacheable(value = "userPermissions", key = "#userId")
    public Set<String> getUserPermissions(Long userId) {
        String cacheKey = USER_PERMISSIONS_KEY + userId;
        Set<String> permissions = (Set<String>) redisTemplate.opsForValue().get(cacheKey);
        
        if (permissions == null) {
            permissions = permissionService.getUserPermissions(userId);
            redisTemplate.opsForValue().set(cacheKey, permissions, 
                    CACHE_EXPIRE_HOURS, TimeUnit.HOURS);
        }
        
        return permissions;
    }
    
    /**
     * 清除用户权限缓存
     */
    public void evictUserPermissionCache(Long userId) {
        String cacheKey = USER_PERMISSIONS_KEY + userId;
        redisTemplate.delete(cacheKey);
    }
    
    /**
     * 批量清除权限缓存(当角色权限发生变化时)
     */
    public void evictRolePermissionCache(Long roleId) {
        // 获取拥有该角色的所有用户
        List<Long> userIds = permissionService.getUsersByRole(roleId);
        
        // 批量清除缓存
        List<String> keys = userIds.stream()
                .map(userId -> USER_PERMISSIONS_KEY + userId)
                .collect(Collectors.toList());
        
        redisTemplate.delete(keys);
    }
}

🗃️ 2. 数据库查询优化

-- 优化权限查询,使用JOIN减少查询次数
-- 查询用户权限的优化SQL
SELECT DISTINCT p.perm_code 
FROM users u
JOIN user_roles ur ON u.id = ur.user_id
JOIN roles r ON ur.role_id = r.id
JOIN role_permissions rp ON r.id = rp.role_id
JOIN permissions p ON rp.perm_id = p.id
WHERE u.id = ? AND u.status = 1 AND r.status = 1 AND p.status = 1;

-- 添加合适的索引
CREATE INDEX idx_user_roles_user_id ON user_roles(user_id);
CREATE INDEX idx_role_permissions_role_id ON role_permissions(role_id);
CREATE INDEX idx_permissions_status ON permissions(status);
CREATE INDEX idx_users_status ON users(status);

🔧 权限系统监控与审计

📝 1. 权限系统监控与审计

/**
 * 权限访问日志服务
 */
@Service
public class PermissionAuditService {
    
    @Autowired
    private AuditLogMapper auditLogMapper;
    
    /**
     * 记录权限访问日志
     */
    public void logPermissionAccess(AccessLog accessLog) {
        AuditLog log = new AuditLog();
        log.setUserId(accessLog.getUserId());
        log.setUserName(accessLog.getUserName());
        log.setResourceType(accessLog.getResourceType());
        log.setResourceId(accessLog.getResourceId());
        log.setAction(accessLog.getAction());
        log.setResult(accessLog.getResult());
        log.setIp(accessLog.getIp());
        log.setUserAgent(accessLog.getUserAgent());
        log.setTimestamp(LocalDateTime.now());
        log.setDescription(accessLog.getDescription());
        
        auditLogMapper.insert(log);
    }
    
    /**
     * 分析权限访问异常
     */
    public List<SecurityRisk> analyzeSecurityRisks(LocalDateTime startTime, 
                                                   LocalDateTime endTime) {
        return auditLogMapper.findSecurityRisks(startTime, endTime);
    }
}

📢 2. 权限变更通知机制

/**
 * 权限变更通知服务
 */
@Service
public class PermissionNotificationService {
    
    @Autowired
    private NotificationService notificationService;
    
    /**
     * 通知管理员权限变更
     */
    @EventListener
    public void handlePermissionChange(PermissionChangeEvent event) {
        // 获取所有管理员
        List<User> admins = userService.getAdmins();
        
        // 构建通知消息
        String message = String.format(
                "用户 %s 的权限发生变更:%s - %s",
                event.getUserName(),
                event.getOldPermissions(),
                event.getNewPermissions()
        );
        
        // 发送通知
        for (User admin : admins) {
            notificationService.sendNotification(admin.getId(), 
                    "权限变更通知", message);
        }
    }
}

🎯 选择合适的权限方案

方案适用场景复杂度性能安全性
拦截器模式简单应用、权限固定
RBAC模型大部分业务系统
ABAC模型复杂权限场景、动态权限极高

记住,权限安全是一个持续的过程,需要根据业务发展不断优化和完善权限控制机制。


📚 参考资源

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐