Spring security and ExceptionHandler: allowAll path exception always returns 401 on the Tomcat server

I have a problem with my Spring Boot application. I used Spring Security to authenticate and annotate the ControllerAdvice using @ExceptionHandler to handle exceptions. When I call the token protected by api, everything goes well, but when I try to find it in a path that is always allowed (for example, registration or password reset), a problem occurs. If the answer is 200, everything is fine, but when an exception occurs, and I see the exception in the tomcat log, the response is always unauthorized 401, even if @ExceptionHandler should return 403.

And the strangest thing about this behavior is that this only happens on my tomcat online server, and not when I run it on my local machine, when tomcat started using gradle.

this is the configure function in spring security

@Override
protected void configure(HttpSecurity http) throws Exception {
    http
    .csrf().disable() // We don't need CSRF for JWT based authentication
    .exceptionHandling()
    .authenticationEntryPoint(this.authenticationEntryPoint)

    .and()
        .sessionManagement()
        .sessionCreationPolicy(SessionCreationPolicy.STATELESS)

    .and()
        .authorizeRequests()
            .antMatchers(FORM_BASED_LOGIN_ENTRY_POINT).permitAll() // Login end-point
            .antMatchers(TOKEN_REFRESH_ENTRY_POINT).permitAll() // Token refresh end-point
            .antMatchers(FORM_BASED_REGISTRATION_ENTRY_POINT).permitAll() // Sign up end-point
            .antMatchers(PHONE_PREFIXES_ENTRY_POINT).permitAll() // Get international prefixes
            .antMatchers(ACCOUNT_ACTIVATION_ENTRY_POINT).permitAll() // Account activation end-point
            .antMatchers(PASSWORD_RESET_EMAIL_ENTRY_POINT).permitAll() // Password reset email send
            .antMatchers(PASSWORD_RESET_ENTRY_POINT).permitAll() // Password reset
            .antMatchers(TOKEN_BASED_AUTH_ENTRY_POINT).authenticated() // Protected API End-points
    .and()
        .addFilterBefore(buildAjaxLoginProcessingFilter(), UsernamePasswordAuthenticationFilter.class)
        .addFilterBefore(buildJwtTokenAuthenticationProcessingFilter(), UsernamePasswordAuthenticationFilter.class);
}

this is an exception handled in the ControllerAdvice

@ResponseStatus(value=HttpStatus.FORBIDDEN, reason="User already registered")
@ExceptionHandler(UserAlreadyRegisteredException.class)
public void handleUserAlreadyRegisteredException(UserAlreadyRegisteredException exception){}

This exception should be thrown when the user is already registered, but instead he starts 401. Of course, if I set the Authorization header in the request header, it will work.

Does anyone have such a problem? Could this be related to Tomcat configuration?

Thank you in advance.

EDIT

This is a class UserAlreadyRegisteredException

public class UserAlreadyRegisteredException extends Exception {  
   public UserAlreadyRegisteredException(String email){ 
    super("User with email "+email+" already present");    
   } 
}

The error path is FORM_BASED_REGISTRATION_ENTRY_POINT, which is defined in WebSecurityConfig

public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
 public static final String JWT_TOKEN_HEADER_PARAM = "Authorization";
 public static final String FORM_BASED_LOGIN_ENTRY_POINT = UtilsProperties.API_PATH + "/auth/login";
 public static final String TOKEN_BASED_AUTH_ENTRY_POINT = UtilsProperties.API_PATH + "/**";
 public static final String TOKEN_REFRESH_ENTRY_POINT = UtilsProperties.API_PATH + "/auth/token";
 public static final String FORM_BASED_REGISTRATION_ENTRY_POINT = UtilsProperties.API_PATH + "/auth/signup";
 public static final String PHONE_PREFIXES_ENTRY_POINT = UtilsProperties.API_PATH + "/phoneprefixes";
 public static final String ACCOUNT_ACTIVATION_ENTRY_POINT = UtilsProperties.API_PATH + "/auth/*/activate/*";
 public static final String PASSWORD_RESET_EMAIL_ENTRY_POINT = UtilsProperties.API_PATH + "/auth/resetpassword";
 public static final String PASSWORD_RESET_ENTRY_POINT = UtilsProperties.API_PATH + "/auth/*/resetpassword/*";

@Autowired private RestAuthenticationEntryPoint authenticationEntryPoint;
@Autowired private AuthenticationSuccessHandler successHandler;
@Autowired private AuthenticationFailureHandler failureHandler;
@Autowired private AjaxAuthenticationProvider ajaxAuthenticationProvider;
@Autowired private JwtAuthenticationProvider jwtAuthenticationProvider;

@Autowired private TokenExtractor tokenExtractor;

@Autowired private AuthenticationManager authenticationManager;

@Autowired private ObjectMapper objectMapper;

@Bean
protected AjaxLoginProcessingFilter buildAjaxLoginProcessingFilter() throws Exception {
    AjaxLoginProcessingFilter filter = new AjaxLoginProcessingFilter(FORM_BASED_LOGIN_ENTRY_POINT, successHandler, failureHandler, objectMapper);
    filter.setAuthenticationManager(this.authenticationManager);
    return filter;
}

@Bean
protected JwtTokenAuthenticationProcessingFilter buildJwtTokenAuthenticationProcessingFilter() throws Exception {
    List<String> pathsToSkip = Arrays.asList(TOKEN_REFRESH_ENTRY_POINT, FORM_BASED_LOGIN_ENTRY_POINT, FORM_BASED_REGISTRATION_ENTRY_POINT, PHONE_PREFIXES_ENTRY_POINT, ACCOUNT_ACTIVATION_ENTRY_POINT, PASSWORD_RESET_EMAIL_ENTRY_POINT, PASSWORD_RESET_ENTRY_POINT);
    SkipPathRequestMatcher matcher = new SkipPathRequestMatcher(pathsToSkip, TOKEN_BASED_AUTH_ENTRY_POINT);
    JwtTokenAuthenticationProcessingFilter filter 
        = new JwtTokenAuthenticationProcessingFilter(failureHandler, tokenExtractor, matcher);
    filter.setAuthenticationManager(this.authenticationManager);
    return filter;
}
 @Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
  return super.authenticationManagerBean();
 }

 protected void configure(AuthenticationManagerBuilder auth) {
  auth.authenticationProvider(ajaxAuthenticationProvider);
  auth.authenticationProvider(jwtAuthenticationProvider);
 }

 @Bean
 protected BCryptPasswordEncoder passwordEncoder() {
  return new BCryptPasswordEncoder();
 }

@Override
protected void configure(HttpSecurity http) throws Exception {

    //the configure function added before
}
}

EDIT 2

, , ControllerAdvice @ExceptionHandler. , , ResponseBody , .

//register a new user
@RequestMapping(
        value = UtilsProperties.API_PATH+"/auth/signup",
        method = RequestMethod.POST,
        consumes = MediaType.APPLICATION_JSON_VALUE,
        produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity createUser( @RequestHeader(value="Accept-Language") String language, @RequestBody UserDto userDto ) throws SQLException, MessageNotFoundException, UserAlreadyRegisteredException, PermissionNotFoundException, UserNotFoundException, GroupNotFoundException, PermissionAlreadyInGroupException, InvalidEmailException, MailNotSentException, MissingParametersException, PhonePrefixNotFoundException, InvalidPasswordException, TelephoneAlreadyPresentException {
    User user = dtoConvertionService.convertUserToEntity(userDto);
    User savedUser = userService.create(user, language);
    if(savedUser.getIdUser().equals(-3L)) {
        return ResponseEntity
                .status(HttpStatus.FORBIDDEN)
                .build();
    }
    EmailUtils.sendActivationMail(language, user);
    return ResponseEntity
            .status(HttpStatus.CREATED)
            .body(dtoConvertionService.convertUserToDto(savedUser));
}

. , UserAlreadyRegisteredException.

@ControllerAdvice
public class ExceptionHandlingController {


//AUTH
@ResponseStatus(value=HttpStatus.NOT_FOUND, reason="Email not found")
@ExceptionHandler(EmailNotFoundException.class)
public void handleEmailNotFoundException(EmailNotFoundException exception){}

@ResponseStatus(value=HttpStatus.UNAUTHORIZED, reason="User not active")
@ExceptionHandler(UserNotActiveException.class)
public void handleUserNotActiveException(UserNotActiveException exception){}

//USERS
@ResponseStatus(value=HttpStatus.NOT_FOUND, reason="User not found")
@ExceptionHandler(UserNotFoundException.class)
public void handleUserNotFoundException(UserNotFoundException exception){}

@ResponseStatus(value=HttpStatus.FORBIDDEN, reason="User already registered")
@ExceptionHandler(UserAlreadyRegisteredException.class)
public void handleUserAlreadyRegisteredException(UserAlreadyRegisteredException exception){}

//more exceptions that don't need to be written

}

, , Spring . ?

+4

Source: https://habr.com/ru/post/1663980/


All Articles