本文目录导读:

在Java项目中,权限校验是保障系统安全的核心环节,一个规范的权限校验流程,通常遵循 “认证(Authentication) -> 授权(Authorization) -> 审计(Audit)” 的三角模型。
下面从架构分层、核心流程、规范标准、常见模式及最佳实践五个维度,来拆解如何实现规范的Java权限校验。
规范流程的核心阶段
一个完整的权限校验生命周期通常分为三个阶段:
- 认证(你是谁?):验证用户身份(用户名密码、Token、证书等)。
- 授权(你能做什么?):根据用户的角色/属性,判断其是否有权执行某个操作或访问某个资源。
- 审计(你做了什么?):记录校验的结果(成功/失败)以及操作日志,用于事后追溯。
标准化的校验执行流程(以Web请求为例)
下面是推荐的、基于 Filter -> Interceptor -> AOP 的规范化执行顺序:
请求到达
|
v
【1. 全局过滤器(Filter)】
- 忽略公开路径(/login, /register, /static/*)
- 提取Token/Cookie
- 调用认证服务解析用户身份
- 将用户身份存入ThreadLocal或SecurityContext
|
v
【2. Controller层拦截器(Interceptor)】
- 路径匹配校验(AntPathMatcher)
- 检查方法级注解(@PreAuthorize, @Secured, @RequiresRoles)
- 进行角色/权限判断
|
v
【3. 方法级AOP切面(Aspect)】
- 解析方法参数中的资源ID
- 执行细粒度数据权限校验(如:当前用户只能查看本部门数据)
|
v
【4. 业务Service层】
- 核心业务逻辑
- 内置必要的二次校验(防御性编程)
|
v
【5. 统一异常处理】
- 捕获 AccessDeniedException
- 返回标准化的 403 错误码
规范落地的关键技术点
统一的认证状态管理(SecurityContext)
-
规范做法:使用 ThreadLocal 或 Spring Security 的
SecurityContextHolder存储当前登录用户信息。 -
禁止做法:在Service层方法间通过参数传递用户对象(容易遗漏、暴露用户信息)。
-
代码示例:
// 1. 定义用户上下文对象 public class UserContext { private Long userId; private String username; private Set<String> roles; private Set<String> permissions; } // 2. 使用ThreadLocal管理 public class UserContextHolder { private static final ThreadLocal<UserContext> holder = new ThreadLocal<>(); public static void set(UserContext user) { holder.set(user); } public static UserContext get() { return holder.get(); } public static void clear() { holder.remove(); } }
基于注解的声明式权限控制
这是目前最主流、最规范的方式,将权限逻辑与业务代码解耦。
-
依赖:Spring Security, Apache Shiro, 或自定义AOP注解。
-
规范示例:
@RestController @RequestMapping("/api/orders") public class OrderController { // 1. 角色校验 @PreAuthorize("hasRole('ADMIN')") @GetMapping("/all") public Result getAllOrders() { ... } // 2. 权限字符串校验 @PreAuthorize("hasPermission('order:create')") @PostMapping public Result createOrder(@Valid @RequestBody Order order) { ... } // 3. 复杂表达式校验(结合Spring EL) @PreAuthorize("#order.userId == authentication.principal.userId or hasRole('ADMIN')") @PutMapping("/{id}") public Result updateOrder(@PathVariable Long id, @RequestBody Order order) { ... } // 4. 自定义数据权限注解 @DataScope(tableAlias = "o", field = "dept_id") @GetMapping("/list") public Result listOrders(PageQuery query) { // 该注解会自动拼接SQL: WHERE o.dept_id IN (用户可见部门列表) return service.page(query); } }
细粒度数据权限控制
这是很多项目容易忽略的地方,除了“能不能访问/order/list”这个API,还要控制“能看到哪些订单”。
- 规范方案:
- 行级权限:通过MyBatis-Plus的拦截器或JPA的Filter,自动在SQL末尾拼接
WHERE created_by = 当前用户ID或WHERE dept_id IN (子部门列表)。 - 字段级权限:通过AOP切面,在返回结果前,将用户无权查看的字段置空(如:手机号、身份证号)。
- 行级权限:通过MyBatis-Plus的拦截器或JPA的Filter,自动在SQL末尾拼接
动态权限加载
不建议将权限写死在代码或配置文件中。
- 规范方案:
- 用户登录成功后,从数据库加载该用户的所有角色和权限点。
- 存入Redis,设置过期时间。
- Spring Security的
AccessDecisionManager或自定义PermissionEvaluator从Redis中获取权限进行比对。
规范级别 & 常见模式对比
| 规范级别 | 校验方式 | 典型技术 | 适用场景 | 优缺点 |
|---|---|---|---|---|
| L0 - 无校验 | 靠自觉 | 无 | 快速原型、内部工具 | 极不安全,不推荐 |
| L1 - 硬编码校验 | 代码内 if-else | if(user.isAdmin()) |
小型项目、模块内 | 耦合度高,难以维护 |
| L2 - 过滤器/拦截器 | URL路径拦截 | Filter, HandlerInterceptor |
通用权限(如:登录认证、角色校验) | 无法控制API内部或数据级别 |
| L3 - 注解+表达式 | 方法级别声明 | @PreAuthorize, @Secured, Apache Shiro注解 |
企业级标准方案 | 解耦、可读性强、易于单元测试 |
| L4 - 动态数据权限 | SQL拼接/结果过滤 | MyBatis Plus DataPermissionInterceptor, JPA Filter | 多租户、SaaS系统、数据隔离要求高的场景 | 最灵活,但实现复杂度最高 |
推荐标准:至少达到 L3 级别,敏感系统需达到 L4 级别。
规范落地的最佳实践
-
权限模型标准化:使用 RBAC(基于角色的权限控制)模型,不要直接将权限赋给用户,而是给角色。
- 用户 -> 角色(多对多)
- 角色 -> 权限(多对多)
- 权限点通常是
资源:操作的格式,如order:create,user:update,report:export。
-
拒绝默认开放:采用 白名单 机制。
- 默认所有接口都需要登录和授权。
- 只有明确配置了
permitAll()或ignoring()的路径才允许匿名访问。
-
统一异常处理:捕获
AccessDeniedException(Spring Security)或抛出自定义PermissionDeniedException,返回统一的JSON结构(如{code: 403, message: "无权访问"})。 -
权限缓存与及时失效:
- 用户权限缓存到Redis,设置TTL(建议1-2小时)。
- 当管理员修改了用户角色或权限时,需要手动触发清除该用户缓存的操作(如:Redis删除对应key,或维护一个权限版本号)。
-
写一份清晰的权限设计文档:
- 定义好所有的权限点(
xxx:yyy)。 - 定义好默认角色及其权限集(如:普通员工、部门主管、系统管理员)。
- 定义好数据权限的规则(如:只能看自己、只能看本部门、可看全部)。
- 定义好所有的权限点(
一个简单的校验流程时序图(文字版)
用户 Web层(Filter/Interceptor) 业务层(Service/AOP)
| | |
|--- 1.请求接口 ------------------>| |
| |--- 2.从Redis/Header获取Token->|
| |<-- 3.返回用户信息 ------------|
| |--- 4.判断URL/方法是否需要权限 |
| |--- 5.比对用户权限(hasPermission?)|
| | (失败 -> 返回403) |
| | |
| |--- 6.放行(或触发AOP数据权限)-->|
| | |--- 7.执行业务逻辑(注入数据权限)
| |<-- 8.返回业务结果 -----------|
|<-- 9.返回数据或标准错误码 -------| |
规范的Java权限校验流程应具备:
- 清晰的分层:Filter处理认证,Interceptor/AOP处理授权。
- 统一的上下文:使用 ThreadLocal 或 SecurityContext 传递用户信息。
- 声明式控制:优先使用
@PreAuthorize或自定义注解,避免业务代码内充斥 if 判断。 - 细粒度数据隔离:通过拦截器动态处理SQL或返回结果。
- 完善的审计日志:记录谁在什么时间操作了什么资源,结果如何。
坚持以上原则,可以让你的权限系统既安全又易于维护。