Обрабатывать исключения безопасности в Spring Boot Resource Server

как я могу получить мой заказ ResponseEntityExceptionHandler или OAuth2ExceptionRenderer обрабатывать исключения, вызванные Spring security на чистом сервере ресурсов?

мы реализовали

@ControllerAdvice
@RestController
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {

поэтому всякий раз, когда есть ошибка на сервере ресурсов, мы хотим, чтобы он ответил с

{
  "message": "...",
  "type": "...",
  "status": 400
}

сервер ресурсов использует приложение.настройка свойств:

security.oauth2.resource.userInfoUri: http://localhost:9999/auth/user

для аутентификации и авторизации запроса на нашем сервере аутентификации.

однако любая весна ошибка безопасности всегда будет обходить наш обработчик исключений в

    @ExceptionHandler(InvalidTokenException.class)
    public ResponseEntity<Map<String, Object>> handleInvalidTokenException(InvalidTokenException e) {
        return createErrorResponseAndLog(e, 401);
    }

и произвести либо

{
  "timestamp": "2016-12-14T10:40:34.122Z",
  "status": 403,
  "error": "Forbidden",
  "message": "Access Denied",
  "path": "/api/templates/585004226f793042a094d3a9/schema"
}

или

{
  "error": "invalid_token",
  "error_description": "5d7e4ab5-4a88-4571-b4a4-042bce0a076b"
}

Итак, как настроить обработку исключений безопасности для сервера ресурсов? Все, что я когда-либо находил, - это примеры того, как настроить сервер Auth, реализовав пользовательский OAuth2ExceptionRenderer. Но я не могу найти, где подключить это к цепочке безопасности сервера ресурсов.

наша единственная конфигурация/настройки это:

@SpringBootApplication
@Configuration
@ComponentScan(basePackages = {"our.packages"})
@EnableAutoConfiguration
@EnableResourceServer

5 ответов


как отмечалось в предыдущих комментариях, запрос отклоняется платформой безопасности, прежде чем он достигнет уровня MVC so @ControllerAdvice - это не вариант.

в Spring Security framework есть 3 интерфейса, которые могут представлять интерес здесь:

  • org.springframework.безопасность.сеть.идентификация.AuthenticationSuccessHandler
  • org.springframework.безопасность.сеть.идентификация.AuthenticationFailureHandler
  • org.springframework.безопасность.сеть.доступ.AccessDeniedHandler

вы можете создавать реализации каждого из этих интерфейсов для настройки ответа, отправленного для различных событий: успешного входа в систему, неудачного входа в систему, попытки доступа к защищенному ресурсу с помощью недостаточные разрешения.

следующее вернет ответ JSON при неудачной попытке входа в систему:

@Component
public class RestAuthenticationFailureHandler implements AuthenticationFailureHandler
{
  @Override
  public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
      AuthenticationException ex) throws IOException, ServletException
  {
    response.setStatus(HttpStatus.FORBIDDEN.value());

    Map<String, Object> data = new HashMap<>();
    data.put("timestamp", new Date());
    data.put("status",HttpStatus.FORBIDDEN.value());
    data.put("message", "Access Denied");
    data.put("path", request.getRequestURL().toString());

    OutputStream out = response.getOutputStream();
    com.fasterxml.jackson.databind.ObjectMapper mapper = new ObjectMapper();
    mapper.writeValue(out, data);
    out.flush();
  }
}

вам также необходимо зарегистрировать вашу реализацию(ы) в рамках безопасности. В конфигурации Java это выглядит следующим образом:

@Configuration
@EnableWebSecurity
@ComponentScan("...")
public class SecurityConfiguration extends WebSecurityConfigurerAdapter
{
  @Override
  public void configure(HttpSecurity http) throws Exception
  {
    http.addFilterBefore(corsFilter(), ChannelProcessingFilter.class).logout().deleteCookies("JESSIONID")
        .logoutUrl("/api/logout").logoutSuccessHandler(logoutSuccessHandler()).and().formLogin().loginPage("/login")
        .loginProcessingUrl("/api/login").failureHandler(authenticationFailureHandler())
        .successHandler(authenticationSuccessHandler()).and().csrf().disable().exceptionHandling()
        .authenticationEntryPoint(authenticationEntryPoint()).accessDeniedHandler(accessDeniedHandler());
  }

  /**
   * @return Custom {@link AuthenticationFailureHandler} to send suitable response to REST clients in the event of a
   *         failed authentication attempt.
   */
  @Bean
  public AuthenticationFailureHandler authenticationFailureHandler()
  {
    return new RestAuthenticationFailureHandler();
  }

  /**
   * @return Custom {@link AuthenticationSuccessHandler} to send suitable response to REST clients in the event of a
   *         successful authentication attempt.
   */
  @Bean
  public AuthenticationSuccessHandler authenticationSuccessHandler()
  {
    return new RestAuthenticationSuccessHandler();
  }

  /**
   * @return Custom {@link AccessDeniedHandler} to send suitable response to REST clients in the event of an attempt to
   *         access resources to which the user has insufficient privileges.
   */
  @Bean
  public AccessDeniedHandler accessDeniedHandler()
  {
    return new RestAccessDeniedHandler();
  }
}

в случае если вы используете @EnableResourceServer, вы также можете найти удобным для расширения ResourceServerConfigurerAdapter вместо WebSecurityConfigurerAdapter в своем @Configuration класса. Сделав это, вы можете просто зарегистрировать custom AuthenticationEntryPoint переопределив configure(ResourceServerSecurityConfigurer resources) и с помощью resources.authenticationEntryPoint(customAuthEntryPoint()) внутри метода.

что-то вроде этого:

@Configuration
@EnableResourceServer
public class CommonSecurityConfig extends ResourceServerConfigurerAdapter {

    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
        resources.authenticationEntryPoint(customAuthEntryPoint());
    }

    @Bean
    public AuthenticationEntryPoint customAuthEntryPoint(){
        return new AuthFailureHandler();
    }
}

еще OAuth2AuthenticationEntryPoint который может быть расширен (поскольку он не является окончательным) и частично повторно использован при реализации пользовательского AuthenticationEntryPoint. В частности, он добавляет Заголовки "WWW-Authenticate" с деталями, связанными с ошибкой.


вы не можете использовать аннотации обработчика исключений Spring MVC, такие как @ControllerAdvice потому что весенние фильтры безопасности срабатывают намного раньше весеннего MVC.


OAuth2ExceptionRenderer предназначен для сервера авторизации. Правильный ответ, вероятно, будет обрабатывать его, как подробно описано в этом посте (то есть игнорировать, что это oauth и относиться к нему как к любому другому механизму аутентификации spring security):https://stackoverflow.com/a/26502321/5639571

конечно, это поймает связанные с OAuth исключения (которые выбрасываются до достижения конечной точки ресурса), но любые исключения, происходящие в конечной точке ресурса, все равно будут требуется метод @ExceptionHandler.


Весна 3.0 вперед, вы можете использовать @ControllerAdvice (на уровне класса) и распространяется org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler класс CustomGlobalExceptionHandler

@ExceptionHandler({com.test.CustomException1.class,com.test.CustomException2.class})
public final ResponseEntity<CustomErrorMessage> customExceptionHandler(RuntimeException ex){
     return new ResponseEntity<CustomErrorMessage>(new CustomErrorMessage(false,ex.getMessage(),404),HttpStatus.BAD_REQUEST);
}