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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.29 23:26:12