accessdeniedexception 是 spring security 的拒绝访问异常信号,用于解耦权限决策与业务代码,通过 @preauthorize+spel、自定义 permissionevaluator、差异化异常处理及动态 grantedauthority 缓存构建可测试、可配置、可追溯的细粒度权限体系。
accessdeniedexception 是 spring security 提供的标准拒绝访问异常,它本身不负责权限判断,而是作为权限校验失败后的统一信号。构建异常驱动的细粒度权限控制体系,关键在于将权限决策逻辑与业务代码解耦,并让 accessdeniedexception 成为触发拦截、审计、降级或自定义响应的“枢纽点”,而非简单抛出后由全局处理器吞掉。
用 @PreAuthorize + SpEL 实现方法级动态权限表达式
在 Service 方法上标注注解,把权限规则写成可读性强的表达式,避免硬编码 if-check。Spring Security 会在方法执行前自动解析 SpEL,调用 Authentication 和上下文对象完成校验:
- 资源归属校验:@PreAuthorize("#order.userId == authentication.principal.id") —— 确保当前用户是订单所属人
- 角色+属性组合:@PreAuthorize("hasRole('ADMIN') or (#doc.status == 'DRAFT' and #doc.ownerId == authentication.principal.id)")
- 委托调用支持:配合 @EnableGlobalMethodSecurity(prePostEnabled = true, proxyTargetClass = true) 启用代理
自定义 PermissionEvaluator 实现领域模型感知的权限判定
当 SpEL 表达式无法覆盖复杂业务逻辑(如审批链、部门树、租户隔离)时,应实现 PermissionEvaluator 接口,把权限判断下沉到领域层:
- 在 supports() 中识别自定义权限类型(如 "CAN_EDIT_PROJECT")
- 在 hasPermission() 中注入 Repository 或 DomainService,查库/调用领域规则(例如:“用户是否在项目成员列表中且角色非只读”)
- 在 @PreAuthorize 中直接使用:@PreAuthorize("hasPermission(#project, 'CAN_EDIT_PROJECT')")
捕获 AccessDeniedException 做差异化响应与可观测增强
不要只靠 @ControllerAdvice 全局返回 403。应在关键入口(如 API 网关、聚合服务、前端 BFF 层)主动捕获并区分处理:
将 WCAG 2 AA 无障碍标准应用于设计系统、UI 组件和网页——通过可运行的验证器进行对比度计算,采用设计 token 模式使颜色天生可访问,设定排版和焦点状态规则,并提供语义检查清单。适用于构建或审查 UI。
- 前端友好提示:根据异常 cause 或嵌套 exception 类型,返回不同 message(如 “您无权编辑他人草稿” vs “该功能暂未向您的角色开放”)
- 审计日志强化:记录被拒资源 ID、操作类型、用户身份、请求路径、时间戳,便于后续分析越权尝试模式
- 熔断/降级联动:对高频拒绝的接口路径做统计,触发告警或临时限制非核心权限请求频次
结合 GrantedAuthority 动态构造与缓存提升性能
细粒度权限若每次请求都实时计算,容易成为瓶颈。应将权限元数据预加载并结构化:
- 登录时从 DB 或配置中心拉取用户全部有效权限(如 PROJECT_123:EDIT、TENANT_A:MANAGE_USERS),封装为 CustomGrantedAuthority 对象
- 重写 UserDetailsService 的 loadUserByUsername,把权限集合注入 Authentication
- 对高频变更权限(如角色调整),通过 Redis Pub/Sub 通知各节点刷新 Authority 缓存,避免 Session 重启
这套体系不是靠异常本身做控制,而是以 AccessDeniedException 为统一出口,倒逼权限逻辑前置、可配置、可追溯。它让权限成为可测试的领域能力,而不是散落在 if 语句里的魔法字符串。










