You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.08 11:27:45