Spring Boot 3中如何通过SecurityFilterChain控制返回403或404
实现方案与可行性说明
针对Spring Boot 3中匿名用户访问不同端点返回不同状态码的需求,以下是三种可行的实现方向:
方案一:自定义AccessDeniedHandler
利用Spring Security的权限拒绝处理机制,根据请求路径和用户认证状态返回对应状态码
步骤与代码
- 实现自定义权限拒绝处理器:
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, "访问被拒绝"); } }
- 在
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过滤器链中添加自定义过滤器,提前拦截匿名用户的请求并返回对应状态码
步骤与代码
- 实现自定义过滤器(继承
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); } }
- 将过滤器添加到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
相关产品推荐
相关产品推荐

