Working principle of spring security filter

This article introduces the working principle of filter. The configuration is xml.

How does Filter go into execution logic

Initial configuration:

 <filter>
    <filter-name>springSecurityFilterChain</filter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
  </filter>

  <filter-mapping>
    <filter-name>springSecurityFilterChain</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>

The Delegating FilterProxy class inherits GenericFilterBean, which implements the Filter interface.

This configuration is the beginning of everything. After configuring this, the initialization method of Filterd will be executed when the project is started.

@Override
    public final void init(FilterConfig filterConfig) throws ServletException {
        Assert.notNull(filterConfig, "FilterConfig must not be null");
        if (logger.isDebugEnabled()) {
            logger.debug("Initializing filter '" + filterConfig.getFilterName() + "'");
        }

        this.filterConfig = filterConfig;

        // Set bean properties from init parameters.
        PropertyValues pvs = new FilterConfigPropertyValues(filterConfig, this.requiredProperties);
        if (!pvs.isEmpty()) {
            try {
                BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
                ResourceLoader resourceLoader = new ServletContextResourceLoader(filterConfig.getServletContext());
                Environment env = this.environment;
                if (env == null) {
                    env = new StandardServletEnvironment();
                }
                bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, env));
                initBeanWrapper(bw);
                bw.setPropertyValues(pvs, true);
            }
            catch (BeansException ex) {
                String msg = "Failed to set bean properties on filter '" +
                    filterConfig.getFilterName() + "': " + ex.getMessage();
                logger.error(msg, ex);
                throw new NestedServletException(msg, ex);
            }
        }

        // Let subclasses do whatever initialization they like.
        initFilterBean(); // This method

        if (logger.isDebugEnabled()) {
            logger.debug("Filter '" + filterConfig.getFilterName() + "' configured successfully");
        }
    }

In the initialization method, the initFilterBean method that initializes the Filter is executed. This method is implemented in Delegating Filter Proxy:

protected void initFilterBean() throws ServletException {
        synchronized (this.delegateMonitor) {
            if (this.delegate == null) {
                // If no target bean name specified, use filter name.
                if (this.targetBeanName == null) {
                    this.targetBeanName = getFilterName();
                }
                // Fetch Spring root application context and initialize the delegate early,
                // if possible. If the root application context will be started after this
                // filter proxy, we'll have to resort to lazy initialization.
                WebApplicationContext wac = findWebApplicationContext();
                if (wac != null) {
                    this.delegate = initDelegate(wac); //This method
                }
            }
        }
    }

In this initialization method, initDelegate method is called to initialize:

protected Filter initDelegate(WebApplicationContext wac) throws ServletException {
        String targetBeanName = getTargetBeanName();
        Assert.state(targetBeanName != null, "No target bean name set");
        Filter delegate = wac.getBean(targetBeanName, Filter.class);
        if (isTargetFilterLifecycle()) {
            delegate.init(getFilterConfig());
        }
        return delegate;
    }

In this method, we first get the targetBeanName, which is assigned in the constructor:

public DelegatingFilterProxy(String targetBeanName, @Nullable WebApplicationContext wac) {
        Assert.hasText(targetBeanName, "Target Filter bean name must not be null or empty");
        this.setTargetBeanName(targetBeanName);
        this.webApplicationContext = wac;
        if (wac != null) {
            this.setEnvironment(wac.getEnvironment());
        }
    }

This name is the name spring Security FilterChain configured in web.xml:

 <filter>
    <filter-name>springSecurityFilterChain</filter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
  </filter>

Spring Security Filter Chain is fixed and cannot be changed. If it is changed, it will report an error at startup. This is a built-in bean at spring startup. This bean is actually FilterChain Proxy.

If such a Filter is initialized, then the Filter chain is initialized.

When a request comes in, it enters FilterChainProxy to execute the doFilter method:

public void doFilter(ServletRequest request, ServletResponse response,
            FilterChain chain) throws IOException, ServletException {
        boolean clearContext = request.getAttribute(FILTER_APPLIED) == null;
        if (clearContext) {
            try {
                request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
                doFilterInternal(request, response, chain);
            }
            finally {
                SecurityContextHolder.clearContext();
                request.removeAttribute(FILTER_APPLIED);
            }
        }
        else {
            doFilterInternal(request, response, chain);
        }
    }

Get all filters first, and then execute the do Filter International method:

private void doFilterInternal(ServletRequest request, ServletResponse response,
            FilterChain chain) throws IOException, ServletException {

        FirewalledRequest fwRequest = firewall
                .getFirewalledRequest((HttpServletRequest) request);
        HttpServletResponse fwResponse = firewall
                .getFirewalledResponse((HttpServletResponse) response);

        List<Filter> filters = getFilters(fwRequest);

        if (filters == null || filters.size() == 0) {
            if (logger.isDebugEnabled()) {
                logger.debug(UrlUtils.buildRequestUrl(fwRequest)
                        + (filters == null ? " has no matching filters"
                                : " has an empty filter list"));
            }

            fwRequest.reset();

            chain.doFilter(fwRequest, fwResponse);

            return;
        }

        // Finally, execute the following code
        VirtualFilterChain vfc = new VirtualFilterChain(fwRequest, chain, filters);
        vfc.doFilter(fwRequest, fwResponse);
    }

VirtualFilterChain is an anonymous inner class:

private static class VirtualFilterChain implements FilterChain {
        private final FilterChain originalChain;
        private final List<Filter> additionalFilters;
        private final FirewalledRequest firewalledRequest;
        private final int size;
        private int currentPosition = 0;

        private VirtualFilterChain(FirewalledRequest firewalledRequest,
                FilterChain chain, List<Filter> additionalFilters) {
            this.originalChain = chain;
            this.additionalFilters = additionalFilters;
            this.size = additionalFilters.size();
            this.firewalledRequest = firewalledRequest;
        }

        @Override
        public void doFilter(ServletRequest request, ServletResponse response)
                throws IOException, ServletException {
            if (currentPosition == size) {
                if (logger.isDebugEnabled()) {
                    logger.debug(UrlUtils.buildRequestUrl(firewalledRequest)
                            + " reached end of additional filter chain; proceeding with original chain");
                }

                // Deactivate path stripping as we exit the security filter chain
                this.firewalledRequest.reset();

                originalChain.doFilter(request, response);
            }
            else {
                currentPosition++;

                Filter nextFilter = additionalFilters.get(currentPosition - 1);

                if (logger.isDebugEnabled()) {
                    logger.debug(UrlUtils.buildRequestUrl(firewalledRequest)
                            + " at position " + currentPosition + " of " + size
                            + " in additional filter chain; firing Filter: '"
                            + nextFilter.getClass().getSimpleName() + "'");
                }

                nextFilter.doFilter(request, response, this);
            }
        }
    }

The logic executed by the filter set is in the doFilter method of VirtualFilterChain.

How does filter execute

It says how to get into the filter's execution logic. Here's how and why a filter is executed.
The doFilter method in VirtualFilterChain can execute all filter s.

Here's an example to simulate the filter's execution logic.
Define the FilterChain interface, the Filter interface:

public interface Filter {

    void doFilter(String username, int age, FilterChain filterChain);
}
public interface FilterChain {

    void doFilter(String username, int age);
}

Define two Filter implementations:

public class NameFilter implements Filter {

    @Override
    public void doFilter(String username, int age, FilterChain filterChain) {

        username = username + 1;
        System.out.println("username: " + username + "   age: " + age);
        System.out.println("Executing: NameFilter");
        filterChain.doFilter(username, age);
    }
}
public class AgeFilter implements Filter {

    @Override
    public void doFilter(String username, int age, FilterChain filterChain) {

        age += 10;
        System.out.println("username: " + username + "   age: " + age);
        System.out.println("Executing: AgeFilter");
        filterChain.doFilter(username, age);
    }
}

Define a FilterChain implementation:

public class FilterChainProxy implements FilterChain {


    private int position = 0;
    private int size = 0;
    private List<Filter> filterList = new ArrayList<>();

    public void addFilter(Filter filter) {

        filterList.add(filter);
        size++;
    }

    @Override
    public void doFilter(String username, int age) {

        if (size == position) {
            System.out.println("End of filter chain execution");
        } else {

            Filter filter = filterList.get(position);
            position++;
            filter.doFilter(username, age, this);
        }
    }
}

Test Filter implementation:

public class FilterTest {

    public static void main(String[] args) {

        FilterChainProxy proxy = new FilterChainProxy();
        proxy.addFilter(new NameFilter());
        proxy.addFilter(new AgeFilter());

        proxy.doFilter("Zhang San", 0);
    }
}
=======
username: Zhang San1 age: 0
 Executing: NameFilter
 username: Zhang San1 age: 10
 Executing: AgeFilter
 End of filter chain execution

In this execution logic, the most important thing is that this is a good initialized instance of FilterChain, in this test case, this is FilterChain Proxy.

When the doFilter method of FilterChain Proxy is executed, the initial parameters username and age are passed in. After entering the method, the corresponding filter is taken out according to the position. The first entry position is 0. The doFilter method of the filter is executed. Note that the doFilter method of the filter passes in an additional parameter of this parameter. It is a good example of filter Chain initialization. In the method of doFilter in Filter, the doFilter method of filter Chain is finally executed, which is equivalent to the second call of doFilter method of filter Chain instance. At this time, posotion is 1, and then the doFilter method of filter is executed until all filters are executed, and the whole execution process is completed. End.

The execution logic of VirtualFilterChain's doFilter method is basically the same as that in this test case.
This completes the execution of the entire filter chain.

summary

In the past, when you used Filter, you were very puzzled about how the filter works. Until today, you have solved this puzzle.

Keywords: Java Spring xml firewall

Added by icyfire624 on Thu, 25 Jul 2019 06:10:54 +0300