accessdeniedhandler用于处理已发生的403访问拒绝,而非抛出异常;推荐通过继承accessdeniedexception定义业务异常,在service或@preauthorize中抛出,并在自定义accessdeniedhandler中解析响应。

在 Spring Security 中,AccessDeniedHandler 本身不用于“抛出”或“包装”自定义鉴权异常,而是用于**处理已发生的访问拒绝(403)场景**——即当用户已认证但权限不足时,由 FilterSecurityInterceptor 抛出 AccessDeniedException 后,交由它统一响应。因此,“包装自定义鉴权异常”的核心在于:**让业务逻辑中主动拒绝访问时,也能触发同一套处理流程,且携带你想要的上下文信息(如错误码、提示语、原因标识)**。
1. 自定义 AccessDeniedException 子类(推荐)
Spring Security 的异常处理链天然识别 AccessDeniedException。你可以继承它,添加业务字段,不影响原有机制:
- 定义带扩展字段的异常,例如:
private final String errorCode;
private final Object details;
public BusinessException(String msg, String errorCode) {
super(msg);
this.errorCode = errorCode;
this.details = null;
}
// getter 省略
}
- 在 Service 或 @PreAuthorize 表达式中直接 throw 它(比如权限校验失败时);
- Spring Security 拦截器仍会捕获并交给
AccessDeniedHandler处理,你的自定义字段可被提取使用。
2. 在 AccessDeniedHandler 中解析并响应自定义信息
实现 AccessDeniedHandler,从异常中提取扩展字段,构造结构化响应(如 JSON):
- 重写
handle()方法,判断异常类型:
@Override
public void handle(HttpServletRequest request, HttpServletResponse response,
AccessDeniedException accessDeniedException) throws IOException {
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
response.setContentType("application/json;charset=UTF-8");
String errorMsg = "无权限访问";
String errorCode = "ACCESS_DENIED";
Object details = null;
if (accessDeniedException instanceof BusinessException) {
BusinessException be = (BusinessException) accessDeniedException;
errorMsg = be.getMessage();
errorCode = be.getErrorCode();
details = be.getDetails();
}
Map
"code", errorCode,
"message", errorMsg,
"details", details
);
response.getWriter().write(new ObjectMapper().writeValueAsString(result));
}
}
- 注册该 handler 到
HttpSecurity:
.accessDeniedHandler(new CustomAccessDeniedHandler());
3. 配合 @PreAuthorize / @PostAuthorize 使用(可选增强)
若需在方法级权限表达式中主动触发自定义异常,可用 SpEL 调用工具类:
- 定义一个权限检查工具类:
public class PermissionChecker {
public boolean checkResourcePermission(String resourceId) {
if (!hasPermission(resourceId)) {
throw new BusinessException("资源不可访问", "RESOURCE_FORBIDDEN");
}
return true;
}
}
- 在 Controller 方法上使用:
public ResponseEntity> getResource(@PathVariable String id) { ... }
此时异常仍走 AccessDeniedHandler 流程,保持统一出口。
4. 注意事项
- 不要在
AccessDeniedHandler中再 throw 异常——它已是最终响应环节; - 避免混淆
AuthenticationEntryPoint(处理未认证 401)和AccessDeniedHandler(处理已认证但无权 403); - 若使用全局异常处理器(
@ControllerAdvice),需确保它不拦截AccessDeniedException,否则会绕过AccessDeniedHandler; - 自定义异常务必继承
AccessDeniedException,而非RuntimeException,否则不会被 Security Filter 捕获到。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











