Пользовательская логика проверки для параметра в конечной точке REST Spring Boot

В настоящее время у меня есть этот RequestMapping, где я использую проверку с помощью регулярного выражения:

 @RequestMapping(value = "/example/{id}", method = GET)
 public Response getExample(
         @PathVariable("id") String id,
         @RequestParam(value = "myParam", required = true) @Valid @Pattern(regexp = MY_REGEX) String myParamRequest,
         @RequestParam(value = "callback", required = false) String callback,
         @RequestHeader(value = "X-API-Key", required = true) @Valid @Pattern(regexp = SEGMENTS_REGEX) String apiKeyHeader) {

     // Stuff here...
 }

Однако регулярного выражения недостаточно. Вместо этого я хотел бы выполнить некоторую пользовательскую проверку атрибута заголовка, т.е.

if (!API_KEY_LIST.contains(apiKeyHeader)) {
    throw Exception();
}

Это возможно?


person ptimson    schedule 29.07.2016    source источник
comment
@RequestHeader(value = X-API-Key, required = true) @Valid @Pattern(regexp = SEGMENTS_REGEX) String apiKeyHeader Не могли бы вы сообщить мне, работает ли приведенная выше строка для вас?   -  person Lathy    schedule 01.12.2017
comment
Обратите внимание, что хотя эта проблема не решена, аннотации проверки на @PathVariable, @RequestHeader и @RequestParam работают, только если класс помечен @Validated. На всякий случай, если кто-то задается вопросом, почему @Pattern работает для ОП...   -  person oberlies    schedule 26.02.2019
comment
не могли бы вы поделиться примером о заголовках @Validated & validate?   -  person VelNaga    schedule 27.02.2019


Ответы (4)


Лучший способ сделать это, IMO, — создать собственный HandlerMethodArgumentResolver, который будет выглядеть примерно так, используя пользовательскую аннотацию @Segment:

public class SegmentHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver {

    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        return parameter.getParameterType().equals(String.class)
            && parameter.getParameterAnnotation(Segment.class);
    }

    @Override
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
        String apiKey = webRequest.getHeader("X-API-Key");
        if (apiKey != null) {
            if (!API_KEY_LIST.contains(apiKey)) {
                throw new InvalidApiKeyException();
            }
            return apiKey;
        } else {
            return WebArgumentResolver.UNRESOLVED;
        }
    }
}

Тогда ваша подпись контроллера выглядит так:

@RequestMapping(value = "/example/{id}", method = GET)
 public Response getExample(
         @PathVariable("id") String id,
         @RequestParam(value = "myParam", required = true) @Valid @Pattern(regexp = MY_REGEX) String myParamRequest,
         @RequestParam(value = "callback", required = false) String callback,
         @Segment String apiKeyHeader) {

     // Stuff here...
 }

Вы зарегистрируете преобразователь аргумента метода обработчика в своем WebMvcConfigurationAdapter:

@Configuration
public class MvcConfig extends WebMvcConfigurerAdapter {

    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
        argumentResolvers.add(segmentHandler());
    }

    @Bean
    public SegmentHandlerMethodArgumentResolver segmentHandler() {
        return new SegmentHandlerMethodArgumentResolver();
    }

}
person Ulises    schedule 29.07.2016
comment
почему это называется «Сегмент»? - person MariuszS; 27.09.2017
comment
параметр.getParameterAnnotation(Segment.class); должен быть параметр.getParameterAnnotation(Segment.class) != null - person Darshana; 15.04.2020

Уже есть бэклог запроса функции Spring, проверьте JIRA. Однако мне удалось добиться того, что вы пытаетесь использовать, используя аннотацию @Validated на контроллере.

@RestController
@RequestMapping("/user")
@Validated
public class UserController {

   @GetMapping("/{loginId}")
   public User getUserBy(@PathVariable @LoginID final String loginId) {
      // return some user
   }
}

Здесь @LoginID — пользовательский валидатор. И @Validated от org.springframework.validation.annotation.Validated, что делает свое дело.

person Sarvesh Dubey    schedule 25.04.2018
comment
можете ли вы поделиться своим решением, пожалуйста? - person Daniel Gomez Rico; 23.07.2018
comment
не могли бы вы поделиться реализацией @LoginID? - person VelNaga; 27.02.2019
comment
Просто внедрите настраиваемый валидатор hibernate, ознакомьтесь с этим docs.jboss.org/hibernate/validator/5.0/reference/en-US/html/ - person Sarvesh Dubey; 28.02.2019

1) Проверить вручную

Вы можете ввести HttpServletRequest и проверить заголовки.

@RestController
public class HomeController {
    public ResponseEntity<String> test(HttpServletRequest request){
        if(request.getHeader("apiKeyHeader") == null){
            return new ResponseEntity<String>(HttpStatus.BAD_REQUEST);
        }
        return new ResponseEntity<String>(HttpStatus.OK);
    }
}

2) Вставить заголовок

@RequestMapping(value = "/test", method = RequestMethod.POST)
public ResponseEntity<String> test(@RequestHeader(value="myheader") String myheader){
    return new ResponseEntity<String>(HttpStatus.OK);
}

Это вернет:

{
  "timestamp": 1469805110889,
  "status": 400,
  "error": "Bad Request",
  "exception": "org.springframework.web.bind.ServletRequestBindingException",
  "message": "Missing request header 'myheader' for method parameter of type String",
  "path": "/test"
}

если заголовок отсутствует.

3) Использовать фильтр

Вы можете автоматизировать проверку с помощью некоторого фильтра, если хотите использовать его для нескольких методов. В вашем пользовательском фильтре просто получите заголовок (например, в методе 1), и если заголовок отсутствует, просто ответьте 400 или чем хотите. Для меня это имеет смысл, когда вы не используете значение заголовка в методе контроллера и вам нужно только проверить его наличие.

@Bean
public FilterRegistrationBean someFilterRegistration() { 
    FilterRegistrationBean registration = new FilterRegistrationBean();
    registration.setFilter(apiHeaderFilter());
    registration.addUrlPatterns("/example/*");
    registration.setName("apiHeaderFilter");
    registration.setOrder(1);
    return registration;
} 

@Bean(name = "ApiHeaderFilter")
public Filter apiHeaderFilter() {
    return new ApiHeaderFilter();
}

Пропустить запрос

Если вы используете атрибут заголовков в @RequestMapping

@RequestMapping(value = "/test", method = RequestMethod.POST,
    headers = {"content-type=application/json"})

это приведет к 404, если нет другого обработчика для обработки запроса.

person Evgeni Dimitrov    schedule 29.07.2016
comment
Для варианта № 3: как бы мы использовали этот код фильтра, чтобы вернуть неверный ответ на запрос, если заголовок был отправлен неправильно? - person IcedDante; 16.09.2017
comment
@IcedDante См. ответы здесь: stackoverflow.com/questions/23621037/ - person Evgeni Dimitrov; 16.09.2017

Просто добавьте следующий класс. Выполните любые проверки внутри метода «doFilter» и установите соответствующий код ответа.

@Configuration
public class ApiHeaderFilter extends GenericFilterBean {

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest)servletRequest;
        HttpServletResponse response = (HttpServletResponse)servletResponse;
        String token = request.getHeader("token");
        if (StringUtil.isNullOrEmpty(token)) {
            response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
            return;
        }
        filterChain.doFilter(servletRequest,servletResponse);
    }
}
person Pruthviraj    schedule 30.01.2019