Spring Boot 3集成Spring Security误拦截415异常返回403问题求助
问题分析
你的问题核心是Spring Security集成后,非认证类异常(如415媒体类型不支持)被错误拦截返回403,根源在于:
- Spring Security的过滤器链执行顺序早于Spring MVC的内容类型校验逻辑,导致部分请求在进入MVC处理前就被Security拦截;
- 默认的Security异常处理未区分「认证失败」和「业务/请求格式异常」,统一返回了403。
解决方案
1. 添加全局异常处理器,确保415异常正常返回
通过@ControllerAdvice定义全局异常处理,专门捕获HttpMediaTypeNotSupportedException并返回415状态码,优先级高于Security的异常处理:
@ControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(HttpMediaTypeNotSupportedException.class) public ResponseEntity<Map<String, String>> handleMediaTypeNotSupported(HttpMediaTypeNotSupportedException ex) { Map<String, String> error = new HashMap<>(); error.put("status", String.valueOf(HttpStatus.UNSUPPORTED_MEDIA_TYPE.value())); error.put("message", "不支持的媒体类型:" + ex.getContentType()); error.put("supportedTypes", ex.getSupportedMediaTypes().toString()); return new ResponseEntity<>(error, HttpStatus.UNSUPPORTED_MEDIA_TYPE); } }
2. 配置Spring Security,仅对认证/权限异常返回403
修改SecurityFilterChain配置,自定义异常处理逻辑,只在未认证或权限不足时返回403,其他异常交给Spring MVC处理:
@Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { return http .cors().and() .csrf().disable() .authorizeHttpRequests(auth -> auth .requestMatchers("/auth/**").permitAll() // 改为/auth/**更规范,匹配所有子路径 .anyRequest().authenticated()) .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class) .userDetailsService(jpaUserDetailsService) // 自定义异常处理 .exceptionHandling(exceptions -> exceptions .authenticationEntryPoint((request, response, authException) -> { // 仅未认证时返回403 response.setContentType(MediaType.APPLICATION_JSON_VALUE); response.setStatus(HttpServletResponse.SC_FORBIDDEN); response.getWriter().write("{\"status\":403,\"message\":\"未授权访问\"}"); }) .accessDeniedHandler((request, response, accessDeniedException) -> { // 权限不足时返回403 response.setContentType(MediaType.APPLICATION_JSON_VALUE); response.setStatus(HttpServletResponse.SC_FORBIDDEN); response.getWriter().write("{\"status\":403,\"message\":\"权限不足\"}"); }) ) .build(); }
3. 优化JwtAuthFilter逻辑
确保token无效时不干扰SecurityContext,让permitAll的接口能正常进入MVC层处理:
@Component @RequiredArgsConstructor public class JwtAuthFilter extends OncePerRequestFilter { private final JwtUtils jwtUtils; private static final String AUTHENTICATION_HEADER = "Authorization"; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String jwtToken = request.getHeader(AUTHENTICATION_HEADER); if (jwtToken != null && jwtToken.startsWith("Bearer ")) { jwtToken = jwtToken.split(" ")[1]; if (jwtUtils.isValidToken(jwtToken)) { UserDetails userDetails = new UserSecurity(jwtUtils.extractUser(jwtToken)); UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken( userDetails, null, userDetails.getAuthorities()); auth.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); SecurityContextHolder.getContext().setAuthentication(auth); } // token无效时不做任何操作,保持SecurityContext为空即可 } filterChain.doFilter(request, response); } }
可选:调整Controller的consumes配置
如果GET请求不需要强制application/json,可以去掉类上的consumes属性,仅对POST等需要接收JSON的方法单独配置:
@RestController @RequestMapping(value = "auth", produces = MediaType.APPLICATION_JSON_VALUE) public class AuthController { @GetMapping public Object test() { Map<String, String> test = new HashMap<>(); test.put("key", "val"); return test; } // 仅POST请求要求Content-Type为application/json @PostMapping(consumes = MediaType.APPLICATION_JSON_VALUE) public Object login(@RequestBody LoginRequest request) { // 登录逻辑 return null; } }
内容的提问来源于stack exchange,提问作者Brandon
相关产品推荐
相关产品推荐

