Подтвердить что ты не робот

Обработка исключений безопасности в Spring Сервер загрузочных ресурсов

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

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

@ControllerAdvice
@RestController
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {

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

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

Сервер ресурсов использует параметр application.properties:

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

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

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

    @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
4b9b3361

Ответ 1

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

В Spring структуре безопасности есть 3 интерфейса:

  • org.springframework.security.web.authentication.AuthenticationSuccessHandler
  • org.springframework.security.web.authentication.AuthenticationFailureHandler
  • org.springframework.security.web.access.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();
  }
}

Ответ 2

Если вы используете @EnableResourceServer, вы можете также найти расширение ResourceServerConfigurerAdapter вместо WebSecurityConfigurerAdapter в вашем классе @Configuration. Делая это, вы можете просто зарегистрировать пользовательский 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" с деталями, связанными с ошибкой.

Ответ 3

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

Ответ 4

OAuth2ExceptionRenderer предназначен для сервера авторизации. Правильный ответ, скорее всего, будет обрабатывать его как подробно описанный в этом сообщении (т.е. Игнорировать его и рассматривать как любой другой механизм проверки подлинности spring): fooobar.com/questions/130587/...

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

Ответ 5

Если вы используете URL-адрес проверки токена с конфигурацией, аналогичной настройке сервера ресурсов с RemoteTokenServices в Spring Security Oauth2, который возвращает HTTP-статус 401 в случае неавторизованного доступа:

@Primary
@Bean
public RemoteTokenServices tokenService() {
    RemoteTokenServices tokenService = new RemoteTokenServices();
    tokenService.setCheckTokenEndpointUrl("https://token-validation-url.com");
    tokenService.setTokenName("token");
    return tokenService;
}

Реализация пользовательской authenticationEntryPoint EntryPoint, как описано в других ответах (fooobar.com/questions/843480/...), не будет работать, поскольку RemoteTokenService использует состояние 400 и выдает необработанные исключения для других состояний, таких как 401:

public RemoteTokenServices() {
        restTemplate = new RestTemplate();
        ((RestTemplate) restTemplate).setErrorHandler(new DefaultResponseErrorHandler() {
            @Override
            // Ignore 400
            public void handleError(ClientHttpResponse response) throws IOException {
                if (response.getRawStatusCode() != 400) {
                    super.handleError(response);
                }
            }
        });
}

Таким образом, вам нужно установить пользовательский RestTemplate в конфигурации RemoteTokenServices который будет обрабатывать 401 без исключения:

@Primary
@Bean
public RemoteTokenServices tokenService() {
    RemoteTokenServices tokenService = new RemoteTokenServices();
    tokenService.setCheckTokenEndpointUrl("https://token-validation-url.com");
    tokenService.setTokenName("token");
    RestOperations restTemplate = new RestTemplate();
    restTemplate.setRequestFactory(new HttpComponentsClientHttpRequestFactory());
    ((RestTemplate) restTemplate).setErrorHandler(new DefaultResponseErrorHandler() {
            @Override
            // Ignore 400 and 401
            public void handleError(ClientHttpResponse response) throws IOException {
                if (response.getRawStatusCode() != 400 && response.getRawStatusCode() != 401) {
                    super.handleError(response);
                }
            }
        });
    }
    tokenService.setRestTemplate(restTemplate);
    return tokenService;
}

И добавьте зависимость для HttpComponentsClientHttpRequestFactory:

<dependency>
  <groupId>org.apache.httpcomponents</groupId>
  <artifactId>httpclient</artifactId>
</dependency>

Ответ 6

Spring 3.0 и далее, вы можете использовать @ControllerAdvice (на уровне класса) и org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler класс CustomGlobalExceptionHandler из 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);
}