Java权限校验流程如何规范

wen java案例 27

本文目录导读:

Java权限校验流程如何规范

  1. 规范流程的核心阶段
  2. 标准化的校验执行流程(以Web请求为例)
  3. 规范落地的关键技术点
  4. 规范级别 & 常见模式对比
  5. 规范落地的最佳实践
  6. 一个简单的校验流程时序图(文字版)

在Java项目中,权限校验是保障系统安全的核心环节,一个规范的权限校验流程,通常遵循 “认证(Authentication) -> 授权(Authorization) -> 审计(Audit)” 的三角模型。

下面从架构分层、核心流程、规范标准、常见模式及最佳实践五个维度,来拆解如何实现规范的Java权限校验。


规范流程的核心阶段

一个完整的权限校验生命周期通常分为三个阶段:

  1. 认证(你是谁?):验证用户身份(用户名密码、Token、证书等)。
  2. 授权(你能做什么?):根据用户的角色/属性,判断其是否有权执行某个操作或访问某个资源。
  3. 审计(你做了什么?):记录校验的结果(成功/失败)以及操作日志,用于事后追溯。

标准化的校验执行流程(以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 = 当前用户IDWHERE dept_id IN (子部门列表)
    • 字段级权限:通过AOP切面,在返回结果前,将用户无权查看的字段置空(如:手机号、身份证号)。

动态权限加载

不建议将权限写死在代码或配置文件中。

  • 规范方案
    • 用户登录成功后,从数据库加载该用户的所有角色和权限点。
    • 存入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 级别。

规范落地的最佳实践

  1. 权限模型标准化:使用 RBAC(基于角色的权限控制)模型,不要直接将权限赋给用户,而是给角色。

    • 用户 -> 角色(多对多)
    • 角色 -> 权限(多对多)
    • 权限点通常是 资源:操作 的格式,如 order:create, user:update, report:export
  2. 拒绝默认开放:采用 白名单 机制。

    • 默认所有接口都需要登录和授权。
    • 只有明确配置了 permitAll()ignoring() 的路径才允许匿名访问。
  3. 统一异常处理:捕获 AccessDeniedException(Spring Security)或抛出自定义 PermissionDeniedException,返回统一的JSON结构(如 {code: 403, message: "无权访问"})。

  4. 权限缓存与及时失效

    • 用户权限缓存到Redis,设置TTL(建议1-2小时)。
    • 当管理员修改了用户角色或权限时,需要手动触发清除该用户缓存的操作(如:Redis删除对应key,或维护一个权限版本号)。
  5. 写一份清晰的权限设计文档

    • 定义好所有的权限点(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或返回结果。
  • 完善的审计日志:记录谁在什么时间操作了什么资源,结果如何。

坚持以上原则,可以让你的权限系统既安全又易于维护。

抱歉,评论功能暂时关闭!