本文目录导读:

在Java中实现权限管理,通常有基于角色(RBAC)、基于资源(URL/方法)和基于属性(ABAC)几种模式,最经典且应用最广泛的是RBAC (Role-Based Access Control)。
下面我会从核心组件、数据库设计、代码实现(结合Spring Security)和核心判断逻辑四个部分,给出一个可落地的案例方案。
核心思想与组件
- 用户 (User):系统的操作者。
- 角色 (Role):权限的集合(如:管理员、普通用户)。
- 权限 (Permission):具体的操作能力(如:用户创建、订单查询、删除文章)。
- 资源/接口:被保护的对象(如:
/api/order/delete)。
判断逻辑: 用户 → 拥有 → 角色 → 拥有 → 权限 → 允许访问 → 资源
数据库表设计(五张核心表)
这是RBAC的经典设计,支持多对多关系。
-- 1. 用户表
CREATE TABLE sys_user (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(50) NOT NULL UNIQUE,
password VARCHAR(100) NOT NULL,
status INT DEFAULT 1 -- 1正常 0禁用
);
-- 2. 角色表
CREATE TABLE sys_role (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
role_name VARCHAR(50) NOT NULL, -- 如: ADMINISTRATOR
role_desc VARCHAR(100)
);
-- 3. 权限表(菜单/按钮/接口)
CREATE TABLE sys_permission (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
perms VARCHAR(100) NOT NULL, -- 权限标识符,如: user:create, user:delete
name VARCHAR(50), -- 权限名称
url VARCHAR(200), -- 对应资源路径(可选)
type INT -- 1菜单 2按钮 3接口
);
-- 4. 用户-角色 关联表
CREATE TABLE sys_user_role (
user_id BIGINT NOT NULL,
role_id BIGINT NOT NULL,
PRIMARY KEY (user_id, role_id)
);
-- 5. 角色-权限 关联表
CREATE TABLE sys_role_permission (
role_id BIGINT NOT NULL,
permission_id BIGINT NOT NULL,
PRIMARY KEY (role_id, permission_id)
);
Java代码实战(基于 Spring Boot + Spring Security)
1 实体对象(简化版)
// 用户实体
@Data
public class SysUser {
private Long id;
private String username;
private String password;
private Integer status;
}
// 角色实体
@Data
public class SysRole {
private Long id;
private String roleName; // ADMIN, USER
}
// 权限实体(核心)
@Data
public class SysPermission {
private Long id;
private String perms; // user:add 或 sys:user:delete
private String url;
}
2 核心权限判断工具类
这个类负责判断「当前用户是否拥有某个权限字符串」。
import org.springframework.stereotype.Component;
@Component("permissionChecker") // 给一个Bean名称,方便在表达式中调用
public class PermissionChecker {
/**
* 判断用户是否有某个权限
* @param permission 权限标识,如 "user:delete"
* @return 是否拥有
*/
public boolean hasPermission(String permission) {
// 1. 获取当前登录用户(通过SecurityContextHolder)
// 2. 从用户信息中提取权限集合
// 3. 检查是否包含该权限
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication != null && authentication.getPrincipal() instanceof UserDetails) {
UserDetails userDetails = (UserDetails) authentication.getPrincipal();
// 假设我们在用户Details中存了权限列表
// 或者从角色中查询
return userDetails.getAuthorities().stream()
.anyMatch(grantedAuthority -> grantedAuthority.getAuthority().equals(permission));
// 更常见的做法是:从redis或数据库中查
// List<String> perms = permissionService.getAllPermsByUserId(userId);
// return perms.contains(permission);
}
return false;
}
}
3 拦截器或注解方式实现权限校验
使用Spring Security的 @PreAuthorize 注解(推荐)
在Controller方法上直接声明需要的权限。
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/user")
public class UserController {
/**
* 删除用户 - 需要 user:delete 权限
*/
@DeleteMapping("/{id}")
@PreAuthorize("hasAuthority('user:delete')") // 直接检查权限字符串
// 或者使用自定义Bean: @PreAuthorize("@permissionChecker.hasPermission('user:delete')")
public Result deleteUser(@PathVariable Long id) {
// 业务逻辑删除
return Result.success();
}
/**
* 创建用户 - 需要 user:create 权限
*/
@PostMapping
@PreAuthorize("hasAuthority('user:create')")
public Result createUser(@RequestBody User user) {
return Result.success();
}
/**
* 查看用户列表 - 登录即可(仅认证,不鉴权)
*/
@GetMapping("/list")
@PreAuthorize("isAuthenticated()")
public Result listUsers() {
return Result.success();
}
}
自定义权限注解
如果需要更复杂的业务逻辑(如:只能删除自己的文章),可以自定义注解。
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface CheckPermission {
String value(); // 权限标识
}
// 在AOP中实现
@Aspect
@Component
public class PermissionAspect {
@Around("@annotation(checkPermission)")
public Object around(ProceedingJoinPoint joinPoint, CheckPermission checkPermission) throws Throwable {
String permission = checkPermission.value();
// 调用上面写的 PermissionChecker 判断
if (!permissionChecker.hasPermission(permission)) {
throw new AccessDeniedException("无权限");
}
return joinPoint.proceed();
}
}
4 实现动态URL拦截(过滤器的方式)
如果不想在每个方法上加注解,也可以配置一个过滤器,根据请求的URL去数据库查询该URL需要哪些权限。
@Component
public class DynamicUrlFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain chain)
throws ServletException, IOException {
String requestUri = request.getRequestURI();
String method = request.getMethod();
// 1. 查数据库,获取当前URL需要的权限 (sys_permission表)
// SELECT perms FROM sys_permission WHERE url = ?
// 或者维护一个 Map<URL, String> 缓存
// 2. 如果不需要权限,直接放行
// 3. 如果需要权限,检查当前登录用户是否拥有该权限
// 从SecurityContextHolder获取用户权限列表
// 判断是否包含
// 4. 有权限:放行;无权限:返回403
}
}
完整的权限判断流程图
graph TD
A[用户请求 /api/user/delete] --> B{是否已登录?}
B -- 否 --> C[返回401]
B -- 是 --> D{是否有角色?}
D -- 是 --> E[查询角色关联的权限集合]
E --> F{是否包含 user:delete ?}
F -- 是 --> G[允许访问]
F -- 否 --> H[返回403 禁止访问]
项目中的最佳实践建议
- 权限粒度:建议使用权限字符串(如
order:update)而非硬编码URL,因为URL可能会变,权限字符串更稳定。 - 缓存:用户每次请求都查数据库很慢,通常的做法是:
- 用户登录成功后,一次性查询该用户所有的权限字符串。
- 存入 Redis 或 Session 中(键:
user:permissions:{userId},值:Set<String>)。 - 后续鉴权时,直接从缓存中取数据判断。
- Spring Security + JWT 整合:
- JWT Token 中只存
userId和username。 - 每次请求在 Filter 中解析 Token,获取
userId。 - 然后从 Redis 中取出该用户的权限列表,构造
UsernamePasswordAuthenticationToken(存入GrantedAuthority列表)。 - Spring Security 会自动根据
hasAuthority()进行匹配。
- JWT Token 中只存
- 树形菜单:如果涉及前端菜单权限,后端返回的权限列表通常是一个树形结构,前端根据
perms字段控制菜单显示/隐藏。
简单示例(权限数据初始化)
// 假设用户 admin (角色: ROLE_ADMIN)
// 角色 ROLE_ADMIN 拥有的权限:
// - user:create
// - user:delete
// - user:update
// - order:view
// 用户 zhangsan (角色: ROLE_USER)
// 角色 ROLE_USER 拥有的权限:
// - order:view
// 当 admin 访问 /api/user/delete -> @PreAuthorize("hasAuthority('user:delete')") -> true -> 成功
// 当 zhangsan 访问 /api/user/delete -> @PreAuthorize("hasAuthority('user:delete')") -> false -> 403
在Java中实现权限管理,最核心的三件事是:
- 设计好数据模型(用户、角色、权限的关联关系)。
- 用户登录后加载权限到Security上下文中。
- 在接口调用点(AOP注解或过滤器)对比用户权限与接口所需权限。
推荐使用 Spring Security + RBAC + 权限字符串 的组合,这是目前Java生态中最成熟、最灵活的方案。