Spring Boot Web - Filter

Seunghwan Choi·2025년 8월 17일

Java Backend

목록 보기
15/16

Filter is part of the Servlet API. In a Spring Boot web app, filters sit in the Servlet container chain and can intercept every HTTP request/response before it reaches your controller (and after it leaves)

What filters can do

  • Request preprocessing
    - Logging request details (URL, headers, body)
    • Authentication / authorization checks
    • Input validation / sanitization
  • Response preprocessing
    - Adding headers (e.g. CORS, security headers)
    • Compressing responses
    • Transforming output

Filter workflow in Spring Boot

  1. Client sends HTTP request ->
  2. Filter(s) intercept request ->
  3. Controller (e.g. @RestController) processes it ->
  4. Filter(s) intercept response on the way back
  5. Response sent to client
    So filters wrap around our app like middleware

Example

  • UserApiController
@Slf4j
@RestController
@RequestMapping("/api/user")
public class UserApiController {
    
    @PostMapping("")
    public void register(
            @RequestBody
            UserRequest userRequest
    ){
        log.info("{}", userRequest);
    }
}
  • UserRequest
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@ToString
@JsonNaming(value = PropertyNamingStrategy.SnakeCaseStrategy.class)
public class UserRequest {

    private String name;
    private String phoneNumber;
    private String email;
    private Integer age;
}
  • For demo purposes, we send a POST method with invalid values in the JSON body:
{
  "user_name": "Choi Seunghwan",
  "phoneNumber": "020-2231-4213",
  "age": 100,
  "email": "choi@gmail.com"
}
  • And with no @Valid or any other validation method, a status code 200 will be returned but object mapping will not be done properly. Below userRequest is logged (We only see the parsed UserRequest object):
UserRequest(name=null, phoneNumber=null, email=choi@gmail.com, age=100)
  • The filter sees the raw HTTP request before Spring parses it - headers, method, URI, and raw JSON body, which can be useful for debugging malformed requests, auditing exact input payloads, detecting suspicious/malicious traffic.

  • LoggingFilter

@Slf4j
@Component
public class LoggerFilter implements Filter{

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        //before entry
        log.info("before entry");

        var req = new ContentCachingRequestWrapper((HttpServletRequest) request);
        var res = new ContentCachingResponseWrapper((HttpServletResponse) response);

        chain.doFilter(req, res);

        var reqJSON = new String(req.getContentAsByteArray());
        log.info("req : {}", reqJSON);
        var resJSON = new String(res.getContentAsByteArray());
        log.info("res : {}", resJSON);

        log.info("after entry");
        //after entry
        
        res.copyBodyToResponse();
    }
}
  • Logs incoming requests and outgoing responses around our Spring controller calls
var req = new ContentCachingRequestWrapper((ContentCachingRequestWrapper) request);
var res = new ContentCachingResponseWrapper((ContentCachingResponseWrapper) response);
  • Wraps the original request/response in caching wrappers so their bodies can be read multiple times.
  • ContentCachingRequestWrapper caches the request body so it can be read after Spring consumes it for JSON deserialization.
  • ContentCachingResponseWrapper caches the response body so it can be logged after the controller executes.
  • Why we can't use HTTPServletRequestWrapper:
    - HTTPServletRequestWrapper (and HttpServletResponseWrapper) do not cache the body by default.
    • HTTP request bodies in Java are streams.
    • We can read them only once with getReader() or getInputStream().
    • If we call getReader() in the filter on a plain HTTPServletRequestWrapper, Spring won't be able to read it later, leading to IllegalStateException: getReader() has already been called for this request.
    • On the other hand ContentCachingRequestWrapper buffers the request body in memory.
    • We can safely read it multiple times: once in our filter, once in Spring's controller for JSON binding.
    • Without it, calling getReader() in our filter consumes the stream.
  • Output:
2025-08-17T20:25:57.257+08:00  INFO 66375 --- [filter] [nio-8080-exec-1] com.example.filter.filter.LoggerFilter   : req : {
  "user_name": "Choi Seunghwan",
  "phoneNumber": "020-2231-4213",
  "age": 100,
  "email": "choi@gmail.com"
}

2025-08-17T20:25:57.258+08:00  INFO 66375 --- [filter] [nio-8080-exec-1] com.example.filter.filter.LoggerFilter   : res : {"name":null,"phone_number":null,"email":"choi@gmail.com","age":100}
2025-08-17T20:25:57.259+08:00  INFO 66375 --- [filter] [nio-8080-exec-1] com.example.filter.filter.LoggerFilter   : after entry

0개의 댓글