Spring Boot中自定义用户不存在异常未返回JSON响应问题求助
我正在开发基于Spring Boot的应用,采用Spring Security实现用户认证。自定义了继承UsernameNotFoundException的UserExistException用于处理用户不存在的场景,但完成配置后,用户不存在时并未返回预期的JSON响应,仅在控制台打印堆栈信息。
以下是相关代码:
1. CustomUserDetailsService实现
@AllArgsConstructor @Service public class CustomUserDetailsService implements UserDetailsService { private final UserManagerRepository userManagerRepository; @SneakyThrows @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { UserManger user = userManagerRepository.findUserMangerByUserName(username) .orElseThrow(() -> new UserExistException("User with given username not found: " + username)); Set<GrantedAuthority> authorities = user.getRoles() .stream() .map(role -> new SimpleGrantedAuthority(role.getRoleName())) .collect(Collectors.toSet()); return new User(user.getUserName(), user.getPassword(), authorities); } }
2. 全局异常处理器
@ControllerAdvice public class GlobeControllerAdvice { @ExceptionHandler(UserExistException.class) public ResponseEntity<GlobalJsonResponseBody> generateUserExistException(Exception exception) { GlobalJsonResponseBody globalJsonResponseBody = getGlobalJsonResponseBody(exception); return new ResponseEntity<>(globalJsonResponseBody, HttpStatus.BAD_REQUEST); } }
3. UserExistException定义
public class UserExistException extends UsernameNotFoundException { public UserExistException(String message){ super(message); } }
4. SecurityConfiguration配置
@AllArgsConstructor @EnableWebSecurity @Configuration @EnableMethodSecurity public class SecurityConfiguration extends WebSecurityConfiguration{ private final JWTAuthEntryPoint authEntryPoint; private final JWTAuthFilter jwtAuthenticationFilter; private final CustomUserDetailsService customUserDetailsService; @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Bean public AuthenticationManager authenticationManager(AuthenticationConfiguration configuration) throws Exception { return configuration.getAuthenticationManager(); } @Bean SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception { httpSecurity.csrf(AbstractHttpConfigurer::disable) .authorizeHttpRequests((authorize) -> authorize .requestMatchers(HttpMethod.POST, "/api/v1/auth/**").permitAll()) .httpBasic(Customizer.withDefaults()) .exceptionHandling(exceptionHandlingConfigurer -> exceptionHandlingConfigurer .authenticationEntryPoint(authEntryPoint)) .sessionManagement(session -> session .sessionCreationPolicy(SessionCreationPolicy.STATELESS)); httpSecurity.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); return httpSecurity.build(); } @Bean public AuthenticationProvider daoAuthenticationProvider() { DaoAuthenticationProvider provider = new DaoAuthenticationProvider(); provider.setUserDetailsService(customUserDetailsService); provider.setHideUserNotFoundExceptions(false); return provider; }
1. 异常被Spring Security认证入口点拦截
你配置了自定义JWTAuthEntryPoint,认证过程中抛出的异常会被这个入口点优先处理,而非@ControllerAdvice的全局异常处理器。
解决方法:
修改JWTAuthEntryPoint的commence方法,针对UserExistException返回自定义JSON响应:
@Component public class JWTAuthEntryPoint implements AuthenticationEntryPoint { @Override public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException { if (authException.getCause() instanceof UserExistException) { UserExistException ex = (UserExistException) authException.getCause(); response.setContentType(MediaType.APPLICATION_JSON_VALUE); response.setStatus(HttpStatus.BAD_REQUEST.value()); GlobalJsonResponseBody body = new GlobalJsonResponseBody(HttpStatus.BAD_REQUEST.value(), ex.getMessage()); ObjectMapper mapper = new ObjectMapper(); mapper.writeValue(response.getOutputStream(), body); } else { response.sendError(HttpStatus.UNAUTHORIZED.value(), "Unauthorized"); } } }
2. 全局异常处理器参数类型不匹配
你的@ExceptionHandler方法参数是Exception,Spring匹配异常时会优先选择更具体的类型声明,直接使用UserExistException作为参数能提升匹配优先级与准确性。
解决方法:
调整全局异常处理器方法:
@ControllerAdvice public class GlobeControllerAdvice { @ExceptionHandler(UserExistException.class) public ResponseEntity<GlobalJsonResponseBody> generateUserExistException(UserExistException exception) { GlobalJsonResponseBody globalJsonResponseBody = getGlobalJsonResponseBody(exception); return new ResponseEntity<>(globalJsonResponseBody, HttpStatus.BAD_REQUEST); } }
3. 移除@SneakyThrows注解
@SneakyThrows会将受检异常包装为RuntimeException抛出,导致Spring无法正确识别UserExistException的类型。而loadUserByUsername本身已声明抛出UsernameNotFoundException,无需额外包装。
解决方法:
删除loadUserByUsername方法上的@SneakyThrows注解:
@Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { UserManger user = userManagerRepository.findUserMangerByUserName(username) .orElseThrow(() -> new UserExistException("User with given username not found: " + username)); Set<GrantedAuthority> authorities = user.getRoles() .stream() .map(role -> new SimpleGrantedAuthority(role.getRoleName())) .collect(Collectors.toSet()); return new User(user.getUserName(), user.getPassword(), authorities); }
4. 确保自定义AuthenticationProvider被正确使用
显式配置AuthenticationManager使用你定义的DaoAuthenticationProvider,避免Spring使用默认Provider:
@Bean public AuthenticationManager authenticationManager(AuthenticationConfiguration configuration) throws Exception { AuthenticationManagerBuilder authBuilder = configuration.getAuthenticationManagerBuilder(); authBuilder.authenticationProvider(daoAuthenticationProvider()); return authBuilder.build(); }
内容的提问来源于stack exchange,提问作者Josh Simon

