J'ai actuellement ce mappeur de demandes où je suis en train de valider un paramètre de demande à l'aide d'un REGEX.
@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) String apiKeyHeader) {
// Stuff here...
}
Je peux donc valider avec un motif:
@RequestHeader(value = "X-API-Key", required = true) @Valid @Pattern(regexp = SEGMENTS_REGEX) String apiKeyHeader
Mais je voudrais faire une validation personnalisée sur l'attribut d'en-tête i.e.
if (!API_KEY_LIST.contains(apiKeyHeader)) {
throw Exception();
}
La meilleure façon de faire cette OMI est de créer une HandlerMethodArgumentResolver
personnalisée qui ressemblerait à ceci en utilisant une annotation personnalisée @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;
}
}
}
Ensuite, la signature de votre contrôleur ressemble à ceci:
@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...
}
Vous allez enregistrer le résolveur d'arguments de la méthode de gestionnaire dans votre WebMvcConfigurationAdapter:
@Configuration
public class MvcConfig extends WebMvcConfigurerAdapter {
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
argumentResolvers.add(segmentHandler());
}
@Bean
public SegmentHandlerMethodArgumentResolver segmentHandler() {
return new SegmentHandlerMethodArgumentResolver();
}
}
Il y a déjà une demande de fonctionnalité dans le carnet de commandes de Spring, paiement JIRA . Cependant, j'ai pu réaliser ce que vous essayez d'utiliser l'annotation @Validated
sur Controller.
@RestController
@RequestMapping("/user")
@Validated
public class UserController {
@GetMapping("/{loginId}")
public User getUserBy(@PathVariable @LoginID final String loginId) {
// return some user
}
}
Ici, @LoginID
est un validateur personnalisé. Et @Validated
est de org.springframework.validation.annotation.Validated
qui fait l'affaire.
1) Vérifier manuellement
Vous pouvez injecter HttpServletRequest et vérifier les en-têtes.
@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) Injecter l'en-tête
@RequestMapping(value = "/test", method = RequestMethod.POST)
public ResponseEntity<String> test(@RequestHeader(value="myheader") String myheader){
return new ResponseEntity<String>(HttpStatus.OK);
}
Cela va retourner:
{
"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"
}
si l'en-tête est manquant.
3) Utiliser le filtre
Vous pouvez automatiser la vérification avec certains filtres si vous souhaitez l’utiliser sur plusieurs méthodes. Dans votre filtre personnalisé, récupérez simplement l'en-tête (comme dans la méthode 1) et si l'en-tête est manquant, répondez avec 400 ou ce que vous voulez. Pour moi, cela a du sens lorsque vous n'utilisez pas la valeur d'en-tête dans la méthode du contrôleur et que vous devez simplement valider sa présence.
@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();
}
Ignorer la demande
Si vous utilisez l'attribut d'en-tête dans @RequestMapping
@RequestMapping(value = "/test", method = RequestMethod.POST,
headers = {"content-type=application/json"})
cela aboutira à 404 s'il n'y a pas d'autre gestionnaire pour prendre la demande.
Ajoutez simplement la classe suivante . Effectuez toutes les validations dans la méthode "doFilter" et définissez le code de réponse approprié.
@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);
}
}