Spring安全性和ExceptionHandler:permitAll路径异常总是在Tomcat服务器上返回401

时间:2016-12-16 16:40:03

标签: spring tomcat spring-boot spring-security

我的Spring Boot应用程序有问题。我使用Spring Security进行身份验证,使用带有@ExceptionHandler的ControllerAdvice注释进行异常处理。当我调用令牌受保护的api时一切顺利但是当我在一条总是被授权的路径上尝试它时(如注册或密码重置),这就出现了问题。如果响应为200,则一切正常,但是当引发异常,并且我可以在tomcat日志上看到异常时,即使@ExceptionHandler应该返回403,响应仍然是未经授权的401。

关于这种行为最奇怪的是它只发生在我的在线tomcat服务器上,而不是当我在我的本地机器上运行时,tomcat开始使用gradle。

这是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);
}

这是ControllerAdvice

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

当用户已经注册但是它会触发401时,应抛出此异常。 当然,如果我在请求标头中设置了Authorization标头,它就可以工作。

有人刚遇到这个问题吗?它可能与Tomcat配置有关吗?

提前感谢您的帮助。

修改

这是UserAlreadyRegisteredException类

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

带错误的路径是FORM_BASED_REGISTRATION_ENTRY_POINT,它在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
}
}

编辑2

我仍然找不到解决方案,但我发现问题是带有@ExceptionHandler的ControllerAdvice。 事实上,如果不是抛出异常,我通常会返回控制器并返回一个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 security filter在exceptionhandler之前运行的事实。这个问题有解决办法吗?

0 个答案:

没有答案