Я создаю 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 в локальном профиле?Еще раз хочу подчеркнуть, что когда приложение запускается в «облачном» профиле, все работает как положено.