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.