Shiro框架:ShiroFilterFactoryBean过滤器源码解析

news2024/11/17 23:45:21

目录

1.Shiro自定义拦截器SpringShiroFilter

1.1 ShiroFilterFactoryBean解析

1.1.1 实现FactoryBean接口

1.1.2 实现BeanPostProcessor接口

 1.2 SpringShiroFilter解析

1.2.1 OncePerRequestFilter过滤逻辑实现

1.2.2 AbstractShiroFilter过滤逻辑实现

1.2.2.1 创建Subject对象

1.2.2.2 更新Session最后访问时间

1.2.2.3 执行过滤链 

1.2.2.3.1  构造过滤链

1.2.2.3.2  执行构造好的过滤链

2.SpringShiroFilter如何添加到Servlet Filter中

2.1 常见的添加Servlet Filter方式

2.1.1 方式一:显示定义FilterRegistrationBean

2.1.2 方式二:显示定义ShiroFilterFactoryBean

2.2 添加Servlet Filter方式源码解析

2.2.1 addServletContextInitializerBeans

2.2.2 addAdaptableBeans


Shiro通过添加Servlet Filter的方式,提供了登录验证(Authentication)、访问控制(Authorization)以及Session管理等功能,极大的简化了Spring项目中登录鉴权模块的开发工作。

下面通过ShiroFilterFactoryBean作为切入点,详细分析下自定义拦截器SpringShiroFilter的处理流程;并通过源码解析,跟踪SpringShiroFilter是如何添加到Servlet Filter中的;

1.Shiro自定义拦截器SpringShiroFilter

SpringShiroFilter的初始化和构造逻辑是ShiroFilterFactoryBean完成的,首先看一下ShiroFilterFactoryBean的处理逻辑;

1.1 ShiroFilterFactoryBean解析

ShiroFilterFactoryBean同时实现了FactoryBean和BeanPostProcessor接口,因此其具体实现也包含了这2部分功能,下面分别进行说明;

1.1.1 实现FactoryBean接口

    /**
     * Lazily creates and returns a {@link AbstractShiroFilter} concrete instance via the
     * {@link #createInstance} method.
     *
     * @return the application's Shiro Filter instance used to filter incoming web requests.
     * @throws Exception if there is a problem creating the {@code Filter} instance.
     */
    public Object getObject() throws Exception {
        if (instance == null) {
            instance = createInstance();
        }
        return instance;
    }

    /**
     * Returns <code>{@link org.apache.shiro.web.servlet.AbstractShiroFilter}.class</code>
     *
     * @return <code>{@link org.apache.shiro.web.servlet.AbstractShiroFilter}.class</code>
     */
    public Class getObjectType() {
        return SpringShiroFilter.class;
    }

    /**
     * Returns {@code true} always.  There is almost always only ever 1 Shiro {@code Filter} per web application.
     *
     * @return {@code true} always.  There is almost always only ever 1 Shiro {@code Filter} per web application.
     */
    public boolean isSingleton() {
        return true;
    }

    /**
     * This implementation:
     * <ol>
     * <li>Ensures the required {@link #setSecurityManager(org.apache.shiro.mgt.SecurityManager) securityManager}
     * property has been set</li>
     * <li>{@link #createFilterChainManager() Creates} a {@link FilterChainManager} instance that reflects the
     * configured {@link #setFilters(java.util.Map) filters} and
     * {@link #setFilterChainDefinitionMap(java.util.Map) filter chain definitions}</li>
     * <li>Wraps the FilterChainManager with a suitable
     * {@link org.apache.shiro.web.filter.mgt.FilterChainResolver FilterChainResolver} since the Shiro Filter
     * implementations do not know of {@code FilterChainManager}s</li>
     * <li>Sets both the {@code SecurityManager} and {@code FilterChainResolver} instances on a new Shiro Filter
     * instance and returns that filter instance.</li>
     * </ol>
     *
     * @return a new Shiro Filter reflecting any configured filters and filter chain definitions.
     * @throws Exception if there is a problem creating the AbstractShiroFilter instance.
     */
    protected AbstractShiroFilter createInstance() throws Exception {

        log.debug("Creating Shiro Filter instance.");

        SecurityManager securityManager = getSecurityManager();
        if (securityManager == null) {
            String msg = "SecurityManager property must be set.";
            throw new BeanInitializationException(msg);
        }

        if (!(securityManager instanceof WebSecurityManager)) {
            String msg = "The security manager does not implement the WebSecurityManager interface.";
            throw new BeanInitializationException(msg);
        }

        FilterChainManager manager = createFilterChainManager();

        //Expose the constructed FilterChainManager by first wrapping it in a
        // FilterChainResolver implementation. The AbstractShiroFilter implementations
        // do not know about FilterChainManagers - only resolvers:
        PathMatchingFilterChainResolver chainResolver = new PathMatchingFilterChainResolver();
        chainResolver.setFilterChainManager(manager);

        //Now create a concrete ShiroFilter instance and apply the acquired SecurityManager and built
        //FilterChainResolver.  It doesn't matter that the instance is an anonymous inner class
        //here - we're just using it because it is a concrete AbstractShiroFilter instance that accepts
        //injection of the SecurityManager and FilterChainResolver:
        return new SpringShiroFilter((WebSecurityManager) securityManager, chainResolver);
    }

    protected FilterChainManager createFilterChainManager() {

        DefaultFilterChainManager manager = new DefaultFilterChainManager();
        Map<String, Filter> defaultFilters = manager.getFilters();
        //apply global settings if necessary:
        for (Filter filter : defaultFilters.values()) {
            applyGlobalPropertiesIfNecessary(filter);
        }

        //Apply the acquired and/or configured filters:
        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);
                }
                //'init' argument is false, since Spring-configured filters should be initialized
                //in Spring (i.e. 'init-method=blah') or implement InitializingBean:
                manager.addFilter(name, filter, false);
            }
        }

        //build up the chains:
        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();
                manager.createChain(url, chainDefinition);
            }
        }

        return manager;
    }

如上,通过初始化SecurityManager,DefaultFilterChainManager以及PathMatchingFilterChainResolver,完成了SpringShiroFilter的单例构造;

1.1.2 实现BeanPostProcessor接口

    /**
     * Inspects a bean, and if it implements the {@link Filter} interface, automatically adds that filter
     * instance to the internal {@link #setFilters(java.util.Map) filters map} that will be referenced
     * later during filter chain construction.
     */
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        if (bean instanceof Filter) {
            log.debug("Found filter chain candidate filter '{}'", beanName);
            Filter filter = (Filter) bean;
            applyGlobalPropertiesIfNecessary(filter);
            getFilters().put(beanName, filter);
        } else {
            log.trace("Ignoring non-Filter bean '{}'", beanName);
        }
        return bean;
    }

    /**
     * Does nothing - only exists to satisfy the BeanPostProcessor interface and immediately returns the
     * {@code bean} argument.
     */
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }

在postProcessBeforeInitialization方法中,通过拦截Filter类型的bean,完成全局属性的注入,包括设置:loginUrl、successUrl、unauthorizedUrl; 

 1.2 SpringShiroFilter解析

SpringShiroFilter类继承结构如下,实现了顶层接口Servlet Filter,因此SpringShiroFilter作为Shiro实现的自定义Filter,可以注册到Servlet Filter中执行过滤器逻辑(第2部分进行具体说明);

在如上的继承层次结构中:

  • Filter作为Servlet过滤器顶层接口,声明了doFilter过滤方法

    /* @param request  The request to process
     * @param response The response associated with the request
     * @param chain    Provides access to the next filter in the chain for this
     *                 filter to pass the request and response to for further
     *                 processing
     *
     * @throws IOException if an I/O error occurs during this filter's
     *                     processing of the request
     * @throws ServletException if the processing fails for any other reason
     */
    public void doFilter(ServletRequest request, ServletResponse response,
            FilterChain chain) throws IOException, ServletException;
  • AbstractFilter抽象类支持FilterConfig初始化
  • NameableFilter实现了Nameable,内部实现主要完成了过滤器name的解析
  • OncePerRequestFilter见名知意,实现了“单次请求,一次过滤”的语义
  • AbstractShiroFilter通过注入WebSecurityManager和FilterChainResolver完成Shiro内部的过滤处理逻辑 

下面着重分析下doFilter方法在OncePerRequestFilter和AbstractShiroFilter的过滤逻辑实现;

1.2.1 OncePerRequestFilter过滤逻辑实现

为了实现“单次请求,一次过滤”的语义,OncePerRequestFilter的过滤逻辑中是通过在ServletRequest中记录标志位属性值的方式实现的:

通过判断ServletRequest中是否已经设置了属性Key为alreadyFilteredAttributeName值的方式,判断该filter是否已经执行了过滤逻辑,已执行则跳过,未执行则执行过滤;

并通过引入抽象方法doFilterInternal完成内部过滤逻辑,交由子类具体实现;

具体过滤逻辑实现如下:

    /**
     * This {@code doFilter} implementation stores a request attribute for
     * "already filtered", proceeding without filtering again if the
     * attribute is already there.
     *
     * @see #getAlreadyFilteredAttributeName
     * @see #shouldNotFilter
     * @see #doFilterInternal
     */
    public final void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
        String alreadyFilteredAttributeName = getAlreadyFilteredAttributeName();
        if ( request.getAttribute(alreadyFilteredAttributeName) != null ) {
            log.trace("Filter '{}' already executed.  Proceeding without invoking this filter.", getName());
            filterChain.doFilter(request, response);
        } else //noinspection deprecation
            if (/* added in 1.2: */ !isEnabled(request, response) ||
                /* retain backwards compatibility: */ shouldNotFilter(request) ) {
            log.debug("Filter '{}' is not enabled for the current request.  Proceeding without invoking this filter.",
                    getName());
            filterChain.doFilter(request, response);
        } else {
            // Do invoke this filter...
            log.trace("Filter '{}' not yet executed.  Executing now.", getName());
            request.setAttribute(alreadyFilteredAttributeName, Boolean.TRUE);

            try {
                doFilterInternal(request, response, filterChain);
            } finally {
                // Once the request has finished, we're done and we don't
                // need to mark as 'already filtered' any more.
                request.removeAttribute(alreadyFilteredAttributeName);
            }
        }
    }

    /**
     * Return name of the request attribute that identifies that a request has already been filtered.
     * <p/>
     * The default implementation takes the configured {@link #getName() name} and appends &quot;{@code .FILTERED}&quot;.
     * If the filter is not fully initialized, it falls back to the implementation's class name.
     *
     * @return the name of the request attribute that identifies that a request has already been filtered.
     * @see #getName
     * @see #ALREADY_FILTERED_SUFFIX
     */
    protected String getAlreadyFilteredAttributeName() {
        String name = getName();
        if (name == null) {
            name = getClass().getName();
        }
        return name + ALREADY_FILTERED_SUFFIX;
    }

1.2.2 AbstractShiroFilter过滤逻辑实现

继承了抽象类OncePerRequestFilter,并实现了方法doFilterInternal,完成Shiro具体过滤逻辑嵌入,具体实现如下:

    /**
     * {@code doFilterInternal} implementation that sets-up, executes, and cleans-up a Shiro-filtered request.  It
     * performs the following ordered operations:
     * <ol>
     * <li>{@link #prepareServletRequest(ServletRequest, ServletResponse, FilterChain) Prepares}
     * the incoming {@code ServletRequest} for use during Shiro's processing</li>
     * <li>{@link #prepareServletResponse(ServletRequest, ServletResponse, FilterChain) Prepares}
     * the outgoing {@code ServletResponse} for use during Shiro's processing</li>
     * <li> {@link #createSubject(javax.servlet.ServletRequest, javax.servlet.ServletResponse) Creates} a
     * {@link Subject} instance based on the specified request/response pair.</li>
     * <li>Finally {@link Subject#execute(Runnable) executes} the
     * {@link #updateSessionLastAccessTime(javax.servlet.ServletRequest, javax.servlet.ServletResponse)} and
     * {@link #executeChain(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain)}
     * methods</li>
     * </ol>
     * <p/>
     * The {@code Subject.}{@link Subject#execute(Runnable) execute(Runnable)} call in step #4 is used as an
     * implementation technique to guarantee proper thread binding and restoration is completed successfully.
     *
     * @param servletRequest  the incoming {@code ServletRequest}
     * @param servletResponse the outgoing {@code ServletResponse}
     * @param chain           the container-provided {@code FilterChain} to execute
     * @throws IOException                    if an IO error occurs
     * @throws javax.servlet.ServletException if an Throwable other than an IOException
     */
    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);

            //noinspection unchecked
            subject.execute(new Callable() {
                public Object call() throws Exception {
                    updateSessionLastAccessTime(request, response);
                    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;
            }
            //otherwise it's not one of the two exceptions expected by the filter method signature - wrap it in one:
            String msg = "Filtered request failed.";
            throw new ServletException(msg, t);
        }
    }

过滤逻辑主要包含了如下几部分:

  1. 创建Subject对象,标识系统登录用户
  2. 更新Session最后访问时间
  3. 执行过滤链 

下面分别进行说明:

1.2.2.1 创建Subject对象

创建Subject对象底层是通过securityManager完成的,具体实现如下:

    public Subject buildSubject() {
        return this.securityManager.createSubject(this.subjectContext);
    }

    public Subject createSubject(SubjectContext subjectContext) {
        //create a copy so we don't modify the argument's backing map:
        SubjectContext context = copy(subjectContext);

        //ensure that the context has a SecurityManager instance, and if not, add one:
        context = ensureSecurityManager(context);

        //Resolve an associated Session (usually based on a referenced session ID), and place it in the context before
        //sending to the SubjectFactory.  The SubjectFactory should not need to know how to acquire sessions as the
        //process is often environment specific - better to shield the SF from these details:
        context = resolveSession(context);

        //Similarly, the SubjectFactory should not require any concept of RememberMe - translate that here first
        //if possible before handing off to the SubjectFactory:
        context = resolvePrincipals(context);

        Subject subject = doCreateSubject(context);

        //save this subject for future reference if necessary:
        //(this is needed here in case rememberMe principals were resolved and they need to be stored in the
        //session, so we don't constantly rehydrate the rememberMe PrincipalCollection on every operation).
        //Added in 1.2:
        save(subject);

        return subject;
    }

 其中doCreateSubject方法委托给DefaultWebSubjectFactory完成Subject创建

public class DefaultWebSubjectFactory extends DefaultSubjectFactory {

    public DefaultWebSubjectFactory() {
        super();
    }

    public Subject createSubject(SubjectContext context) {
        if (!(context instanceof WebSubjectContext)) {
            return super.createSubject(context);
        }
        WebSubjectContext wsc = (WebSubjectContext) context;
        SecurityManager securityManager = wsc.resolveSecurityManager();
        Session session = wsc.resolveSession();
        boolean sessionEnabled = wsc.isSessionCreationEnabled();
        PrincipalCollection principals = wsc.resolvePrincipals();
        boolean authenticated = wsc.resolveAuthenticated();
        String host = wsc.resolveHost();
        ServletRequest request = wsc.resolveServletRequest();
        ServletResponse response = wsc.resolveServletResponse();

        return new WebDelegatingSubject(principals, authenticated, host, session, sessionEnabled,
                request, response, securityManager);
    }
1.2.2.2 更新Session最后访问时间

记录Session最后访问时间,

  • 如果是HttpSession,直接返回
  • 如果是Native Session,也即Shiro创建的Session,通过touch方法更新最后访问时间
    /**
     * Updates any 'native'  Session's last access time that might exist to the timestamp when this method is called.
     * If native sessions are not enabled (that is, standard Servlet container sessions are being used) or there is no
     * session ({@code subject.getSession(false) == null}), this method does nothing.
     * <p/>This method implementation merely calls
     * <code>Session.{@link org.apache.shiro.session.Session#touch() touch}()</code> on the session.
     *
     * @param request  incoming request - ignored, but available to subclasses that might wish to override this method
     * @param response outgoing response - ignored, but available to subclasses that might wish to override this method
     * @since 1.0
     */
    @SuppressWarnings({"UnusedDeclaration"})
    protected void updateSessionLastAccessTime(ServletRequest request, ServletResponse response) {
        if (!isHttpSessions()) { //'native' sessions
            Subject subject = SecurityUtils.getSubject();
            //Subject should never _ever_ be null, but just in case:
            if (subject != null) {
                Session session = subject.getSession(false);
                if (session != null) {
                    try {
                        session.touch();
                    } catch (Throwable t) {
                        log.error("session.touch() method invocation has failed.  Unable to update" +
                                "the corresponding session's last access time based on the incoming request.", t);
                    }
                }
            }
        }
    }
1.2.2.3 执行过滤链 

该部分具体实现如下,其主要包含了2个步骤:

  1. 构造过滤链
  2. 执行构造好的过滤链

下面分别进行说明

    /**
     * Executes a {@link FilterChain} for the given request.
     * <p/>
     * This implementation first delegates to
     * <code>{@link #getExecutionChain(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain) getExecutionChain}</code>
     * to allow the application's Shiro configuration to determine exactly how the chain should execute.  The resulting
     * value from that call is then executed directly by calling the returned {@code FilterChain}'s
     * {@link FilterChain#doFilter doFilter} method.  That is:
     * <pre>
     * FilterChain chain = {@link #getExecutionChain}(request, response, origChain);
     * chain.{@link FilterChain#doFilter doFilter}(request,response);</pre>
     *
     * @param request   the incoming ServletRequest
     * @param response  the outgoing ServletResponse
     * @param origChain the Servlet Container-provided chain that may be wrapped further by an application-configured
     *                  chain of Filters.
     * @throws IOException      if the underlying {@code chain.doFilter} call results in an IOException
     * @throws ServletException if the underlying {@code chain.doFilter} call results in a ServletException
     * @since 1.0
     */
    protected void executeChain(ServletRequest request, ServletResponse response, FilterChain origChain)
            throws IOException, ServletException {
        FilterChain chain = getExecutionChain(request, response, origChain);
        chain.doFilter(request, response);
    }
1.2.2.3.1  构造过滤链

构造过滤链的逻辑如下:

    /**
     * Returns the {@code FilterChain} to execute for the given request.
     * <p/>
     * The {@code origChain} argument is the
     * original {@code FilterChain} supplied by the Servlet Container, but it may be modified to provide
     * more behavior by pre-pending further chains according to the Shiro configuration.
     * <p/>
     * This implementation returns the chain that will actually be executed by acquiring the chain from a
     * {@link #getFilterChainResolver() filterChainResolver}.  The resolver determines exactly which chain to
     * execute, typically based on URL configuration.  If no chain is returned from the resolver call
     * (returns {@code null}), then the {@code origChain} will be returned by default.
     *
     * @param request   the incoming ServletRequest
     * @param response  the outgoing ServletResponse
     * @param origChain the original {@code FilterChain} provided by the Servlet Container
     * @return the {@link FilterChain} to execute for the given request
     * @since 1.0
     */
    protected FilterChain getExecutionChain(ServletRequest request, ServletResponse response, FilterChain origChain) {
        FilterChain chain = origChain;

        FilterChainResolver resolver = getFilterChainResolver();
        if (resolver == null) {
            log.debug("No FilterChainResolver configured.  Returning original FilterChain.");
            return origChain;
        }

        FilterChain resolved = resolver.getChain(request, response, origChain);
        if (resolved != null) {
            log.trace("Resolved a configured FilterChain for the current request.");
            chain = resolved;
        } else {
            log.trace("No FilterChain configured for the current request.  Using the default.");
        }

        return chain;
    }

这里首先获取FilterChainResolver过滤链解析器,并调用getChain获取解析后的过滤链,getChain方法实现如下:

    public FilterChain getChain(ServletRequest request, ServletResponse response, FilterChain originalChain) {
        FilterChainManager filterChainManager = getFilterChainManager();
        if (!filterChainManager.hasChains()) {
            return null;
        }

        String requestURI = getPathWithinApplication(request);

        //the 'chain names' in this implementation are actually path patterns defined by the user.  We just use them
        //as the chain name for the FilterChainManager's requirements
        for (String pathPattern : filterChainManager.getChainNames()) {

            // If the path does match, then pass on to the subclass implementation for specific checks:
            if (pathMatches(pathPattern, requestURI)) {
                if (log.isTraceEnabled()) {
                    log.trace("Matched path pattern [" + pathPattern + "] for requestURI [" + requestURI + "].  " +
                            "Utilizing corresponding filter chain...");
                }
                return filterChainManager.proxy(originalChain, pathPattern);
            }
        }

        return null;
    }

这里首先获取请求的Url(requestURI),然后和配置好的过滤链进行路径匹配,得到匹配成功的过滤链后,调用filterChainManager的proxy代理方法进行代理,返回代理后的过滤链;

具体代理逻辑如下:

    public FilterChain proxy(FilterChain original, String chainName) {
        NamedFilterList configured = getChain(chainName);
        if (configured == null) {
            String msg = "There is no configured chain under the name/key [" + chainName + "].";
            throw new IllegalArgumentException(msg);
        }
        return configured.proxy(original);
    }
    public FilterChain proxy(FilterChain orig) {
        return new ProxiedFilterChain(orig, this);
    }
public class ProxiedFilterChain implements FilterChain {

    //TODO - complete JavaDoc

    private static final Logger log = LoggerFactory.getLogger(ProxiedFilterChain.class);

    private FilterChain orig;
    private List<Filter> filters;
    private int index = 0;

    public ProxiedFilterChain(FilterChain orig, List<Filter> filters) {
        if (orig == null) {
            throw new NullPointerException("original FilterChain cannot be null.");
        }
        this.orig = orig;
        this.filters = filters;
        this.index = 0;
    }

    public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
        if (this.filters == null || this.filters.size() == this.index) {
            //we've reached the end of the wrapped chain, so invoke the original one:
            if (log.isTraceEnabled()) {
                log.trace("Invoking original filter chain.");
            }
            this.orig.doFilter(request, response);
        } else {
            if (log.isTraceEnabled()) {
                log.trace("Invoking wrapped filter at index [" + this.index + "]");
            }
            this.filters.get(this.index++).doFilter(request, response, this);
        }
    }
}

通过过滤链代理类ProxiedFilterChain完成了代码配置好的过滤器链与Sevlet容器中的origin过滤器链的串接,并指定了串接后的过滤器执行顺序;

1.2.2.3.2  执行构造好的过滤链

执行构造好的过滤器链,就是调用上面的过滤链代理类ProxiedFilterChain的doFilter方法,进而完整调用所有相关的过滤器,包括根据Url路径匹配成功的过滤器以及Servlet容器定义的过滤器;

如下是项目中过滤器链常见的配置方式:

后续就是执行匹配成功的过滤器链的过滤逻辑了,由于本文篇幅较大,这点放到后面文章进行解析;

2.SpringShiroFilter如何添加到Servlet Filter中

这部分章节首先介绍下常见的添加Servlet Filter的方式,然后从源码解析的角度进行阐释说明;

2.1 常见的添加Servlet Filter方式

2.1.1 方式一:显示定义FilterRegistrationBean

    @Bean
    public FilterRegistrationBean filterRegistrationBean() {
        FilterRegistrationBean filterRegistration = new FilterRegistrationBean();
        filterRegistration.setFilter(new DelegatingFilterProxy("shiroFilter"));
        filterRegistration.addInitParameter("targetFilterLifecycle", "true");
        filterRegistration.setEnabled(true);
        filterRegistration.addUrlPatterns("/*");
        return filterRegistration;
    }

2.1.2 方式二:显示定义ShiroFilterFactoryBean

@Configuration
public class ShiroWebFilterConfiguration {

    @Autowired
    protected SecurityManager securityManager;

    @Autowired
    protected ShiroFilterChainDefinition shiroFilterChainDefinition;

    @Value("#{ @environment['shiro.loginUrl'] ?: '/login.jsp' }")
    protected String loginUrl;

    @Value("#{ @environment['shiro.successUrl'] ?: '/' }")
    protected String successUrl;

    @Value("#{ @environment['shiro.unauthorizedUrl'] ?: null }")
    protected String unauthorizedUrl;

    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean() {
        ShiroFilterFactoryBean filterFactoryBean = new ShiroFilterFactoryBean();

        filterFactoryBean.setLoginUrl(loginUrl);
        filterFactoryBean.setSuccessUrl(successUrl);
        filterFactoryBean.setUnauthorizedUrl(unauthorizedUrl);

        filterFactoryBean.setSecurityManager(securityManager);
        filterFactoryBean.setFilterChainDefinitionMap(shiroFilterChainDefinition.getFilterChainMap());

        return filterFactoryBean;
    }
}

2.2 添加Servlet Filter方式源码解析

添加Servlet Filter的逻辑是在EmbeddedWebApplicationContext容器中实现的,具体如下:

在容器启动时,会调用onRefresh方法,如下:

	@Override
	protected void onRefresh() {
		super.onRefresh();
		try {
			createEmbeddedServletContainer();
		}
		catch (Throwable ex) {
			throw new ApplicationContextException("Unable to start embedded container",
					ex);
		}
	}

	private void createEmbeddedServletContainer() {
		EmbeddedServletContainer localContainer = this.embeddedServletContainer;
		ServletContext localServletContext = getServletContext();
		if (localContainer == null && localServletContext == null) {
			EmbeddedServletContainerFactory containerFactory = getEmbeddedServletContainerFactory();
			this.embeddedServletContainer = containerFactory
					.getEmbeddedServletContainer(getSelfInitializer());
		}
		else if (localServletContext != null) {
			try {
				getSelfInitializer().onStartup(localServletContext);
			}
			catch (ServletException ex) {
				throw new ApplicationContextException("Cannot initialize servlet context",
						ex);
			}
		}
		initPropertySources();
	}

如上会调用onStartup方法:

	private org.springframework.boot.web.servlet.ServletContextInitializer getSelfInitializer() {
		return new ServletContextInitializer() {
			@Override
			public void onStartup(ServletContext servletContext) throws ServletException {
				selfInitialize(servletContext);
			}
		};
	}

	private void selfInitialize(ServletContext servletContext) throws ServletException {
		prepareEmbeddedWebApplicationContext(servletContext);
		ConfigurableListableBeanFactory beanFactory = getBeanFactory();
		ExistingWebApplicationScopes existingScopes = new ExistingWebApplicationScopes(
				beanFactory);
		WebApplicationContextUtils.registerWebApplicationScopes(beanFactory,
				getServletContext());
		existingScopes.restore();
		WebApplicationContextUtils.registerEnvironmentBeans(beanFactory,
				getServletContext());
		for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
			beans.onStartup(servletContext);
		}
	}

	/**
	 * Returns {@link ServletContextInitializer}s that should be used with the embedded
	 * Servlet context. By default this method will first attempt to find
	 * {@link ServletContextInitializer}, {@link Servlet}, {@link Filter} and certain
	 * {@link EventListener} beans.
	 * @return the servlet initializer beans
	 */
	protected Collection<ServletContextInitializer> getServletContextInitializerBeans() {
		return new ServletContextInitializerBeans(getBeanFactory());
	}

如上会创建ServletContextInitializerBeans,其构造方法如下:

	public ServletContextInitializerBeans(ListableBeanFactory beanFactory) {
		this.initializers = new LinkedMultiValueMap<Class<?>, ServletContextInitializer>();
		addServletContextInitializerBeans(beanFactory);
		addAdaptableBeans(beanFactory);
		List<ServletContextInitializer> sortedInitializers = new ArrayList<ServletContextInitializer>();
		for (Map.Entry<?, List<ServletContextInitializer>> entry : this.initializers
				.entrySet()) {
			AnnotationAwareOrderComparator.sort(entry.getValue());
			sortedInitializers.addAll(entry.getValue());
		}
		this.sortedList = Collections.unmodifiableList(sortedInitializers);
	}

这里主要包含了2部分内容:

  1. 方法addServletContextInitializerBeans完成ServletContextInitializer类型bean的处理
  2. 方法addAdaptableBeans完成Filter类型bean的处理

下面分别对这2部分进行分析;

2.2.1 addServletContextInitializerBeans

方法具体实现如下:

	private void addServletContextInitializerBeans(ListableBeanFactory beanFactory) {
		for (Entry<String, ServletContextInitializer> initializerBean : getOrderedBeansOfType(
				beanFactory, ServletContextInitializer.class)) {
			addServletContextInitializerBean(initializerBean.getKey(),
					initializerBean.getValue(), beanFactory);
		}
	}

这里获取Spring容器中类型为ServletContextInitializer的bean,然后调用addServletContextInitializerBean进行注册: 

	private void addServletContextInitializerBean(String beanName,
			ServletContextInitializer initializer, ListableBeanFactory beanFactory) {
		if (initializer instanceof ServletRegistrationBean) {
			Servlet source = ((ServletRegistrationBean) initializer).getServlet();
			addServletContextInitializerBean(Servlet.class, beanName, initializer,
					beanFactory, source);
		}
		else if (initializer instanceof FilterRegistrationBean) {
			Filter source = ((FilterRegistrationBean) initializer).getFilter();
			addServletContextInitializerBean(Filter.class, beanName, initializer,
					beanFactory, source);
		}
		else if (initializer instanceof DelegatingFilterProxyRegistrationBean) {
			String source = ((DelegatingFilterProxyRegistrationBean) initializer)
					.getTargetBeanName();
			addServletContextInitializerBean(Filter.class, beanName, initializer,
					beanFactory, source);
		}
		else if (initializer instanceof ServletListenerRegistrationBean) {
			EventListener source = ((ServletListenerRegistrationBean<?>) initializer)
					.getListener();
			addServletContextInitializerBean(EventListener.class, beanName, initializer,
					beanFactory, source);
		}
		else {
			addServletContextInitializerBean(ServletContextInitializer.class, beanName,
					initializer, beanFactory, initializer);
		}
	}

这里可以看到FilterRegistrationBean(实现了ServletContextInitializer接口)类型的具体处理逻辑,将FilterRegistrationBean类型的bean注册到initializers中,也即完成了Servlet Filter的注册,

支持了方式一中通过显示定义FilterRegistrationBean完成Servlet Filter的注册;

	private void addServletContextInitializerBean(Class<?> type, String beanName,
			ServletContextInitializer initializer, ListableBeanFactory beanFactory,
			Object source) {
		this.initializers.add(type, initializer);
		if (source != null) {
			// Mark the underlying source as seen in case it wraps an existing bean
			this.seen.add(source);
		}
		if (ServletContextInitializerBeans.logger.isDebugEnabled()) {
			String resourceDescription = getResourceDescription(beanName, beanFactory);
			int order = getOrder(initializer);
			ServletContextInitializerBeans.logger.debug("Added existing "
					+ type.getSimpleName() + " initializer bean '" + beanName
					+ "'; order=" + order + ", resource=" + resourceDescription);
		}
	}

2.2.2 addAdaptableBeans

方法体具体实现如下:

这里可以看到针对Filter类型的处理,展开具体实现如下:

	private <T, B extends T> void addAsRegistrationBean(ListableBeanFactory beanFactory,
			Class<T> type, Class<B> beanType, RegistrationBeanAdapter<T> adapter) {
		List<Map.Entry<String, B>> beans = getOrderedBeansOfType(beanFactory, beanType,
				this.seen);
		for (Entry<String, B> bean : beans) {
			if (this.seen.add(bean.getValue())) {
				int order = getOrder(bean.getValue());
				String beanName = bean.getKey();
				// One that we haven't already seen
				RegistrationBean registration = adapter.createRegistrationBean(beanName,
						bean.getValue(), beans.size());
				registration.setName(beanName);
				registration.setOrder(order);
				this.initializers.add(type, registration);
				if (ServletContextInitializerBeans.logger.isDebugEnabled()) {
					ServletContextInitializerBeans.logger.debug(
							"Created " + type.getSimpleName() + " initializer for bean '"
									+ beanName + "'; order=" + order + ", resource="
									+ getResourceDescription(beanName, beanFactory));
				}
			}
		}
	}
	/**
	 * {@link RegistrationBeanAdapter} for {@link Filter} beans.
	 */
	private static class FilterRegistrationBeanAdapter
			implements RegistrationBeanAdapter<Filter> {

		@Override
		public RegistrationBean createRegistrationBean(String name, Filter source,
				int totalNumberOfSourceBeans) {
			return new FilterRegistrationBean(source);
		}

	}

这里通过createRegistrationBean完成了Shiro自定义Filter(SpringShiroFilter)构造为FilterRegistrationBean的逻辑,并添加到initializers中完成Servlet Filter的注册。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1368240.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

YOLOv8训练参数解析

全部参数表 首先罗列一下官网提供的全部参数。 文章目录 1. model ✰✰✰✰✰2. data ✰✰✰✰✰3. epochs ✰✰✰4. patience5. batch ✰✰✰✰✰6. imgsz ✰✰✰✰✰7. save、save_period ✰✰✰8. cache9. device ✰✰✰✰✰10. workers11. project12. name13. exist_ok14…

gitee创建远程仓库并克隆远程仓库到电脑

1、首先点加号新建一个仓库 2、输入仓库名&#xff0c;路径会自动填充&#xff0c;填写简单的仓库介绍&#xff0c;先选择私有&#xff0c;在仓库创建之后&#xff0c;可以改为开源 3、打开建好的仓库 4、复制仓库链接 5、打开一个文件夹(想要存储远程仓库的地址)&#xff0c;在…

DP Alt Mode(Alternative Mode)

一、Type-C的DP AlT Mode Type-C 上 配置 Alternative Mode&#xff08;Alt Mode&#xff09;。这种配置可以使得其他协议比如DP、HDMI、Thunderbolt™、MHL&#xff08;mobile high-definition link&#xff09;,以及PCIe等在 Type C线缆上传输。以最流行的DP为例&#xff1a;…

摆动排序 II

题目链接 摆动排序 II 题目描述 注意点 将数组重新排列成 nums[0] < nums[1] > nums[2] < nums[3]… 的顺序题目数据保证&#xff0c;对于给定的输入 nums &#xff0c;总能产生满足题目要求的结果用 O(n) 时间复杂度和 / 或原地 O(1) 额外空间来实现 解答思路 如…

Qt QLabel标签控件

文章目录 1 属性和方法1.1 文本1.2 对齐方式1.3 换行1.4 图像 2. 实例2.1 布局2.2 为标签添加背景色2.3 为标签添加图片2.4 代码实现 QLabeI是Qt中的标签类&#xff0c;通常用于显示提示性的文本&#xff0c;也可以显示图像 1 属性和方法 QLabel有很多属性&#xff0c;完整的可…

Wargames与bash知识12

Wargames与bash知识12 Bandit20 关卡提示&#xff1a; 主目录中有一个setuid二进制文件&#xff0c;它执行以下操作&#xff1a;它在您指定为命令行参数的端口上连接到localhost。然后&#xff0c;它从连接中读取一行文本&#xff0c;并将其与前一级别的密码&#xff08;band…

IntelliJ IDEA 如何编译 Maven 工程项目

在当今的Java开发领域&#xff0c;Maven已经成为项目构建和依赖管理的标准工具。IntelliJ IDEA作为一款集成度高的Java开发环境&#xff0c;提供了许多强大的功能来简化和优化Maven项目的构建流程。本文将深入介绍如何使用IntelliJ IDEA编译Maven工程的详细步骤以及一些高级技巧…

vagrant 用户名密码登录

正常登录后 sudo -i 切换到root权限 vim /etc/ssh/vim sshd_config 将PasswordAuthentication no设置 为yes 重启sshd.service服务 systemctl restart sshd.service

整理的Binder、DMS、Handler、PMS、WMS等流程图

AMS&#xff1a; Binder&#xff1a; Handler&#xff1a; PMS&#xff1a; starActivity&#xff1a; WMS&#xff1a; 系统启动&#xff1a;

【深度学习每日小知识】Data Augmentation 数据增强

数据增强是通过对原始数据进行各种转换和修改来人工生成附加数据的过程&#xff0c;旨在增加机器学习模型中训练数据的大小和多样性。这对于计算机视觉领域尤为重要&#xff0c;因为图像经常被用作输入数据。 计算机视觉中的数据增强 数据增强的主要目标是解决过拟合问题&…

mongodb基本命令操作

1.创建数据库 语法 use 数据库名字例如:创建hero数据库 use hero查询当前数据库 db如果想查询所有的数据库 show dbs发现并没有刚刚创建的数据库,如果要显示创建的数据库,需要向表中插入一条记录 db.hero.insert({name: "zs",age: 20,country: "china&quo…

具有低待机电流,抗电压波动等特性的抗干扰液位检测/2点水位检测IC-VK36W2D SOP8

产品型号&#xff1a;VK36W2D 产品品牌&#xff1a;永嘉微电/VINKA 封装形式&#xff1a;SOP8 原厂&#xff0c;工程服务&#xff0c;技术支持&#xff01; 概述 VK36W2D具有2个触摸检测通道&#xff0c;可用来检测外部2个点的水从无到有和水从有到无的动作。该芯片具有较高…

给充电桩嵌入一个强大的“心脏”——工控机

到底什么样的工控机才算是真正的好用&#xff1f; 小编还特地去问了技术老王 他表示&#xff1a;稳定第一&#xff0c;性能第二&#xff01; 不稳定&#xff0c;性能再好也白搭&#xff01; 什么是工控机呢&#xff1f; 工控机是一种专门为工业现场而设计的计算机设备&…

高功率电源PCB设计中变压器底层走线的关键要点

高功率电源的设计中&#xff0c;变压器起到了电能的传递与转换的重要作用。变压器下方的走线设计不仅涉及到电路的功率传输效率&#xff0c;还与电磁兼容性&#xff08;EMC&#xff09;、热管理以及电路的可靠性密切相关。 1. 走线布局 在进行变压器下方走线设计时&#xff0c…

视频剪辑方法:智能转码从视频到图片序列,高效转换攻略

在视频编辑和后期处理中&#xff0c;经常要将视频转换为图片序列&#xff0c;以便进行单独编辑或应用。下面一起来看云炫AI智剪如何批量智能转码的方法&#xff0c;高效地将视频转换为图片序列。 视频转为序列图片缩略图效果 视频转为序列图片的效果图&#xff0c;画面清晰&a…

JHipster - Spring Boot 的快速开发利器

产品介绍&#xff1a; JHipster是一个开源的、全面的应用程序生成器&#xff0c;它能够帮助开发者快速生成Spring Boot Angular/React/Vue.js的完整应用程序。它不仅提供了一个简单的界面来定义应用程序的配置&#xff0c;还提供了一组强大的代码生成器&#xff0c;可以在数分…

Lumerical Monitors------ Global properties

Lumerical Monitors------ Global properties Global properties 全局属性 Global properties 全局属性 在 Lumerical 中&#xff0c;这里以 FDTD 工程文件举例&#xff0c;所有的 monitors 都可以通过上方选项卡中的 monitor 标签页添加。 注意上面有一个 Global properties…

Unity中URP下使用屏幕坐标采样深度图

文章目录 前言一、Unity使用了ComputeScreenPos函数得到屏幕坐标1、 我们来看一下这个函数干了什么2、我们看一下该函数实现该结果的意义 二、在Shader中使用&#xff08;法一&#xff09;1、在Varying结构体中2、在顶点着色器中3、在片元着色器中 三、在Shader中使用&#xff…

JUC Lock 计数锁

文章目录 Semaphore继承关系图构造函数常用 API示例总结 CountDownLatch继承关系图构造函数常用 API示例 CyclicBarrier原理构造方法常用 API示例 Semaphore Semaphore字面意思是信号量。主要用于控制有限的资源的访问数量。比如&#xff1a;公共厕所有5个蹲位&#xff0c;但有…

Android readelf 工具查找函数符号

ELF&#xff08;Executable and Linkable Format&#xff09;是一种执行文件和可链接文件的格式。它是一种通用的二进制文件格式&#xff0c;用于在各种操作系统中存储可执行程序、共享库和内核模块。 Android 开发当中的 so 库本质上就是一种特殊类型的 ELF 文件&#xff0c;…