【Spring Boot & Node.js】接口权限校验的 3 种实现方式(从基础到进阶,避免越权漏洞)
·
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四大核心组件
- Subject(主体):访问者的属性(用户ID、角色、部门、安全级别等)
- Resource(资源):被访问对象的属性(数据敏感度、所属部门、访问频率等)
- Action(动作):执行的操作(读、写、删除、审批等)
- 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模型 | 复杂权限场景、动态权限 | 高 | 中 | 极高 |
记住,权限安全是一个持续的过程,需要根据业务发展不断优化和完善权限控制机制。
📚 参考资源
更多推荐


所有评论(0)