ShiroFilterFactoryBean, a key bean for the integration of shrio and spring

Keywords: Spring Apache Shiro

1. Let's first look at the inheritance relationship of ShiroFilterFactoryBean

public class ShiroFilterFactoryBean implements FactoryBean, BeanPostProcessor

The FactoryBean interface is implemented, which is an important interface in spring, so let's take a look at the interface methods implemented by this class

 public Object getObject() throws Exception {
        if (instance == null) {
            instance = createInstance();
        }
        return instance;
 }

This instance object is private AbstractShiroFilter instance; it is the filter spring will proxy

2. See the createInstance() method

protected AbstractShiroFilter createInstance() throws Exception {
		//Get the security management container of shrio
        SecurityManager securityManager = getSecurityManager();
        
        //Access to the filter chain manager is all the filters of shrio, in which path and corresponding filter or filter chain are stored
        FilterChainManager manager = createFilterChainManager();
        
        //Initialize the Ant style url matcher and pass it to the filter chain manager above
        PathMatchingFilterChainResolver chainResolver = new PathMatchingFilterChainResolver();
        chainResolver.setFilterChainManager(manager);
        
        //Initialize the portal filter through which all requests should go
        return new SpringShiroFilter((WebSecurityManager) securityManager, chainResolver);
    }
protected FilterChainManager createFilterChainManager() {

        DefaultFilterChainManager manager = new DefaultFilterChainManager();
        //filter provided by shrio
        //Defined in org.apache.shiro.web.filter.mgt.DefaultFilter class
        Map<String, Filter> defaultFilters = manager.getFilters();
        for (Filter filter : defaultFilters.values()) {
        	//Important, configure the three URLs we set for the corresponding Filter
        	//Three URLs: private string login url;
    		//		  private String successUrl;
    		//		  private String unauthorizedUrl;
            applyGlobalPropertiesIfNecessary(filter);
        }
		
		//Custom filter, same process as above
        Map<String, Filter> filters = getFilters();
        if (!CollectionUtils.isEmpty(filters)) {
            for (Map.Entry<String, Filter> entry : filters.entrySet()) {
                String name = entry.getKey();
                Filter filter = entry.getValue();
                applyGlobalPropertiesIfNecessary(filter);
                if (filter instanceof Nameable) {
                    ((Nameable) filter).setName(name);
                }
                manager.addFilter(name, filter, false);
            }
        }
		
		//Get custom filter rules
        Map<String, String> chains = getFilterChainDefinitionMap();
        if (!CollectionUtils.isEmpty(chains)) {
            for (Map.Entry<String, String> entry : chains.entrySet()) {
                String url = entry.getKey();
                String chainDefinition = entry.getValue();
                //Focus on traversal. See the following
                manager.createChain(url, chainDefinition);
            }
        }
        return manager;
}
public DefaultFilterChainManager() {
        this.filters = new LinkedHashMap<String, Filter>();
        this.filterChains = new LinkedHashMap<String, NamedFilterList>();
        addDefaultFilters(false);
  }
private void applyGlobalPropertiesIfNecessary(Filter filter) {
		//The filter inherits from AccessControlFilter to take effect. It can also be customized, which will cover the global
        applyLoginUrlIfNecessary(filter);
        //Only inheriting from the AuthenticationFilter can the filter work, or it can be customized, which will cover the global
        applySuccessUrlIfNecessary(filter);
        //filter inherits from AuthorizationFilter to take effect. It can also be customized, which will cover the global
        applyUnauthorizedUrlIfNecessary(filter);
 }
public void createChain(String chainName, String chainDefinition) {
		//String to array, comma separated
		String[] filterTokens = splitChainDefinition(chainDefinition);
        for (String token : filterTokens) {
        	// Continue to parse the filter, for example: roles[admin,user]
        	// After parsing, we get: {"roles", "admin,user"}
        	//roles is one of the shrio default filters
            String[] nameConfigPair = toNameConfigPair(token);
            //Add filter chain
            addToChain(chainName, nameConfigPair[0], nameConfigPair[1]);
        }
 }
public void addToChain(String chainName, String filterName, String chainSpecificFilterConfig) {
      
        Filter filter = getFilter(filterName);
        //The first parameter is our custom Ant style intercept Url
        //The second parameter is the corresponding filter
        //The third parameter is the interception rule. If "/ Edit" - > "perms [Edit]" is configured, the parameter value is edit
        //Look at the bottom.
        applyChainConfig(chainName, filter, chainSpecificFilterConfig);
        //For each Ant style Url we customize, there is a NamedFilterList object associated with it
        NamedFilterList chain = ensureChain(chainName);
        //Add the filter of Url to this object
        //A Url may correspond to multiple filters, so a NamedFilterList object may have more than one filter
        chain.add(filter);
 }
protected void applyChainConfig(String chainName, Filter filter, String chainSpecificFilterConfig) {
	  //You need to implement the filter of the PathConfigProcessor interface to configure the rules of the third parameter above
	  //So when we customize the filter, we try to inherit the existing implementation class or implement the interface
      if (filter instanceof PathConfigProcessor) {
     	((PathConfigProcessor) filter).processPathConfig(chainName, 			  chainSpecificFilterConfig);
      } 
 }
protected NamedFilterList ensureChain(String chainName) {
		//Try to get the chain object, not necessarily empty, because there may be the same chainName before
        NamedFilterList chain = getChain(chainName);
        if (chain == null) {
            chain = new SimpleNamedFilterList(chainName);
            this.filterChains.put(chainName, chain);
        }
        return chain;
}

The work of the FilterChainManager class is to define all filters, including the default and custom ones. The general idea is to set an Ant style url to a NamedFilterList, which is an interface. The implementation class is SimpleNamedFilterList, and a NamedFilterList object can include multiple filters. This is a custom configuration. We may set it at the same url Multiple filters, such as setting role permissions and setting operation permissions.

So how does shrio intercept requests?
Note that SpringShiroFilter is a filter, which is the entry class for all requests
Its parent class is AbstractShiroFilter instance. Pay attention to its doFilterInternal method

protected void doFilterInternal(ServletRequest servletRequest, ServletResponse servletResponse, final FilterChain chain)
            throws ServletException, IOException {

        Throwable t = null;

        try {
            final ServletRequest request = prepareServletRequest(servletRequest, servletResponse, chain);
            final ServletResponse response = prepareServletResponse(request, servletResponse, chain);

            final Subject subject = createSubject(request, response);
            
            //Key method
            subject.execute(new Callable() {
                public Object call() throws Exception {
                    updateSessionLastAccessTime(request, response);
                    //Obtain the corresponding chain according to the request and intercept the request
                    //Look at the bottom.
                    executeChain(request, response, chain);
                    return null;
                }
            });
        } catch (ExecutionException ex) {
            t = ex.getCause();
        } catch (Throwable throwable) {
            t = throwable;
        }

        if (t != null) {
            if (t instanceof ServletException) {
                throw (ServletException) t;
            }
            if (t instanceof IOException) {
                throw (IOException) t;
            }
        }
}
protected void executeChain(ServletRequest request, ServletResponse response, FilterChain origChain)
            throws IOException, ServletException {
        //Get chain and intercept
        //Look at the bottom.
        FilterChain chain = getExecutionChain(request, response, origChain);
        chain.doFilter(request, response);
}
protected FilterChain getExecutionChain(ServletRequest request, ServletResponse response, FilterChain origChain) {
        FilterChain chain = origChain;
		
		//Get FilterChainResolver object
		//Because the constructor of this class does not initialize it, it needs to be null
		//If it is empty, it means that there is no corresponding FilterChainResolver, and the original object will be returned directly
        FilterChainResolver resolver = getFilterChainResolver();
        if (resolver == null) {
            return origChain;
        }
		
		//The resolver object here is the object of the PathMatchingFilterChainResolver class
		//Look at the bottom.
        FilterChain resolved = resolver.getChain(request, response,   origChain);
        if (resolved != null) {
            chain = resolved;
        }
        return chain;
}
public FilterChain getChain(ServletRequest request, ServletResponse response, FilterChain originalChain) {
		//Return to the filter manager class. The default is DefaultFilterChainManager
		//Judge whether private map < string, namedfilterlist > filterchains is empty
        FilterChainManager filterChainManager = getFilterChainManager();
        //If it is empty, null will be returned directly, and the original chain will be used above
        if (!filterChainManager.hasChains()) {
            return null;
        }
		
		//Get the request uri excluding the application name, such as / admin
        String requestURI = getPathWithinApplication(request);
        for (String pathPattern : filterChainManager.getChainNames()) {
        	//Match one and return directly
        	//So we should consider the order when we define the uri rules
            if (pathMatches(pathPattern, requestURI)) {
            	//Look at the bottom.
                return filterChainManager.proxy(originalChain, pathPattern);
            }
        }
        return null;
}
protected boolean pathMatches(String pattern, String path) {
		//The default is the ant pathmatcher subclass
		//Other subclasses can be configured to handle matching rules
        PatternMatcher pathMatcher = getPathMatcher();
        //The matching method is too complex to be listed here
        //Just know what matches and return true
        return pathMatcher.matches(pattern, path);
}
//This is the DefaultFilterChainManager method
public FilterChain proxy(FilterChain original, String chainName) {
		//NamedFilterList is an interface with only one implementation class -- SimpleNamedFilterList
        NamedFilterList configured = getChain(chainName);
        //The implementation method is as follows
        return configured.proxy(original);
}
public FilterChain proxy(FilterChain orig) {
        return new ProxiedFilterChain(orig, this);
}
public ProxiedFilterChain(FilterChain orig, List<Filter> filters) {
		//Original chain
        this.orig = orig;
        //NamedFilterList object
        this.filters = filters;
        //The responsibility chain model is obvious
        this.index = 0;
}
//doFilter method of ProxiedFilterChain class
public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
        if (this.filters == null || this.filters.size() == this.index) {
            this.orig.doFilter(request, response);
        } else {
        	//First, use index and add to add
        	//this is the reference of itself and the template of responsibility chain mode
            this.filters.get(this.index++).doFilter(request, response, this);
        }
}

Summary: ShiroFilterFactoryBean integrated by shrio and spring is not a filter, but it will initialize a servlet container's filter. If spring is configured, DelegatingFilterProxy needs to be configured to proxy the filter, and the intercepted uri must be a path larger than the customized one containing the uri rules to be customized, otherwise it will not take effect. If spring boot is the project, It is unnecessary and the default is to intercept / *, just define ShiroFilterFactoryBean directly. In fact, this filter is only the entry of all requests. What is really processed is the default filter of shrio or our customized filter.

Published 14 original articles, won praise 1, visited 102
Private letter follow

Posted by chreez on Tue, 14 Jan 2020 02:04:44 -0800