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()
.exceptionHandling()
.authenticationEntryPoint(this.authenticationEntryPoint)
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers(FORM_BASED_LOGIN_ENTRY_POINT).permitAll()
.antMatchers(TOKEN_REFRESH_ENTRY_POINT).permitAll()
.antMatchers(FORM_BASED_REGISTRATION_ENTRY_POINT).permitAll()
.antMatchers(PHONE_PREFIXES_ENTRY_POINT).permitAll()
.antMatchers(ACCOUNT_ACTIVATION_ENTRY_POINT).permitAll()
.antMatchers(PASSWORD_RESET_EMAIL_ENTRY_POINT).permitAll()
.antMatchers(PASSWORD_RESET_ENTRY_POINT).permitAll()
.antMatchers(TOKEN_BASED_AUTH_ENTRY_POINT).authenticated()
.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 {
}
}
EDIT 2
, , ControllerAdvice @ExceptionHandler.
, , ResponseBody , .
@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 {
@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){}
@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){}
}
, , Spring . ?