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

Spring Boot 3中如何通过SecurityFilterChain控制返回403或404

实现方案与可行性说明

针对Spring Boot 3中匿名用户访问不同端点返回不同状态码的需求,以下是三种可行的实现方向:


方案一:自定义AccessDeniedHandler

利用Spring Security的权限拒绝处理机制,根据请求路径和用户认证状态返回对应状态码

步骤与代码

  1. 实现自定义权限拒绝处理器:
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;

import java.io.IOException;

@Component
public class CustomAccessDeniedHandler implements AccessDeniedHandler {

    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException {
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        // 匿名用户访问/secrets系列端点时返回404
        if (auth == null || !auth.isAuthenticated()) {
            String uri = request.getRequestURI();
            if (uri.startsWith("/secrets/") || uri.equals("/secrets")) {
                response.sendError(HttpServletResponse.SC_NOT_FOUND, "资源不存在");
                return;
            }
        }
        // 其他权限拒绝场景返回403
        response.sendError(HttpServletResponse.SC_FORBIDDEN, "访问被拒绝");
    }
}
  1. 在SecurityFilterChain中配置该处理器:
public SecurityFilterChain mvcFilterChain(HttpSecurity http, CustomAccessDeniedHandler accessDeniedHandler) throws Exception {
    return http
            .authorizeHttpRequests(authorize -> authorize
                    .requestMatchers("/", "/error/**").permitAll()
                    .requestMatchers("/profile/**", "/secrets/**").hasAuthority("ROLE_USER")
            )
            .exceptionHandling(exceptions -> exceptions
                    .accessDeniedHandler(accessDeniedHandler)
            )
            // 补充其他配置(如表单登录、退出等)
            .build();
}

方案二:自定义请求过滤器

在Spring Security过滤器链中添加自定义过滤器,提前拦截匿名用户的请求并返回对应状态码

步骤与代码

  1. 实现自定义过滤器(继承OncePerRequestFilter确保单次请求只执行一次):
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;

@Component
public class AnonymousEndpointFilter extends OncePerRequestFilter {

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        String uri = request.getRequestURI();

        // 仅处理匿名用户的请求
        if (auth == null || !auth.isAuthenticated()) {
            if (uri.startsWith("/profile/") || uri.equals("/profile")) {
                response.sendError(HttpServletResponse.SC_FORBIDDEN, "访问被拒绝");
                return;
            }
            if (uri.startsWith("/secrets/") || uri.equals("/secrets")) {
                response.sendError(HttpServletResponse.SC_NOT_FOUND, "资源不存在");
                return;
            }
        }
        // 符合条件的请求继续执行过滤器链
        filterChain.doFilter(request, response);
    }
}
  1. 将过滤器添加到Security链中(需放在AuthorizationFilter之前):
public SecurityFilterChain mvcFilterChain(HttpSecurity http, AnonymousEndpointFilter anonymousEndpointFilter) throws Exception {
    return http
            .addFilterBefore(anonymousEndpointFilter, AuthorizationFilter.class)
            .authorizeHttpRequests(authorize -> authorize
                    .requestMatchers("/", "/error/**").permitAll()
                    .requestMatchers("/profile/**", "/secrets/**").hasAuthority("ROLE_USER")
            )
            // 补充其他配置
            .build();
}

方案三:全局异常处理器(@ControllerAdvice)

利用全局异常捕获机制,拦截@PreAuthorize抛出的AccessDeniedException,根据请求路径返回不同状态码或页面

代码示例

import jakarta.servlet.http.HttpServletRequest;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;

@ControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(AccessDeniedException.class)
    public String handleAccessDenied(AccessDeniedException ex, HttpServletRequest request, Model model) {
        String uri = request.getRequestURI();
        if (uri.startsWith("/secrets/") || uri.equals("/secrets")) {
            model.addAttribute("msg", "资源不存在");
            return "error/404"; // 跳转至自定义404页面
        } else {
            model.addAttribute("msg", "访问被拒绝");
            return "error/403"; // 跳转至自定义403页面
        }
    }
}

可行性说明

以上方案完全适配Spring Boot 3(基于Spring Security 6),核心机制与Spring Boot 2.x一致,仅需注意包名从javax.servlet替换为jakarta.servlet,其余API逻辑无破坏性变化。

内容的提问来源于stack exchange,提问作者Slevin

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.22 00:53:15