Spring Boot Request Header Validation - java

I am using Spring Boot 2.3.8 for creating rest services. I have a requirement to validate the request headers such that it should have certain headers otherwise throw error. This should be common for all the methods or services. I tried below,
public ResponseEntity<Object> addEmployee(
#RequestHeader(name = "header1", required = true) String header1,
#RequestHeader(name = "header2", required = true) String header2,
#RequestBody Employee employee)
throws Exception
{
But I need to add this for all the methods in all the controllers. If this is the case how can I throw an error like "Header1 missing in request headers" / "header2 missing in request headers" for all the services globally ?

For global use you can register an interceptor.
#Component
public class MyHandlerInterceptor implements HandlerInterceptor {
#Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object object, Exception arg3) throws Exception {
}
#Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object object, ModelAndView model) throws Exception {
}
#Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {
//here check headers contained in request object
if (request.getHeader("header1") == null || request.getHeader("header2") == null) {
response.getWriter().write("something");
response.setStatus(someErrorCode);
return false;
}
return true;
}
And then register it
#Configuration
public class WebMvcConfig implements WebMvcConfigurer {
#Autowired
private MyHandlerInterceptor interceptor;
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(interceptor).addPathPatterns("/**");
}
}

This is what filters are for. You want to filter out requests based on a header being there or not and return an error if its missing.
Extend OncePerRequestFilter optionally override shouldNotFilter if you do not want it to be used for all request OR you could implement a normal Filter and use FilterRegistrationBean to register it only for a specific path.
then in the filter you could throw an exception and let Spring figure out how to display that, or actually set the response to something meaningful.

If you have Zuul in place then you can read the request header attributes there in pre route and on validation failure reply back to the request with error populated.

This is a cross-cutting concern and should be done using AOP. Since you're using Spring, you can do the following:
Create an annotation called ValidateHeaders:
public #interface ValidateHeaders {}
Create a #Before advice that intercepts methods annotated with #ValidateHeaders:
#Before("#annotation(com.somepackage.ValidateHeaders)")
public void controllerProxy(JoinPoint jp) {
Object[] args = jp.getArgs();
//validation logic, throw exception if validation fails
}
Note that you'll have to extract the fields using reflection as:
Annotation[][] pa = ms.getMethod().getParameterAnnotations();
You can iterate through all the annotations and search for request headers as:
if(annotations[i][j].annotationType().getName().contains("RequestHeader")) {
RequestHeader requestHeader = (RequestHeader) m.getParameterAnnotations()[i][j];
//now access fields in requestHeader to do the validation
}
Here's an article that can get you started with Advice types:
https://www.baeldung.com/spring-aop-advice-tutorial

Related

Is there a way to get request URI in Spring?

Whenever a request is made, I need to get the request URI for some internal calculations.
For some time I've been doing it like this:
public Mono<Response> example(ServerHttpRequest req) { ... }
And then using req.getURI(), but that becomes a pain once you need to pass it down multiple times. I need the URI object to extract scheme, schemeSpecificPart, host, port from it.
Is there a way to get these properties without extracting them from a request?
UPD: I see that for Web MVC there are convenient methods to retrieve request URI. But I need the same for reactive stack (netty).
It can be achieved by creating WebFilter that puts ServerHttpRequest into the Context:
#Component
#ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)
public class ReactiveRequestContextFilter implements WebFilter {
#Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
return chain
.filter(exchange)
.contextWrite(ctx -> ctx.put(ReactiveRequestContextHolder.CONTEXT_KEY, request));
}
}
Additionally, create a class that provides static access to request data:
public class ReactiveRequestContextHolder {
public static final Class<ServerHttpRequest> CONTEXT_KEY = ServerHttpRequest.class;
public static Mono<ServerHttpRequest> getRequest() {
return Mono.deferContextual(Mono::just).map(ctx -> ctx.get(CONTEXT_KEY));
}
public static Mono<URI> getURI() {
return getRequest().map(HttpRequest::getURI);
}
}
Methods can be accessed through the class name directly without having to instantiate them. Just be aware that it should not be accessed before the filter is executed.
Example of usage:
#RestController
#RequestMapping
public class TestController {
#GetMapping("/test")
public Mono<URI> test() {
return ReactiveRequestContextHolder.getURI();
}
}
Reference
You can try this :
public Mono<Response> example(WebRequest request){
System.out.println(request.getDescription(false));
.......
}
You can turn this false to true in getDescription as false will only give you the Uri which i think is the only thing you need.
You can inject it in any bean.
#Autowired
private HttpServletRequest request;

Rejecting GET requests with additional query params

I have an HTTP GET endpoint that accepts some query parameters:
#GetMapping("/cat")
public ResponseEntity<Cat> getCat(#RequestParam("catName") String catName){ ...
If the clients will send additional query parameters, the endpoint will ignore them.
GET .../cat?catName=Oscar Getting Oscar
GET .../cat?catName=Oscar&gender=male Getting Oscar
GET .../cat?catName=Oscar&x=y Getting Oscar
I want to reject HTTP requests that will send additional query parameters:
GET .../cat?catName=Oscar OK
GET .../cat?catName=Oscar&gender=male Reject (HTTP error code XYZ)
GET .../cat?catName=Oscar&x=y Reject (HTTP error code XYZ)
I can change the signature of the method to accept a map and validate the values in the map as suggested here.
Is there a way to do with while keeping the cleaner and self explained method signature?
You can try to implement a HandlerInterceptor and validate such rule in preHandle(). If the request contains the query parameters that does not defined in the controller method , you just throw a specific type of Exception and configure a #ControllerAdvice to handle this exception. Something like :
public class FooHandlerInterceptor implements HandlerInterceptor {
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
if (handler instanceof HandlerMethod) {
HandlerMethod hm = (HandlerMethod) handler;
Set<String> allowQueryParams = Stream.of(hm.getMethodParameters())
.map(p -> p.getParameterAnnotation(RequestParam.class))
.map(req -> req.value())
.collect(toSet());
for (String currentRequestParamName : request.getParameterMap().keySet()) {
if (!allowQueryParams.contains(currentRequestParamName)) {
throw new FooRestException();
}
}
}
return true;
}
}
And the #ControllerAdvice to handle the Exception :
#ControllerAdvice
public class FooExceptionHandler {
#ExceptionHandler(FooRestException.class)
public ResponseEntity<Object> handle(FooRestException ex) {
return new ResponseEntity<>("Some query parameter are not defined", HttpStatus.BAD_REQUEST);
}
}
Finally register FooHandlerInterceptor to use it :
#Configuration
public class WebConfig implements WebMvcConfigurer {
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new FooHandlerInterceptor());
}
}
I just show you the idea. You can further tweak the codes in HandlerInterceptor if you want such checking is only applied to a particular controller method.

Spring boot + Caffeine cache + Check header

i'm trying to use cache (caffeine) with Spring boot and im having a problem. I need to check the header "header-name" in every call but application is caching it so after first request with the right header, dont matter what header i send and the application will not check it and is just returning data from the cache, is there anyway that i can force spring to check header and then get data from cache?
#GetMapping("/request-a")
#Cacheable(cacheNames = "cachename", key = "#root.methodName")
public ResponseEntity<?> makeRequest(#RequestHeader("header-name") String headerName) {
this.authConfig.headerCheck(headerName);
/*
code
*/
}
I already used header "Cache-Control:no-cache" and didnt resolve my problem.
Thanks in advance.
Edit1: method "headerCheck" just check if its equal to another String or not null.
Found a solution:
Create a classe that implements HandlerInterceptor and use preHandle method.
#Component
public class CheckHeaderInterceptor implements HandlerInterceptor {
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
// validate what you want, on error return false
// if everything its ok, return true
}
}
Then register the handler with:
public class WebMvcConfig implements WebMvcConfigurer {
#Autowired
private CheckHeaderInterceptor interceptor;
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(interceptor).addPathPatterns("url that you wannna use handler");
}
}

Spring Boot - determining a sub-domain (with wildcard)?

New to Spring Boot here. Spring MVC provides the #SubdomainMapping annotation, which does not seem to be available from what I can see in Spring Boot. I've seen a few people discuss using a filter to handle this. Or other approaches that seem overly convoluted.
Would there not be a (simple/cleaner) way to handle all sub-domains within a standard controller such as:
#SubdomainMapping(value = {"**"}
public String data(ModelMap modelMap, HttpServletRequest request) {
//Code to handles subdomain logic here ....
}
This would be a simple approach where all values are treated equally with minor differences.
Any suggestions would be helpful!
I have worked on this myself and I have an answer that isn't as simple as you wanted, but I don't think there is one that simple.
So, you can create a handler interceptor adapter that will grab every request before it gets to your controller and grabs and processes the subdomain. This would require something like this:
#Component
public class SubDomainInterceptor extends HandlerInterceptorAdapter {
#Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object object, Exception arg3)
throws Exception {
}
#Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object object, ModelAndView model)
throws Exception {
}
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object object) throws Exception {
String mysub = request.getRequestURL().toString();
//...
// Do whatever you need to do with the subdomain
//
if (isGoodSubdomain){
session.sendAttribute("subdomain", mysub);
} else {
response.sendRedirect("http://www.basesite.com"):
}
return true;
}
You then use that session variable in your controllers to filter values or whatever you need to use them for. I know this isn't the simple answer you wanted, but it was the best one that I have found so far.

Spring MVC InterceptorHandler called twice with DeferredResult

When I am using custom HandlerInterceptor and my controller returns DeferredResult, the preHandle method of my custom interceptor called twice on each request. Consider a toy example.
My custom interceptor:
public class MyInterceptor implements HandlerInterceptor {
static int i = 0;
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println(i++);
return true;
}
#Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
#Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
}
}
My Spring Java configuration:
#Configuration
#EnableWebMvc
public class ApplicationConfiguration extends WebMvcConfigurerAdapter {
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new MyInterceptor());
}
}
My Controller:
#Controller
public class MyController {
#RequestMapping(value = "/test", method = RequestMethod.GET)
public DeferredResult<String> test() {
DeferredResult<String> df = new DeferredResult<String>();
df.setResult("blank");
return df;
}
}
So, on each page load I see two outputs from preHandle method. However, if I modify MyController in order to return just "blank" template (instead of DeferredResult with "blank" template), I see just one output from preHandle on each page load.
So, my question is why preHandle called twice when I use DeferredResult and is it possible to avoid this?
You need to use org.springframework.web.servlet.AsyncHandlerInterceptor:
public interface AsyncHandlerInterceptor extends HandlerInterceptor {
void afterConcurrentHandlingStarted(
HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception;
}
Spring MVC execute sequence:
preHandle
afterConcurrentHandlingStarted
preHandle
postHandle
afterCompletion
The difference between the two invocations can be seen by examining the value of request.getDispatcherType().
I followed what #thunder mentioned by checking the value of request.getDispatcherType() and this worked for me.
public class MyInterceptor extends HandlerInterceptorAdapter {
#Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
// NOTE: For all dispatchers that are not "REQUEST" like "ERROR", do
// an early return which prevents the preHandle function from
// running multiple times
if (request.getDispatcherType() != DispatcherType.REQUEST) {
return true;
}
// ... do other stuff and then do a final return
return true;
}
}
As I'm exploring adding filter and Interceptor, I'm prettry sure that's caused by async calling. You use a DeferredResult here, which spring will do the filter in origin thread and filtering it again in a new thread. If you set log level to Debug, you will notice log like this.
15:14:06.948 [http-nio-8199-exec-5] DEBUG o.s.s.w.h.writers.HstsHeaderWriter - Not injecting HSTS header since it did not match the requestMatcher org.springframework.security.web.header.writers.HstsHeaderWriter$SecureRequestMatcher#efe6068
15:14:06.948 [http-nio-8199-exec-5] DEBUG o.s.s.w.c.SecurityContextPersistenceFilter - SecurityContextHolder now cleared, as request processing completed
15:14:06.948 [http-nio-8199-exec-5] DEBUG o.s.b.w.f.OrderedRequestContextFilter - Cleared thread-bound request context: org.apache.catalina.connector.RequestFacade#163cab73
15:14:07.148 [http-nio-8199-exec-6] DEBUG o.s.b.w.f.OrderedRequestContextFilter - Bound request context to thread: SecurityContextHolderAwareRequestWrapper[ FirewalledRequest[ org.apache.catalina.core.ApplicationHttpRequest#42f1262]]
In a word, it's executed once in one thread, but here're two threads.
As I digged the google, I found there's no good solution.
If you have something like auth in request, a walk around way is add
security.filter-dispatcher-types=REQUEST, ERROR
Then the new thread(async one) will not get the security context. You need to check it and stop the filter chain inside it.
Or you just use the tranditional sync call like:
#RequestMapping(value = "/test", method = RequestMethod.GET)
public String test() {
return "blank";
}
I found another helpful answer. https://jira.spring.io/browse/SPR-12608
Hope it helps!
Inside preHandle method, If you have response.sendError(<>, <>), then this preHandle method will execute twice for each API request. So remove sendError() method to execute this only once.
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
response.sendError(401, "Token is invalid");
return false;
}
The prehandle method of the AsyncHandlerInterceptor will always be executed twice in async processing.

Categories

Resources