关键在于解耦权限规则与业务代码,通过注解声明权限需求,拦截器解析并动态决策校验逻辑,支持SpEL表达式和运行时参数,注重拦截顺序、线程安全及边界测试。

回答这类面试题,关键不是背流程,而是讲清楚设计意图、解耦逻辑和落地细节。重点突出“为什么用注解”“怎么让校验真正动态”“如何避免常见坑”。
注解是为了解耦权限规则和业务代码
硬编码判断(比如每个接口开头写 if (!hasPermission("user:delete")) throw new AccessDeniedException())会导致权限逻辑散落在各处,难维护、易遗漏。用注解把“需要什么权限”这个声明从方法体里抽出来,变成一种元数据——就像贴标签,不参与执行,但能被统一读取和处理。
- @RequirePermission("order:edit") 写在方法上,语义清晰,一眼可知准入门槛
- 后续换权限模型(比如从角色码改成资源+操作组合),只需改拦截器,不用动几十个接口
- 配合 IDE,还能做静态检查或生成权限文档
核心三步:声明 → 解析 → 决策
不是光定义个注解就完事,必须闭环落地:
当用户要求为 Java 代码添加注释、修改注释、审查注释、整理注释格式,或要求按照代码注释规范处理 Java 文件时,使用本技能。本技能用于统一生成和维护 Java 类注释模板、按 Git 对比 release 判断版本号、补充每行代码上方中文块注释、调整注释空行规则,并保持 Java 文件 UTF-8 无 BOM 编码。典型触发场景包括:补充类注释、更新 @version、检查 @author 来源、整理行尾注释、把注释放到代码上一行、检查注释是否符合规范、构建前检查 Java 注释风格。
- 声明层:用 @Target(METHOD) + @Retention(RUNTIME),支持 value() 数组(如 {"user:read", "admin"}),最好预留 expression() 字段为 SpEL 留扩展空间
- 解析层:在 HandlerInterceptor#preHandle 中,通过 handlerMethod.getMethod().getAnnotation(RequirePermission.class) 拿到注解;再从 SecurityContext 或 ThreadLocal 取当前用户权限集合(注意:必须确保登录已执行,拦截器顺序不能错)
- 决策层:比对策略要明确——是“至少一个满足”还是“全部满足”?是否区分租户/组织?校验失败直接 return false 并设响应状态码,别抛异常(避免被全局异常处理器吞掉)
动态性体现在参数可插拔,不是写死字符串
真实场景里权限常依赖运行时数据,比如“只能编辑自己创建的订单”。这时注解值就不能是固定字符串:
立即学习“Java免费学习笔记(深入)”;
- 支持 SpEL:@RequirePermission(expression = "#order.userId == principal.userId or hasRole('ADMIN')")
- 或手动提取参数:从 handlerMethod.getMethod().getParameters() 和 request.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE) 拿路径变量、请求体字段
- 关键点:表达式解析要用 Spring 的 StandardEvaluationContext,注入 root 对象(如 principal、order),否则 SpEL 会报找不到变量
注册与避坑要点
很多候选人只讲注解和拦截器,忽略工程化细节:
- 拦截路径设为 /api/**,但必须 excludePathPatterns("/login", "/health", "/static/**") —— 否则登录接口自己都进不去
- 如果项目已用 Spring Security,这个拦截器应作为补充(比如校验按钮级权限),而不是替代其认证主干
- 注意线程安全:ThreadLocal 存用户信息时,记得在 finally 块 clear(),防止 Tomcat 线程复用导致脏数据
- 测试时别只测“有权限”,一定要覆盖“无权限返回 403”和“未登录跳转/提示”的边界情况

















