我的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之前运行的事实。这个问题有解决办法吗?