Spring: метод-не-разрешено возвращает код ошибки 403 (запрещено) вместо 405 - PullRequest
1 голос
/ 30 мая 2019

Я создаю REST API в Spring, который должен действовать как ResouceServer в потоке учетных данных клиента Oauth2.Я пытаюсь отключить потребность в учетных данных клиента во время разработки и тестирования, поэтому я создал два профиля: «локальный» и «облачный».Вот настройки безопасности каждого профиля.

облако :

@Configuration
@EnableResourceServer
@EnableGlobalMethodSecurity(prePostEnabled = true, proxyTargetClass = true)
@Profile("cloud")
public class CloudSecurityConfiguration extends ResourceServerConfigurerAdapter {

  @Autowired
  private MyEntryPoint myEntryPoint;

  @Override
  public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
    resources.resourceId("res-id");          
    resources.authenticationEntryPoint(myEntryPoint);
  }

  @Override
  public void configure(HttpSecurity http) throws Exception {
      http
              .authorizeRequests()
              .antMatchers("/").permitAll()
              .antMatchers("/.../**").fullyAuthenticated();
  }

}

-

@Component
@Profile("cloud")
public class MyEntryPoint implements AuthenticationEntryPoint {

  @Autowired
  private HandlerExceptionResolver handlerExceptionResolver;

  @Override
  public void commence(HttpServletRequest request, HttpServletResponse response,
                     AuthenticationException authException) throws IOException, ServletException {
      handlerExceptionResolver.resolveException(request, response, null, authException);
  }
}

локальное :

@Configuration
@EnableWebSecurity
@Profile("local")
@Order(Ordered.HIGHEST_PRECEDENCE)
@EnableGlobalMethodSecurity(prePostEnabled = true, proxyTargetClass = true)
public class LocalSecurityConfiguration extends WebSecurityConfigurerAdapter {
  @Override
  public void configure(HttpSecurity http) throws Exception {
      http.authorizeRequests().anyRequest().permitAll();
  }
}

(@EnableWebSecurity используется в локальном профиле вместо @EnableResourceServer, потому что он решил странную проблему, заключающуюся в том, что если бы токен присутствовал в заголовке (даже если он не был необходим), каждая конечная точка в локальном профиле всегда возвращала бы ответ об ошибке Invalid access token)

Теперь проблема в том, что API поддерживает только метод GET, и я должен следовать строгой спецификации, утверждающей, что он должен возвращать код ошибки 405, когда запрос с другим методом, таким как POST,отправлено на сервер.С уровнем безопасности он отлично работает с классом @ControllerAdvice, который расширяет ResponseEntityExceptionHandler, но когда я пытаюсь удалить уровень безопасности, переключаясь на «локальный» профиль, в моих тестах я получаю ошибку 403-Forbidden вместоa 405.

Вот мой ResponseEntityExceptionHandler:

@ControllerAdvice
public class MyErrorHandler extends ResponseEntityExceptionHandler {    

  @ExceptionHandler
  @ResponseBody
  @ResponseStatus(HttpStatus.BAD_REQUEST)
  protected ErrorModel handleAllExceptions(ConstraintViolationException e, ServletWebRequest request) {
      return exceptionMapping(e, request, HttpStatus.BAD_REQUEST, TypeEnum.V);
  }

  @ExceptionHandler
  @ResponseBody
  @ResponseStatus(HttpStatus.UNAUTHORIZED)
  protected ErrorModel handleAllExceptions(AuthenticationException e, ServletWebRequest request) {      
      return exceptionMapping(e, request, HttpStatus.UNAUTHORIZED, TypeEnum.S);
  }

  @ExceptionHandler
  @ResponseBody
  @ResponseStatus(HttpStatus.FORBIDDEN)
  protected ErrorModel handleAllExceptions(AccessDeniedException e, ServletWebRequest request) {        
      return exceptionMapping(e, request, HttpStatus.FORBIDDEN, TypeEnum.S);
  } 

  @ExceptionHandler
  @ResponseBody
  @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
  protected ErrorModel handleAllExceptions(Exception  e, ServletWebRequest request) {       
      return exceptionMapping(e, request, HttpStatus.INTERNAL_SERVER_ERROR, TypeEnum.S);
}

  @Override
  protected ResponseEntity<Object> handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException ex,
        HttpHeaders headers, HttpStatus status, WebRequest request) {

      ErrorModel error = exceptionMapping(ex, (ServletWebRequest) request, status, TypeEnum.S);
      return new ResponseEntity<>(error, headers, status);
  }

  private ErrorModel exceptionMapping(Exception e, ServletWebRequest request, HttpStatus status, TypeEnum type) {
    /* build and returns the error view */
  }
}

Ex (с «локальным» профилем):

POST http://localhost:8080/.../my-end-point

ответ: status 403

{
   "timestamp": "2019-05-30T15:44:36.021-0400",
   "status": 403,
   "error": "Forbidden",
   "message": " Forbidden",
   "path": "/.../my-end-point"
}

этот ответ имеет неправильный формат и код статуса!

Ожидается: ответ с status: 405, отформатированный в соответствии с классом ErrorModel.

Пример (с «облачным» профилем):

POST http://localhost:8080/.../my-end-point

ответ: status 405

{
  "status": 405,
  "errors": [
    {
      "exception": "class org.springframework.web.HttpRequestMethodNotSupportedException",
      "systemId": "91224",
      "code": null,
      "systemName": "sys-name",
      "meta": null,
      "detail": "Request method 'POST' not supported",
      "time": "2019-05-30T15:49:10.021",
      "type": "S",
      "message": null,
      "url": "http://localhost:8080/.../my-end-point"
    }
  ]
}

этот ответ имеетправильный формат и код состояния.Это то, что ожидается и требуется!

Как я могу убедиться, что POST (или любой другой метод, кроме GET) для URL возвращает ошибку 405 в локальном профиле?Еще раз хочу подчеркнуть, что когда приложение запускается в «облачном» профиле, все работает как положено.

...