Spring Boot Adding Http Request Interceptors
Asked Answered
S

10

164

What is the right way to add HttpRequest interceptors in spring boot application? What I want to do is log requests and responses for every http request.

Spring boot documentation does not cover this topic at all. (http://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/)

I found some web samples on how to do the same with older versions of spring, but those work with applicationcontext.xml. Please help.

Saintsimon answered 26/6, 2015 at 22:19 Comment(2)
Hi @riship89... I have implemented HandlerInterceptor successfully. It is working fine. Only the problem is, some internal HandlerInterceptor throws an exception before it is handled by the custom HandlerInterceptor. The afterCompletion() method which is overridden is called after the error is thrown by the internal implementation of HandlerInterceptor. Do you have solution for this?Sculptress
For future readers, here is the awesome video tutorial that explains how Spring interceptor works youtube.com/watch?v=DuMf8Nwb-9wBearwood
F
196

Since you're using Spring Boot, I assume you'd prefer to rely on Spring's auto configuration where possible. To add additional custom configuration like your interceptors, just provide a configuration or bean of WebMvcConfigurerAdapter.

Here's an example of a config class:

@Configuration
public class WebMvcConfig extends WebMvcConfigurerAdapter {

  @Autowired 
  HandlerInterceptor yourInjectedInterceptor;

  @Override
  public void addInterceptors(InterceptorRegistry registry) {
    registry.addInterceptor(...)
    ...
    registry.addInterceptor(getYourInterceptor()); 
    registry.addInterceptor(yourInjectedInterceptor);
    // next two should be avoid -- tightly coupled and not very testable
    registry.addInterceptor(new YourInterceptor());
    registry.addInterceptor(new HandlerInterceptor() {
        ...
    });
  }
}

NOTE do not annotate this with @EnableWebMvc, if you want to keep Spring Boots auto configuration for mvc.

Folksy answered 26/6, 2015 at 22:51 Comment(14)
Nice! Looks good! Any example of what goes inside registry.addInterceptor(...)? Just curious to know a sample of "..."Saintsimon
nothing special, just add your interceptor to the registryFolksy
coming back to this, can you post example implementation of "yourInjectedInceptor"?Saintsimon
use @Component annotation on yourInjectedInceptorStaphylo
@riship89 Checkout this for an example: mkyong.com/spring-mvc/spring-mvc-handler-interceptors-exampleGaudery
I'm hoping for help here... when I use the "myInjectedInterceptor" approach, any property placeholders I have in my interceptor, which are annotated with "@Value" are only getting my configured value if I do NOT declare a default. @Value("my.property:defaultValue") <-- uses default instead of configured value @Value("my.property") <-- uses configured value Any idea why?Showy
Using this way, I get getReader() has already been called for this request error. Is that because the request getting deserialized twice? How to overcome that issueBatik
Beware that this is not silver bullet for all HTTP requests (for example the spring-data-rest ones). If you want ALL do MappedInterceptor and map it to /**Cypro
I get error The type WebMvcConfigurerAdapter is deprecated. I am using Spring Web MVC 5.0.6Deoxyribonuclease
In Spring 5, just implement WebMvcConfigurer instead of extending WebMvcConfigurerAdapter. Since Java 8 interfaces allow default implementations, it is no longer required to use the adapter (which is why it's deprecated).Sylvanite
I have one question, I have implemented an Interceptor for my springboot app, and it is not executed, when calling addInterceptor I have added addPathsPatern("/**") for all paths. My question is does this Interceptor intercept all methods? Thank youBlas
This does not intercept requests to inbuilt endpoints such as /actuator/healthIronic
thanks for the call out to not add @EnableWebMvc. it was screwing up my spring-boot appMountie
can you elaborate why @EnableWebMvc causes problemDungeon
P
147

WebMvcConfigurerAdapter will be deprecated with Spring 5. From its Javadoc:

@deprecated as of 5.0 {@link WebMvcConfigurer} has default methods (made possible by a Java 8 baseline) and can be implemented directly without the need for this adapter

As stated above, what you should do is implementing WebMvcConfigurer and overriding addInterceptors method.

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new MyCustomInterceptor());
    }
}
Prairie answered 9/8, 2017 at 8:9 Comment(2)
Your answer is incomplete because it's missing the implementation of MyCustomInterceptorRaised
@AndriiDzhyrma, you have to write code that is comprehensible to a total beginner. Have a look at this complete answer and you'll get my point. https://mcmap.net/q/149351/-spring-boot-adding-http-request-interceptorsRaised
S
50

To add interceptor to a spring boot application, do the following

  1. Create an interceptor class

    public class MyCustomInterceptor implements HandlerInterceptor{
    
        //unimplemented methods comes here. Define the following method so that it     
        //will handle the request before it is passed to the controller.
    
        @Override
        public boolean preHandle(HttpServletRequest request,HttpServletResponse  response){
        //your custom logic here.
            return true;
        }
    }
    
  2. Define a configuration class

    @Configuration
    public class MyConfig extends WebMvcConfigurerAdapter{
        @Override
        public void addInterceptors(InterceptorRegistry registry){
            registry.addInterceptor(new MyCustomInterceptor()).addPathPatterns("/**");
        }
    }
    
  3. Thats it. Now all your requests will pass through the logic defined under preHandle() method of MyCustomInterceptor.

Shoe answered 8/2, 2017 at 12:24 Comment(2)
I have followed this way to intercept the signup requests come to my application in order to do some common validations. But the problem is i get the getReader() has already been called for this request error. Is there any simpler way to get over this without using a copy of the actual request ?Batik
When the pre-handler is called, the request body is not available, but only parameters, To do validation on the request body, the prefered way will be to use Aspect J and create AdviceQuintic
S
45

Since all responses to this make use of the now long-deprecated abstract WebMvcConfigurer Adapter instead of the WebMvcInterface (as already noted by @sebdooe), here is a working minimal example for a SpringBoot (2.1.4) application with an Interceptor:

Minimal.java:

@SpringBootApplication
public class Minimal
{
    public static void main(String[] args)
    {
        SpringApplication.run(Minimal.class, args);
    }
}

MinimalController.java:

@RestController
@RequestMapping("/")
public class Controller
{
    @GetMapping("/")
    @ResponseBody
    public ResponseEntity<String> getMinimal()
    {
        System.out.println("MINIMAL: GETMINIMAL()");

        return new ResponseEntity<String>("returnstring", HttpStatus.OK);
    }
}

Config.java:

@Configuration
public class Config implements WebMvcConfigurer
{
    //@Autowired
    //MinimalInterceptor minimalInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry)
    {
        registry.addInterceptor(new MinimalInterceptor());
    }
}

MinimalInterceptor.java:

public class MinimalInterceptor extends HandlerInterceptorAdapter
{
    @Override
    public boolean preHandle(HttpServletRequest requestServlet, HttpServletResponse responseServlet, Object handler) throws Exception
    {
        System.out.println("MINIMAL: INTERCEPTOR PREHANDLE CALLED");

        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception
    {
        System.out.println("MINIMAL: INTERCEPTOR POSTHANDLE CALLED");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception exception) throws Exception
    {
        System.out.println("MINIMAL: INTERCEPTOR AFTERCOMPLETION CALLED");
    }
}

works as advertised

The output will give you something like:

> Task :Minimal.main()

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.1.4.RELEASE)

2019-04-29 11:53:47.560  INFO 4593 --- [           main] io.minimal.Minimal                       : Starting Minimal on y with PID 4593 (/x/y/z/spring-minimal/build/classes/java/main started by x in /x/y/z/spring-minimal)
2019-04-29 11:53:47.563  INFO 4593 --- [           main] io.minimal.Minimal                       : No active profile set, falling back to default profiles: default
2019-04-29 11:53:48.745  INFO 4593 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
2019-04-29 11:53:48.780  INFO 4593 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2019-04-29 11:53:48.781  INFO 4593 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.17]
2019-04-29 11:53:48.892  INFO 4593 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2019-04-29 11:53:48.893  INFO 4593 --- [           main] o.s.web.context.ContextLoader            : Root WebApplicationContext: initialization completed in 1269 ms
2019-04-29 11:53:49.130  INFO 4593 --- [           main] o.s.s.concurrent.ThreadPoolTaskExecutor  : Initializing ExecutorService 'applicationTaskExecutor'
2019-04-29 11:53:49.375  INFO 4593 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2019-04-29 11:53:49.380  INFO 4593 --- [           main] io.minimal.Minimal                       : Started Minimal in 2.525 seconds (JVM running for 2.9)
2019-04-29 11:54:01.267  INFO 4593 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring DispatcherServlet 'dispatcherServlet'
2019-04-29 11:54:01.267  INFO 4593 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Initializing Servlet 'dispatcherServlet'
2019-04-29 11:54:01.286  INFO 4593 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Completed initialization in 19 ms
MINIMAL: INTERCEPTOR PREHANDLE CALLED
MINIMAL: GETMINIMAL()
MINIMAL: INTERCEPTOR POSTHANDLE CALLED
MINIMAL: INTERCEPTOR AFTERCOMPLETION CALLED
Selfeffacement answered 29/4, 2019 at 10:27 Comment(2)
But this will require you to implement all methods from the WebMvcConfigurer, right?Cerography
No, its a (Java 8) interface with empty default implementationsIncommodity
D
19

I found a good tutorial on this site on how to add request interceptors to specific controllers using annotations:

  1. Define the annotation
  2. Define the interceptor
  3. Add the interceptor to the path
  4. Use the annotation on the specific controller

https://programmer.group/how-do-spring-boot-2.x-add-interceptors.html

I know this question was how to add interceptors to all requests and that's answered already. I was searching the solution to add request interceptors to specific controllers using annotations but couldn't find a solution in stackoverflow. Decided add this content to this question instead of asking a new question.

Define the annotation NeedLogin.class

package com.example.helloSpringBoot.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;


@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface NeedLogin {
}

Then define the inceptor class

package com.example.helloSpringBoot.config;

import com.example.helloSpringBoot.annotation.NeedLogin;
import com.example.helloSpringBoot.util.WxUserInfoContext;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * Logon interceptor
 *
 * @Author: Java Fragment
 */
@Component
public class LoginInterceptor implements HandlerInterceptor {

    //This method is executed before accessing the interface. We only need to write the business logic to verify the login status here to verify the login status before the user calls the specified interface.
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        if (handler instanceof HandlerMethod) {
            NeedLogin needLogin = ((HandlerMethod) handler).getMethodAnnotation(NeedLogin.class);
            if (null == needLogin) {
                needLogin = ((HandlerMethod) handler).getMethod().getDeclaringClass()
                        .getAnnotation(NeedLogin.class);
            }
            // Check login if you have login validation annotations
            if (null != needLogin) {
                WxUserInfoContext curUserContext = (WxUserInfoContext) request.getSession()
                        .getAttribute("curUserContext");
                //If session No, not logged in.
                if (null == curUserContext) {
                    response.setCharacterEncoding("UTF-8");
                    response.getWriter().write("Not logged in!");
                    return false;
                }
            }

        }
        return true;
    }

    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {
    }

    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
    }
}

Then add the inceptor into the WebConfig

package com.example.helloSpringBoot.config;


import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * WebConfig
 *
 * @Author: Java Fragment
 *
 */
@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Autowired
    private LoginInterceptor loginInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // Custom interceptor, add intercept path and exclude intercept path
        registry.addInterceptor(loginInterceptor).addPathPatterns("/**");
    }
}

Finally you are free to use the new interceptor using the new annotation @NeedLogin

package com.example.helloSpringBoot.controller;

import com.example.helloSpringBoot.annotation.NeedLogin;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloController {

    /**
     * Testing does not require login
     *
     *
     */
    @RequestMapping("/testNoLogin")
    public String testNoLogin (){
        return "The call is successful, this interface does not need login validation!-Java Broken read!";
    }

    /**
     * Testing requires login
     *
     *
     */
    @NeedLogin
    @RequestMapping("/testNeedLogin")
    public String testNeedLogin (){
        return "testNeedLogin!";
    }
}
Diminished answered 10/7, 2021 at 10:14 Comment(0)
E
16

I had the same issue of WebMvcConfigurerAdapter being deprecated. When I searched for examples, I hardly found any implemented code. Here is a piece of working code.

create a class that extends HandlerInterceptorAdapter

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

import me.rajnarayanan.datatest.DataTestApplication;
@Component
public class EmployeeInterceptor extends HandlerInterceptorAdapter {
    private static final Logger logger = LoggerFactory.getLogger(DataTestApplication.class);
    @Override
    public boolean preHandle(HttpServletRequest request, 
            HttpServletResponse response, Object handler) throws Exception {

            String x = request.getMethod();
            logger.info(x + "intercepted");
        return true;
    }

}

then Implement WebMvcConfigurer interface

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import me.rajnarayanan.datatest.interceptor.EmployeeInterceptor;
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
    @Autowired
    EmployeeInterceptor employeeInterceptor ;

    @Override
    public void addInterceptors(InterceptorRegistry registry){
        registry.addInterceptor(employeeInterceptor).addPathPatterns("/employee");
    }
}
Effy answered 3/10, 2017 at 17:3 Comment(3)
how can u only override one method on an interface without compilation issues?Wilfordwilfred
@Wilfordwilfred I am also trying to see if we can only implement one method instead of all other methods not used in this case. Is it possible? Did you figured that out?Speaking
@arjun The others are implemented as default methods thanks to Java 8. This reasoning is, conveniently, documented in the deprecated class.Haik
T
7

You might also consider using the open source SpringSandwich library which lets you directly annotate in your Spring Boot controllers which interceptors to apply, much in the same way you annotate your url routes.

That way, no typo-prone Strings floating around -- SpringSandwich's method and class annotations easily survive refactoring and make it clear what's being applied where. (Disclosure: I'm the author).

http://springsandwich.com/

Tadeo answered 30/11, 2016 at 16:32 Comment(4)
That looks awesome! I've created an issue asking for SpringSandwich to be available in maven central to make it easier to use for projects that are building under CI or which are deploying via Heroku.Crab
Great. Is it available in maven central repository? Re - blogging springsandwich.com in my website with the reference of your git repository and referenceAnatolian
SpringSandwich is now in Maven CentralTadeo
Looks like this library has been abandonned since 2017Joshi
J
2

For tracking all the request response in the spring-boot (java) application you can create a filter class like this -

import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.util.ContentCachingRequestWrapper;
import org.springframework.web.util.ContentCachingResponseWrapper;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.Objects;
import java.util.stream.Collectors;

@Component
public class RequestResponseTracker extends OncePerRequestFilter {

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
        ContentCachingRequestWrapper requestWrapper = new ContentCachingRequestWrapper(request);
        ContentCachingResponseWrapper responseWrapper = new ContentCachingResponseWrapper(response);

        filterChain.doFilter(requestWrapper, responseWrapper);

        System.out.println("Request URI: " + request.getRequestURI());
        System.out.println("Request Headers: " + Collections.list(request.getHeaderNames()).stream()
                .map(headerName -> headerName + " -> " + request.getHeader(headerName)).collect(Collectors.toList()));
        System.out.println("Request Method: " + request.getMethod());
        System.out.println("Request Body: " + getStringValue(requestWrapper.getContentAsByteArray(),
                responseWrapper.getCharacterEncoding()));

        System.out.println("Response Code: " + response.getStatus());
        System.out.println("Response Body: " + getStringValue(responseWrapper.getContentAsByteArray(),
                responseWrapper.getCharacterEncoding()));
        System.out.println("Response Headers: " + response.getHeaderNames().stream()
                .map(headerName -> headerName + " -> " + response.getHeader(headerName)).collect(Collectors.toList()));

        responseWrapper.copyBodyToResponse();   // Don't forget to add this at the end
    }


    private String getStringValue(byte[] contentAsByteArray, String characterEncoding) {
        try {
            return new String(contentAsByteArray, 0, contentAsByteArray.length,
                    Objects.nonNull(characterEncoding) ? characterEncoding : StandardCharsets.UTF_8.name());
        }
        catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return "";
    }
}
Jaehne answered 14/9, 2022 at 9:55 Comment(0)
M
1

I will recommend the following ways to dynamically inject custom interceptors.
You don't need to add interceptors one by one.

CustomInterceptor:

public interface CustomInterceptor extends HandlerInterceptor {

default String[] excludePathPatterns() {
    return new String[]{
        "/*/*.html",
        "/*/*.css",
        "/*/*.js",
        "/*/*.png",
        "/*/*.xml",
        "/*/*.json",
        "/*/*.yaml",
        "/*/*.yml",
        "/swagger*/**"
      };
    }


    default String[] pathPatterns() {
        return new String[]{"/**"};
    }
}

WebMvcConfig:

@Slf4j
@Configuration
@RequiredArgsConstructor
@SuppressWarnings("NullableProblems")
@ConditionalOnProperty(name = AutoConfigConstants.ENABLE_MVC, havingValue = "true")
public class DefaultMvcAutoConfiguration implements WebMvcConfigurer {

    static {
        log.info(AutoConfigConstants.LOADING_MVC_AUTO_CONFIGURE);
    }

    private final List<CustomInterceptor> customInterceptors;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        WebMvcConfigurer.super.addInterceptors(registry);
        if (CollectionUtils.isNotEmpty(customInterceptors)) {
            customInterceptors.forEach(customInterceptor ->
                registry.addInterceptor(customInterceptor)
                    .addPathPatterns(customInterceptor.pathPatterns())      
         .excludePathPatterns(customInterceptor.excludePathPatterns()));
        }
    }
}
Mahmud answered 29/9, 2022 at 8:5 Comment(0)
T
-1

Below is an implementation I use to intercept each HTTP request before it goes out and the response which comes back. With this implementation, I also have a single point where I can pass any header value with the request.

public class HttpInterceptor implements ClientHttpRequestInterceptor {
private Logger logger = LoggerFactory.getLogger(this.getClass());
@Override
public ClientHttpResponse intercept(
        HttpRequest request, byte[] body,
        ClientHttpRequestExecution execution
) throws IOException {
    HttpHeaders headers = request.getHeaders();
    headers.add("Accept", MediaType.APPLICATION_JSON_UTF8_VALUE);
    headers.add("Content-Type", MediaType.APPLICATION_JSON_VALUE);
    traceRequest(request, body);
    ClientHttpResponse response = execution.execute(request, body);
    traceResponse(response);
    return response;
}

private void traceRequest(HttpRequest request, byte[] body) throws IOException {
    logger.info("===========================Request begin======================================");
    logger.info("URI         : {}", request.getURI());
    logger.info("Method      : {}", request.getMethod());
    logger.info("Headers     : {}", request.getHeaders() );
    logger.info("Request body: {}", new String(body, StandardCharsets.UTF_8));
    logger.info("==========================Request end=========================================");
}

private void traceResponse(ClientHttpResponse response) throws IOException {
    logger.info("============================Response begin====================================");
    logger.info("Status code  : {}", response.getStatusCode());
    logger.info("Status text  : {}", response.getStatusText());
    logger.info("Headers      : {}", response.getHeaders());
    logger.info("=======================Response end===========================================");
}}

Below is the Rest Template Bean

@Bean
public RestTemplate restTemplate(HttpClient httpClient)
{
    HttpComponentsClientHttpRequestFactory requestFactory =
            new HttpComponentsClientHttpRequestFactory();
    requestFactory.setHttpClient(httpClient);
    RestTemplate restTemplate=  new RestTemplate(requestFactory);
    List<ClientHttpRequestInterceptor> interceptors = restTemplate.getInterceptors();
    if (CollectionUtils.isEmpty(interceptors))
    {
        interceptors = new ArrayList<>();
    }
    interceptors.add(new HttpInterceptor());
    restTemplate.setInterceptors(interceptors);

    return restTemplate;
}
Tamaru answered 27/11, 2019 at 20:42 Comment(2)
OK but what you've actually done here is an interceptor FOR RestTemplate (i.e. when YOU make HTTP calls)... not an interceptor for Spring REST Controllers (i.e. when HTTP calls are made against your Spring app/REST-webservices).Haunch
The question is about HTTP calls to your controller not HTTP calls from your application which RestTemplate handles. Thanks to @Haunch comment.Restaurateur

© 2022 - 2024 — McMap. All rights reserved.