Progress

    0%

    Real-World Usage: How Spring Security Uses Filters and Proxies

    Learning Objectives#

    • Trace a request from the servlet container through Spring Security's filter chain to a controller, naming the structural pattern at each hop.
    • Explain DelegatingFilterProxy and FilterChainProxy as proxies, and the security filters as decorators of the request and response.
    • Connect Spring's AOP proxies, JDK dynamic and CGLIB, to the Proxy pattern, and predict the failure modes that follow from them.

    Introduction#

    This is the chapter's payoff article. The seven structural patterns have been drawn in clean diagrams, and this is where you meet them in a framework you will actually run, Spring Security. It is a structural pattern showroom, and it does not name any of the patterns. DelegatingFilterProxy, FilterChainProxy, SecurityContextHolderAwareRequestWrapper, they are all pattern names wearing class names.

    The skill this chapter has been building, recognizing the shape and naming the intent, is exactly what you need to read a request through this framework without getting lost in the acronyms.

    Problem Statement#

    The failure is the one every Spring developer hits eventually. You add a security filter to the chain and it does nothing. Or you annotate a method @PreAuthorize and it is bypassed. Or you log the transaction inside a service method and the outer transaction never rolls back. In every case the code looks right, the annotation is right, and the behavior is wrong, because there is a layer of proxies and filters between your code and what you think you wrote.

    You cannot debug what you cannot see. Until you know that your bean is wrapped in a proxy, that the request is being decorated as it passes through filters, and that the filter chain has an order that matters, Spring Security looks like magic with occasional bugs. The patterns are the map. This article draws it.

    Core Concept#

    The servlet filter chain#

    Everything starts with the servlet container, Tomcat or Jetty. It receives an HTTP request and passes it through a chain of javax.servlet.Filters before it ever reaches a servlet. Each filter can inspect the request, wrap it, and pass it on:

    public class TimingFilter implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { long start = System.nanoTime(); chain.doFilter(request, response); long elapsed = System.nanoTime() - start; log.info("handled in {} ns", elapsed); } }

    chain.doFilter(...) passes the request to the next filter, and when the last one calls it, the request reaches the servlet. The return path unwinds in reverse. This is Chain of Responsibility, a behavioral pattern that the next chapter will cover properly, but structurally it is the container that holds the chain.

    DelegatingFilterProxy: a proxy across two worlds#

    Here is the first pattern in disguise. Spring Security's filters are Spring beans, they need dependency injection, a Spring context, and lifecycle management. The servlet container knows nothing about Spring beans. Something has to bridge the two, and that something is DelegatingFilterProxy, a servlet filter that the container can instantiate, whose only job is to look up a Spring bean by name and delegate to it:

    public class DelegatingFilterProxy implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { Filter delegate = findBean("springSecurityFilterChain", Filter.class); delegate.doFilter(request, response, chain); } }

    This is a Proxy in the purest sense. It preserves the Filter interface, it stands in for another Filter it may not have loaded yet, and it controls when and how the real object gets involved. The client, the servlet container, cannot tell it is talking to a proxy, and the proxy handles the lookup lazily because the Spring context is bootstrapped after the container starts. Deferred, protected, hidden: all three proxy jobs in one class.

    FilterChainProxy: the chain that owns the chain#

    DelegatingFilterProxy looks up a bean named springSecurityFilterChain, and that bean is an instance of FilterChainProxy. Another proxy, this one is a Spring bean that appears to the container as a single filter and internally dispatches to a list of SecurityFilterChains, one per configuration. Each SecurityFilterChain is a list of security filters in order: CsrfFilter, UsernamePasswordAuthenticationFilter, AuthorizationFilter, and the rest.

    The order is load-bearing. CSRF protection must run before authentication can trust the request, authentication must run before authorization can check the principal. Spring gives you the ordering rules, but the reason they exist is structural: these filters are a chain, and a chain is only as correct as its order.

    The filters as decorators#

    Individual security filters are Decorators of the request and response. ContentCachingRequestWrapper wraps the request to buffer its body so it can be read twice, an application reads it once and the framework logs it after. SecurityContextHolderAwareRequestWrapper wraps the request to add security convenience methods like isUserInRole. Both preserve the HttpServletRequest interface, hold the wrapped request, add behavior, and delegate the rest. That is the Decorator pattern, and the servlet wrapper classes exist precisely so this wrapping is legal, because wrapping a concrete request class would break everything that casts to HttpServletRequest.

    The AOP proxies#

    Method security and transactions run on the third structural pattern: the AOP proxy. When Spring needs to apply an aspect to a bean, it does not modify your class. It creates a proxy: a JDK dynamic proxy if the bean implements an interface, a CGLIB subclass otherwise. Your @PreAuthorize("hasRole('ADMIN')") method is wrapped, and the proxy checks authorization before calling the real method:

    public class MethodSecurityInterceptor { public Object invoke(MethodInvocation invocation) throws Throwable { checkAccess(invocation); return invocation.proceed(); } }

    This is a protection proxy at industrial scale. It also explains the classic Spring gotchas. Calling an annotated method from within the same class bypasses the proxy, because you are holding the plain object, not the injected proxy. A final class or a final method cannot be proxied by CGLIB, so the security just does not apply. Knowing the pattern means these are not mysteries, they are the proxy's known edges.

    Diagram: request through the security filter chain

    HTTP Request enters pipeline DelegatingFilterProxy looks up Spring bean FilterChainProxy matches a chain SecurityFilterChain CSRF, AuthN, AuthZ filters Servlet handles request response

    The request descends through two proxies into the chain of filters, then reaches the servlet. The dashed path on the right is the response unwinding back the same way, which is why a filter's cleanup code, written after chain.doFilter(...), runs on the way back out.

    Real Production Usage#

    This article is the real production usage section. The patterns are not abstract here: DelegatingFilterProxy and FilterChainProxy ship in spring-web, the filter chain order is configured in every Spring Boot security setup, ContentCachingRequestWrapper and SecurityContextHolderAwareRequestWrapper are in spring-security-web, and the AOP proxies behind @PreAuthorize, @Transactional, and @Cacheable are in every Spring application you have run. When you understand the patterns, a Spring Boot security configuration stops being XML you copy and becomes a description of which proxy wraps which object in which order.

    Common Mistakes#

    Treating filter order as optional. The chain is only as correct as its order, and reordering filters, or registering a raw servlet filter in the wrong position, silently disables a guarantee. The @Order on your filter is the chain's correctness, not a cosmetic hint.

    Expecting self-invocation to go through the proxy. this.doSomething() inside a bean is a call to the plain object, no proxy, no transaction, no security. The moment you expect an annotation to apply to an internal call, you have forgotten the proxy pattern you are standing inside.

    Assuming CGLIB can proxy anything. final classes and final methods defeat CGLIB. If a bean with @PreAuthorize is final, the annotation is a no-op, and the compiler will not warn you. The proxy's constraint is your constraint.

    Interview Perspective#

    This article is the answer to "how do these patterns show up in a real framework?" The strong answer is concrete: name DelegatingFilterProxy as a proxy bridging the container and the Spring context, name the filter chain as a chain of decorators over the request, name the AOP proxy as the protection proxy behind method security. A weak answer recites pattern definitions. A strong answer explains why Spring's transaction proxy exists and what it cannot do.

    The interviewers probe the proxy edge cases hardest, because those are the production bugs: the self-invocation gap, the CGLIB final-class gap, the filter ordering gap. If you can explain why those gaps exist in terms of the pattern, you have proven you understand both the pattern and the framework.

    Common follow-ups:

    • "Why does a self-invoked @Transactional method not open a transaction?"
    • "When does Spring use a JDK dynamic proxy and when does it use CGLIB, and what does each choice imply about your beans?"

    Knowledge Check#

    1. Trace a request from the container to a controller and name every structural pattern the request passes through, including the response path.
    2. A final service class annotated with @PreAuthorize is injected and its method called. What happens, and what is the proxy telling you about the class?
    3. A custom servlet filter is registered before Spring Security's filter and wraps the request. Explain the ordering requirement and what breaks if the wrappers collide.

    Key Takeaways#

    • Spring Security is a structural pattern showroom: proxies, decorators, and a chain of responsibility, none of them labeled as such.
    • DelegatingFilterProxy and FilterChainProxy are proxies that bridge the container and the Spring context, and their laziness is a proxy job.
    • The security filters are decorators of the request and response, which is why the servlet wrapper classes exist.
    • AOP proxies are protection proxies, and the self-invocation, final-class, and ordering gaps are the pattern's known edges.
    • Filter order is the chain's correctness, and every annotation that silently does nothing is a proxy you forgot.

    What's Next#

    This closes Chapter 7 on Structural Design Patterns, and Chapter 8 shifts the frame again, this time from shape to interaction. Behavioral Design Patterns are about how objects cooperate at runtime: Strategy swapping an algorithm, Observer broadcasting change, Command packaging an action, State letting an object change its behavior as it changes its state, and Chain of Responsibility, the pattern the Spring Security filter chain is made of. The mental move is to stop asking how objects are arranged and start asking how they behave and talk to each other when the system is actually running.

    Want to Master Spring Boot and Land Your Dream Job?

    Struggling with coding interviews? Learn Data Structures & Algorithms (DSA) with our expert-led course. Build strong problem-solving skills, write optimized code, and crack top tech interviews with ease

    Learn more

    Last updated on Aug 18, 2026

    Was it helpful?

    Subscribe to our newsletter

    Read articles from Coding Shuttle directly inside your inbox. Subscribe to the newsletter, and don't miss out.