Spring Boot如何拦截REST控制器方法级未授权请求?
解决Spring中拦截未授权请求并返回自定义JSON的方案
这个需求我之前也碰到过,Spring提供了两种实用的方案来实现,咱们一步步来:
方案一:自定义AccessDeniedHandler(Spring Security原生拦截)
这是最直接的方式,直接在Spring Security的权限拦截环节处理未授权请求,能精准针对权限校验失败的场景执行自定义逻辑。
步骤1:实现AccessDeniedHandler接口
创建一个自定义的处理器类,重写handle方法来编写你的自定义逻辑(比如日志记录、告警触发),同时构造JSON响应返回给调用方:
import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.web.access.AccessDeniedHandler; import org.springframework.stereotype.Component; import com.fasterxml.jackson.databind.ObjectMapper; import java.io.IOException; import java.util.HashMap; import java.util.Map; @Component public class CustomAccessDeniedHandler implements AccessDeniedHandler { private final ObjectMapper objectMapper; // 注入Spring默认的ObjectMapper用于序列化JSON public CustomAccessDeniedHandler(ObjectMapper objectMapper) { this.objectMapper = objectMapper; } @Override public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException { // 这里写你的自定义逻辑,比如记录未授权请求的URL和用户信息 System.out.println("拦截到未授权请求:URL=" + request.getRequestURI() + ",用户=" + request.getUserPrincipal()); // 设置响应状态码和内容类型 response.setStatus(HttpServletResponse.SC_FORBIDDEN); response.setContentType("application/json;charset=UTF-8"); // 构造自定义的JSON响应体 Map<String, Object> responseBody = new HashMap<>(); responseBody.put("code", 403); responseBody.put("msg", "您没有权限访问该资源,请联系管理员"); responseBody.put("requestPath", request.getRequestURI()); // 将响应体写入输出流 response.getWriter().write(objectMapper.writeValueAsString(responseBody)); } }
步骤2:配置到SecurityFilterChain
把自定义的处理器配置到Spring Security的安全链中,替换默认的未授权处理逻辑:
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.access.AccessDeniedHandler; @Configuration @EnableWebSecurity public class SecurityConfig { private final CustomAccessDeniedHandler customAccessDeniedHandler; public SecurityConfig(CustomAccessDeniedHandler customAccessDeniedHandler) { this.customAccessDeniedHandler = customAccessDeniedHandler; } @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests(auth -> auth // 配置你的接口权限规则 .requestMatchers("/activate_user").hasRole("ADMIN") .anyRequest().authenticated() ) // 替换默认的未授权处理器 .exceptionHandling(ex -> ex .accessDeniedHandler(customAccessDeniedHandler) ); return http.build(); } }
方案二:全局异常处理器(Spring MVC层面)
如果你的项目需要统一处理所有异常,包括权限校验失败的情况,可以用@ControllerAdvice配合@ExceptionHandler来捕获AccessDeniedException,这种方式更通用。
实现全局异常处理器
创建一个全局异常处理类,专门捕获未授权异常:
import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.access.AccessDeniedException; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.context.request.WebRequest; import java.util.HashMap; import java.util.Map; @ControllerAdvice public class GlobalSecurityExceptionHandler { @ExceptionHandler(AccessDeniedException.class) public ResponseEntity<Object> handleAccessDenied(AccessDeniedException ex, WebRequest request) { // 自定义逻辑,比如记录异常日志 System.err.println("未授权异常:" + ex.getMessage() + ",请求路径:" + request.getDescription(false)); // 构造响应体 Map<String, Object> response = new HashMap<>(); response.put("code", HttpStatus.FORBIDDEN.value()); response.put("message", "权限不足,无法执行该操作"); response.put("details", request.getDescription(false)); return new ResponseEntity<>(response, HttpStatus.FORBIDDEN); } }
两种方案的区别
- 方案一:属于Spring Security层面的处理,直接拦截权限校验失败的请求,不会进入MVC的处理流程,适合只针对权限拦截场景做定制。
- 方案二:属于Spring MVC层面的全局异常处理,能捕获所有地方抛出的
AccessDeniedException,包括方法内部手动抛出的情况,适合统一管理项目中的所有异常响应。
你可以根据自己的需求选择其中一种,两种方式都能完美实现你要的效果。
内容的提问来源于stack exchange,提问作者Elio
相关产品推荐
相关产品推荐

