Shiro (two): Spring-boot how to integrate Shiro (on)

This article describes how spring-boot authentication is the process of integration shiro.

Starting from shiro-spring-boot-web-starter

shiro-spring-boot-web-starter is integrated into fast shiro spring-boot configuration package in the web environment. Which itself introduces the necessary modules shiro. And Configuration declares Shiro each component in the form of @Bean, handed over spring container unified management. META-INF look class defines the configuration:

org.springframework.boot.autoconfigure.EnableAutoConfiguration = \
  org.apache.shiro.spring.config.web.autoconfigure.ShiroWebAutoConfiguration,\
  org.apache.shiro.spring.config.web.autoconfigure.ShiroWebFilterConfiguration

You can see there are two configurations: ShiroWebAutoConfiguration and ShiroWebFilterConfiguration.

  • ShiroWebAutoConfiguration
    ShiroWebAutoConfiguration declares each object needs Shiro, where processing requires shiro frame, can be easily implanted, so as to achieve the intended application shiro characteristics. Similarly, we can also replace the partial Bean as needed, so Shiro processes more responsible for their own business needs.
/**
 * @since 1.4.0
 */
@Configuration
@AutoConfigureBefore(ShiroAutoConfiguration.class)
@ConditionalOnProperty(name = "shiro.web.enabled", matchIfMissing = true)
public class ShiroWebAutoConfiguration extends AbstractShiroWebConfiguration {

    //声明了认证时的策略,默认是AtLeastOneSuccessfulStrategy
    @Bean
    @ConditionalOnMissingBean
    @Override
    protected AuthenticationStrategy authenticationStrategy() {
        return super.authenticationStrategy();
    }

    //声明Authenticator对象,负责认证的过程
    @Bean
    @ConditionalOnMissingBean
    @Override
    protected Authenticator authenticator() {
        return super.authenticator();
    }

    //声明Authorizer的对象,负责授权的过程
    @Bean
    @ConditionalOnMissingBean
    @Override
    protected Authorizer authorizer() {
        return super.authorizer();
    }

    //声明Subject数据访问对象,通过该对象可以对Subject数据进行CRUD操作
    @Bean
    @ConditionalOnMissingBean
    @Override
    protected SubjectDAO subjectDAO() {
        return super.subjectDAO();
    }

    //声明SessionStorageEvaluator对象,用来决定Session是否需要持久化
    @Bean
    @ConditionalOnMissingBean
    @Override
    protected SessionStorageEvaluator sessionStorageEvaluator() {
        return super.sessionStorageEvaluator();
    }

    //声明SubjectFactory,用来创建Subject对象
    @Bean
    @ConditionalOnMissingBean
    @Override
    protected SubjectFactory subjectFactory() {
        return super.subjectFactory();
    }

    //声明SessionFactory对象,用来创建Session
    @Bean
    @ConditionalOnMissingBean
    @Override
    protected SessionFactory sessionFactory() {
        return super.sessionFactory();
    }

    //声明Session数据访问对象,提供对Session的CRUD操作
    @Bean
    @ConditionalOnMissingBean
    @Override
    protected SessionDAO sessionDAO() {
        return super.sessionDAO();
    }

    //声明SessionManager对象,负责Session管理
    @Bean
    @ConditionalOnMissingBean
    @Override
    protected SessionManager sessionManager() {
        return super.sessionManager();
    }

    //声明SecurityMananger,Shiro中最重要的组件,对象内封装了各个其他对象,用来处理不同的业务
    @Bean
    @ConditionalOnMissingBean
    @Override
    protected SessionsSecurityManager securityManager(List<Realm> realms) {
        return createSecurityManager();
    }

    //创建会话cookie时的模板,后期应用该模板的属性到创建的cookie对象上
    @Bean
    @ConditionalOnMissingBean(name = "sessionCookieTemplate")
    @Override
    protected Cookie sessionCookieTemplate() {
        return super.sessionCookieTemplate();
    }

    //Remember Me Manager,管理RememerMe
    @Bean
    @ConditionalOnMissingBean
    @Override
    protected RememberMeManager rememberMeManager() {
        return super.rememberMeManager();
    }

    //RememberMe模板
    @Bean
    @ConditionalOnMissingBean(name = "rememberMeCookieTemplate")
    @Override
    protected Cookie rememberMeCookieTemplate() {
        return super.rememberMeCookieTemplate();
    }

    //定义了Shiro的Filter和URL的映射关系
    @Bean
    @ConditionalOnMissingBean
    @Override
    protected ShiroFilterChainDefinition shiroFilterChainDefinition() {
        return super.shiroFilterChainDefinition();
    }
}
  • ShiroWebFilterConfiguration
    conventional access control spring projects are mostly implemented through the filter or interceptor, i.e. before it reaches the controller requests, through the first processing filter or interceptor, and if the verification is successful, then the data is sent to the controller. Shiro also use the same reason.

package org.apache.shiro.spring.config.web.autoconfigure;

import javax.servlet.DispatcherType;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.spring.web.config.AbstractShiroWebFilterConfiguration;
import org.apache.shiro.web.servlet.AbstractShiroFilter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @since 1.4.0
 */
@Configuration
@ConditionalOnProperty(name = "shiro.web.enabled", matchIfMissing = true)
public class ShiroWebFilterConfiguration extends AbstractShiroWebFilterConfiguration {

    //ShiroFilter的FactoryBean,用来创建ShiroFilter
    @Bean
    @ConditionalOnMissingBean
    @Override
    protected ShiroFilterFactoryBean shiroFilterFactoryBean() {
        return super.shiroFilterFactoryBean();
    }

    //ShiroFilter的RegistrationBean,spring-boot舍弃了web.xml的配置,FilterRegistrationBean就成了添加filter的入口
    @Bean(name = "filterShiroFilterRegistrationBean")
    @ConditionalOnMissingBean
    protected FilterRegistrationBean filterShiroFilterRegistrationBean() throws Exception {

        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
        filterRegistrationBean.setDispatcherTypes(DispatcherType.REQUEST, DispatcherType.FORWARD, DispatcherType.INCLUDE, DispatcherType.ERROR);
        filterRegistrationBean.setFilter((AbstractShiroFilter) shiroFilterFactoryBean().getObject());
        filterRegistrationBean.setOrder(1);

        return filterRegistrationBean;
    }
}

FilterRegistrationBeanThe main set Filter to be added
by the ShiroFilterFactoryBeancreation of the. Therefore, we focus on ShiroFilterFactoryBean.

ShiroFilterFactoryBean

ShiroFilterFactoryBeanWhile achieving FactoryBeanand BeanPostProcessor. At the same time that he has the ability to create a post dealing with two of Bean and Bean. Simply look at the source code:


package org.apache.shiro.spring.web;




public class ShiroFilterFactoryBean implements FactoryBean, BeanPostProcessor {

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

    private SecurityManager securityManager;

    //定义了拦截器name和Filter的映射关系
    private Map<String, Filter> filters;
    //定义了拦截器URL和name的映射关系
    private Map<String, String> filterChainDefinitionMap; //urlPathExpression_to_comma-delimited-filter-chain-definition

    private String loginUrl;
    private String successUrl;
    private String unauthorizedUrl;

    private AbstractShiroFilter instance;

    public ShiroFilterFactoryBean() {
        this.filters = new LinkedHashMap<String, Filter>();
        this.filterChainDefinitionMap = new LinkedHashMap<String, String>(); //order matters!
    }

    
    public SecurityManager getSecurityManager() {
        return securityManager;
    }

    
    public void setSecurityManager(SecurityManager securityManager) {
        this.securityManager = securityManager;
    }

    
    public String getLoginUrl() {
        return loginUrl;
    }

    
    public void setLoginUrl(String loginUrl) {
        this.loginUrl = loginUrl;
    }

    
    public String getSuccessUrl() {
        return successUrl;
    }

    
    public void setSuccessUrl(String successUrl) {
        this.successUrl = successUrl;
    }

    
    public String getUnauthorizedUrl() {
        return unauthorizedUrl;
    }

    
    public void setUnauthorizedUrl(String unauthorizedUrl) {
        this.unauthorizedUrl = unauthorizedUrl;
    }

    
    public Map<String, Filter> getFilters() {
        return filters;
    }

    
    public void setFilters(Map<String, Filter> filters) {
        this.filters = filters;
    }

    
    public Map<String, String> getFilterChainDefinitionMap() {
        return filterChainDefinitionMap;
    }

    
    public void setFilterChainDefinitionMap(Map<String, String> filterChainDefinitionMap) {
        this.filterChainDefinitionMap = filterChainDefinitionMap;
    }

    //提供了通过ini配置文件初始化Filter的能力
    public void setFilterChainDefinitions(String definitions) {
        Ini ini = new Ini();
        ini.load(definitions);
        //did they explicitly state a 'urls' section?  Not necessary, but just in case:
        Ini.Section section = ini.getSection(IniFilterChainResolverFactory.URLS);
        if (CollectionUtils.isEmpty(section)) {
            //no urls section.  Since this _is_ a urls chain definition property, just assume the
            //default section contains only the definitions:
            section = ini.getSection(Ini.DEFAULT_SECTION_NAME);
        }
        setFilterChainDefinitionMap(section);
    }

    //FactoryBean获取Bean的方法,这里就是获取对应Filter Bean
    public Object getObject() throws Exception {
        if (instance == null) {
            instance = createInstance();
        }
        return instance;
    }

    //FactoryBean获取生成的Bean的类型
    public Class getObjectType() {
        return SpringShiroFilter.class;
    }

    //要生成的Bean是否需要是单例
    public boolean isSingleton() {
        return true;
    }

    //创建FilterChainManager对象,该对象可以将对应的url和filter组成过滤器链
    protected FilterChainManager createFilterChainManager() {

        DefaultFilterChainManager manager = new DefaultFilterChainManager();
        //Shiro自带的Filter,不需要额外配置,可以开箱即用,详见DefualtFilter
        Map<String, Filter> defaultFilters = manager.getFilters();
        
        for (Filter filter : defaultFilters.values()) {
            applyGlobalPropertiesIfNecessary(filter);
        }

        //获取ShiroFactoryBean中自定义的Filters,添加至manager中
        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);
            }
        }

        //根据定义的url和filter映射关系,创建过滤器链
        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;
    }

   //创建ShiroFilter的实例
    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:
        //创建FilterChainResolver对象,FilterChainResolver包装了Manager,ShiroFilter不需要知道Manager对象
        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:
        //创建ShiroFilter对象
        return new SpringShiroFilter((WebSecurityManager) securityManager, chainResolver);
    }

    private void applyLoginUrlIfNecessary(Filter filter) {
        String loginUrl = getLoginUrl();
        if (StringUtils.hasText(loginUrl) && (filter instanceof AccessControlFilter)) {
            AccessControlFilter acFilter = (AccessControlFilter) filter;
            //only apply the login url if they haven't explicitly configured one already:
            String existingLoginUrl = acFilter.getLoginUrl();
            if (AccessControlFilter.DEFAULT_LOGIN_URL.equals(existingLoginUrl)) {
                acFilter.setLoginUrl(loginUrl);
            }
        }
    }

    private void applySuccessUrlIfNecessary(Filter filter) {
        String successUrl = getSuccessUrl();
        if (StringUtils.hasText(successUrl) && (filter instanceof AuthenticationFilter)) {
            AuthenticationFilter authcFilter = (AuthenticationFilter) filter;
            //only apply the successUrl if they haven't explicitly configured one already:
            String existingSuccessUrl = authcFilter.getSuccessUrl();
            if (AuthenticationFilter.DEFAULT_SUCCESS_URL.equals(existingSuccessUrl)) {
                authcFilter.setSuccessUrl(successUrl);
            }
        }
    }

    private void applyUnauthorizedUrlIfNecessary(Filter filter) {
        String unauthorizedUrl = getUnauthorizedUrl();
        if (StringUtils.hasText(unauthorizedUrl) && (filter instanceof AuthorizationFilter)) {
            AuthorizationFilter authzFilter = (AuthorizationFilter) filter;
            //only apply the unauthorizedUrl if they haven't explicitly configured one already:
            String existingUnauthorizedUrl = authzFilter.getUnauthorizedUrl();
            if (existingUnauthorizedUrl == null) {
                authzFilter.setUnauthorizedUrl(unauthorizedUrl);
            }
        }
    }

    //配置全局属性,只要是针对特定类型的Filter配置其所需要的URL属性
    private void applyGlobalPropertiesIfNecessary(Filter filter) {
        applyLoginUrlIfNecessary(filter);
        applySuccessUrlIfNecessary(filter);
        applyUnauthorizedUrlIfNecessary(filter);
    }

    //通过后置处理器的机制,直接Filter类型的bean,无需配置
    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;
    }

    //SpringShiroFilter只是简单的集成了AbstractShirlFilter,在构造函数中封装了设置SecurityManager和Resolver的操作
    private static final class SpringShiroFilter extends AbstractShiroFilter {

        protected SpringShiroFilter(WebSecurityManager webSecurityManager, FilterChainResolver resolver) {
            super();
            if (webSecurityManager == null) {
                throw new IllegalArgumentException("WebSecurityManager property cannot be null.");
            }
            setSecurityManager(webSecurityManager);
            if (resolver != null) {
                setFilterChainResolver(resolver);
            }
        }
    }
}

The most important step in createInstance()the method is mainly used to create an instance of ShiroFilter substantially procedure comprised three steps:

  1. By createFilterChainManager()created FilterChainManagerobjects:
    • createFilterChainManager
    • Shiro loaded default Filter
    • Filter load added by the user
    • And according to the mapping URL Filter to create a filter chain
  2. Create FilterChainResolverobjects, encapsulationFilterChainManager
  3. Create ShiroFilterobjects, incoming SecurityManagerandFilterChainReslover

SpringShiroFilter

SpringShiroFilterFilter object that we add through the container. To achieve the object Shiro integrating Spring Framework integration by adding the filter.
In fact, SpringShiroFilterjust a simple integrated AbstractShiroFilter, increasing the set in the constructor SecurityManagerand FilterChainResolverprocesses. All logic AbstractShiroFilterhas been defined. Start UML diagrams to understand the entire class inheritance under:

Which look from top to bottom:

  • The Filter
    the Filter is Serlvet packages. Servlet specification defines the basic behavior Filter.
  • ServletContextSupport
    provides the ability to operate ServletContext
  • AbstractFilter
    initially realized Filter, the init process is defined (divided setFilterConfigand the onFilterConfigSettwo parts), a method to set and get configuration.
  • Nameable
    increase the ability of naming
  • NameableFilter
    to increase Naming Filter
  • OncePerRequestFilter
    主要定义了doFilter的过程,并在该过程中限制了一次请求该Filter只处理一次。
    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 {
        //调用Filter处理,并添加已处理属性
            // 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);
            }
        }
    }
  • AbstractShiroFilter
    AbstractShiroFilter已经在Filter的处理过程中添加了Shiro的验证。
package org.apache.shiro.web.servlet;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.ExecutionException;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.mgt.FilterChainResolver;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.mgt.WebSecurityManager;
import org.apache.shiro.web.subject.WebSubject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.concurrent.Callable;


public abstract class AbstractShiroFilter extends OncePerRequestFilter {

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

    private static final String STATIC_INIT_PARAM_NAME = "staticSecurityManagerEnabled";

    // Reference to the security manager used by this filter
    private WebSecurityManager securityManager;

    // Used to determine which chain should handle an incoming request/response
    private FilterChainResolver filterChainResolver;

    /**
     * Whether or not to bind the constructed SecurityManager instance to static memory (via
     * SecurityUtils.setSecurityManager).  This was added to support https://issues.apache.org/jira/browse/SHIRO-287
     * @since 1.2
     */
    private boolean staticSecurityManagerEnabled;

    protected AbstractShiroFilter() {
        this.staticSecurityManagerEnabled = false;
    }

    public WebSecurityManager getSecurityManager() {
        return securityManager;
    }

    public void setSecurityManager(WebSecurityManager sm) {
        this.securityManager = sm;
    }

    public FilterChainResolver getFilterChainResolver() {
        return filterChainResolver;
    }

    public void setFilterChainResolver(FilterChainResolver filterChainResolver) {
        this.filterChainResolver = filterChainResolver;
    }


    public boolean isStaticSecurityManagerEnabled() {
        return staticSecurityManagerEnabled;
    }


    public void setStaticSecurityManagerEnabled(boolean staticSecurityManagerEnabled) {
        this.staticSecurityManagerEnabled = staticSecurityManagerEnabled;
    }

    //重写了AbstractFilter中的空实现,主要设置额外的配置
    protected final void onFilterConfigSet() throws Exception {
        //added in 1.2 for SHIRO-287:
        applyStaticSecurityManagerEnabledConfig();
        init();
        ensureSecurityManager();
        //added in 1.2 for SHIRO-287:
        if (isStaticSecurityManagerEnabled()) {
            SecurityUtils.setSecurityManager(getSecurityManager());
        }
    }

    
    private void applyStaticSecurityManagerEnabledConfig() {
        String value = getInitParam(STATIC_INIT_PARAM_NAME);
        if (value != null) {
            Boolean b = Boolean.valueOf(value);
            if (b != null) {
                setStaticSecurityManagerEnabled(b);
            }
        }
    }

    public void init() throws Exception {
    }

    
    private void ensureSecurityManager() {
        WebSecurityManager securityManager = getSecurityManager();
        if (securityManager == null) {
            log.info("No SecurityManager configured.  Creating default.");
            securityManager = createDefaultSecurityManager();
            setSecurityManager(securityManager);
        }
    }

    protected WebSecurityManager createDefaultSecurityManager() {
        return new DefaultWebSecurityManager();
    }

    protected boolean isHttpSessions() {
        return getSecurityManager().isHttpSessionMode();
    }

    
    protected ServletRequest wrapServletRequest(HttpServletRequest orig) {
        return new ShiroHttpServletRequest(orig, getServletContext(), isHttpSessions());
    }

    
    @SuppressWarnings({"UnusedDeclaration"})
    protected ServletRequest prepareServletRequest(ServletRequest request, ServletResponse response, FilterChain chain) {
        ServletRequest toUse = request;
        if (request instanceof HttpServletRequest) {
            HttpServletRequest http = (HttpServletRequest) request;
            toUse = wrapServletRequest(http);
        }
        return toUse;
    }

    
    protected ServletResponse wrapServletResponse(HttpServletResponse orig, ShiroHttpServletRequest request) {
        return new ShiroHttpServletResponse(orig, getServletContext(), request);
    }

    
    @SuppressWarnings({"UnusedDeclaration"})
    protected ServletResponse prepareServletResponse(ServletRequest request, ServletResponse response, FilterChain chain) {
        ServletResponse toUse = response;
        if (!isHttpSessions() && (request instanceof ShiroHttpServletRequest) &&
                (response instanceof HttpServletResponse)) {
            //the ShiroHttpServletResponse exists to support URL rewriting for session ids.  This is only needed if
            //using Shiro sessions (i.e. not simple HttpSession based sessions):
            toUse = wrapServletResponse((HttpServletResponse) response, (ShiroHttpServletRequest) request);
        }
        return toUse;
    }

    
    protected WebSubject createSubject(ServletRequest request, ServletResponse response) {
        return new WebSubject.Builder(getSecurityManager(), request, response).buildWebSubject();
    }

    //对Session生命周期未交给Web容器管理的情况,由Shiro自己维护
    @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);
                    }
                }
            }
        }
    }

    //重载OncePerRequestFilter的空实现,定义了Filter处理逻辑
    protected void doFilterInternal(ServletRequest servletRequest, ServletResponse servletResponse, final FilterChain chain)
            throws ServletException, IOException {

        Throwable t = null;

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

            //创建Subject对象    
            final Subject subject = createSubject(request, response);

            //noinspection unchecked
            subject.execute(new Callable() {
                public Object call() throws Exception {
                    //更新Session访问时间
                    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);
        }
    }

    //获取待执行的过滤器链
    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) {
            //如果该请求URI是需要Shiro拦截的,则由shiro创建代理的过滤器链,
            //在执行原始的过滤器前,插入Shiro过滤器的执行过程
            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;
    }

    //执行拦截器链
    protected void executeChain(ServletRequest request, ServletResponse response, FilterChain origChain)
            throws IOException, ServletException {
        //获取拦截器链        
        FilterChain chain = getExecutionChain(request, response, origChain);
        //拦截器链处理拦截工作
        chain.doFilter(request, response);
    }
}

其中比较重要的有两个:onFilterConfigSet()doFilterInternal()
onFilterConfigset()AbstractFilter中是空实现,这里重载了该方法,主要是添加一些额外配置。
doFilterInternal()主要流程:

  • 先封装了请求和响应
  • 获取请求代表的Subject对象
  • 更新session最后访问的时间(托管给WEB容器的session不需要shiro管理访问时间)
  • 执行拦截器链executeChain:
    • 获取拦截器链: 如果FilterChainResolver解析到请求的URL是shiro拦截器拦截的URL,则产生代理的FilterChain,让shiro的拦截器集成进拦截器;否则使用原始的拦截器链
    • 拦截器链开始工作

FilterChainResolver和FilterChainManager的工作

之前简单介绍过FilterChainResolver封装了FilterChainManger对象,现在再来看下FilterChainResovler的代码:

public class PathMatchingFilterChainResolver implements FilterChainResolver {

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

    //封装了FilterChainManager
    private FilterChainManager filterChainManager;

    //负责比较URL
    private PatternMatcher pathMatcher;

    public PathMatchingFilterChainResolver() {
        this.pathMatcher = new AntPathMatcher();
        this.filterChainManager = new DefaultFilterChainManager();
    }

    public PathMatchingFilterChainResolver(FilterConfig filterConfig) {
        this.pathMatcher = new AntPathMatcher();
        this.filterChainManager = new DefaultFilterChainManager(filterConfig);
    }

    
    public PatternMatcher getPathMatcher() {
        return pathMatcher;
    }

    
    public void setPathMatcher(PatternMatcher pathMatcher) {
        this.pathMatcher = pathMatcher;
    }

    public FilterChainManager getFilterChainManager() {
        return filterChainManager;
    }

    @SuppressWarnings({"UnusedDeclaration"})
    public void setFilterChainManager(FilterChainManager filterChainManager) {
        this.filterChainManager = filterChainManager;
    }

    //获取拦截器链
    public FilterChain getChain(ServletRequest request, ServletResponse response, FilterChain originalChain) {
        FilterChainManager filterChainManager = getFilterChainManager();
        if (!filterChainManager.hasChains()) {
            return null;
        }

        //获取URL
        String requestURI = getPathWithinApplication(request);

        //匹配URL
        for (String pathPattern : filterChainManager.getChainNames()) {

            //如果匹配,则由filterChainManager创建代理过的Filter Chain
            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;
    }

    
    protected boolean pathMatches(String pattern, String path) {
        PatternMatcher pathMatcher = getPathMatcher();
        return pathMatcher.matches(pattern, path);
    }

    
    protected String getPathWithinApplication(ServletRequest request) {
        return WebUtils.getPathWithinApplication(WebUtils.toHttp(request));
    }
}

这个类的代码相对简单,大概过程是根据FilterChainManager配置的URI和Filter的映射关系,比较请求是否需要经过Shiro的Filter,如果需要则交给了FilterChainManager对象创建代理过的FilterChain,从而加入了Shiro的处理流程。可以看到主要的内容都是由FilterChainManager完成。因此我们再来看FilterChainManager类的proxy方法:

    public FilterChain proxy(FilterChain original, String chainName) {
        //NamedFilterList对象可以简单理解为一个命名了Filter的list,是之前解析filter时,创建的
        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);
    }

NamedFilterList创建代理的过程其实就是创建ProxiedFilterChain对象。

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.");
        }
        //封装了原始filterChain对象和shiro的filter对象
        this.orig = orig;
        this.filters = filters;
        this.index = 0;
    }

    //实现了filterChain的doFilter方法
    public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
        //如果不含有shiro的filter,或是已经遍历完了shiro的filter,则调用原始的fiter chain的方法
        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 + "]");
            }
            //调用shiro的filter
            this.filters.get(this.index++).doFilter(request, response, this);
        }
    }
}

源码读到这里已经大致了解到shiro是如何集成至spring-boot的。接下来再以一个Shiro的Filter为例,具体了解下authentication的流程。

从FormAuthenticatingFilter了解Shiro的authentication过程

一样先看Filter的继承结构:

OncePerRequestFilter父级的结构已经在前文介绍过。现在关注点放在它的子类上。

  • AdviceFilter:
    主要实现了doFilterInternal方法,将filter的过程再切成preHandleexecuteChainpostHandlecleanup四个阶段。有点类似spring中的拦截器。在方法前,方法后做了拦截。并根据返回的结果决定是否继续再filterChain中往下走:
    public void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain)
            throws ServletException, IOException {

        Exception exception = null;

        try {
            //前置处理,根据返回结构决定是否继续走之后的filter
            boolean continueChain = preHandle(request, response);
            if (log.isTraceEnabled()) {
                log.trace("Invoked preHandle method.  Continuing chain?: [" + continueChain + "]");
            }
            
            //继续调用之后的filter
            if (continueChain) {
                executeChain(request, response, chain);
            }
            
            //后置处理
            postHandle(request, response);
            if (log.isTraceEnabled()) {
                log.trace("Successfully invoked postHandle method");
            }

        } catch (Exception e) {
            exception = e;
        } finally {
        //清理
            cleanup(request, response, exception);
        }
    }
  • PathMatchingFilter:根据url是否匹配的逻辑实现preHandle方法。并提供onPreHandle方法让子类可以修改preHandle方法返回的值。
protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {

        if (this.appliedPaths == null || this.appliedPaths.isEmpty()) {
            if (log.isTraceEnabled()) {
                log.trace("appliedPaths property is null or empty.  This Filter will passthrough immediately.");
            }
            return true;
        }

        //匹配请求URL,决定是否需要经过shiro的filter
        for (String path : this.appliedPaths.keySet()) {
            
            if (pathsMatch(path, request)) {
                log.trace("Current requestURI matches pattern '{}'.  Determining filter chain execution...", path);
                Object config = this.appliedPaths.get(path);

                return isFilterChainContinued(request, response, path, config);
            }
        }

        //no path matched, allow the request to go through:
        return true;
    }

    
    @SuppressWarnings({"JavaDoc"})
    private boolean isFilterChainContinued(ServletRequest request, ServletResponse response,
                                           String path, Object pathConfig) throws Exception {

        if (isEnabled(request, response, path, pathConfig)) { //isEnabled check added in 1.2
            if (log.isTraceEnabled()) {
                log.trace("Filter '{}' is enabled for the current request under path '{}' with config [{}].  " +
                        "Delegating to subclass implementation for 'onPreHandle' check.",
                        new Object[]{getName(), path, pathConfig});
            }
            //添加onPreHandle方法
            return onPreHandle(request, response, pathConfig);
        }

        if (log.isTraceEnabled()) {
            log.trace("Filter '{}' is disabled for the current request under path '{}' with config [{}].  " +
                    "The next element in the FilterChain will be called immediately.",
                    new Object[]{getName(), path, pathConfig});
        }
        //This filter is disabled for this specific request,
        //return 'true' immediately to indicate that the filter will not process the request
        //and let the request/response to continue through the filter chain:
        return true;
    }

    //默认返回true,子类可以重载该方法实现自己的控制逻辑
    protected boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
        return true;
    }
  • AccessControlFilter:主要重载了onPreHandle方法,增加了isAccessAllowedonAccessDenied方法。可以在该方法中实现访问控制的逻辑。
   public boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
        return isAccessAllowed(request, response, mappedValue) || onAccessDenied(request, response, mappedValue);
    }
  • AuthenticationFilter:实现了isAccessAllowed方法,通过subject.isAuthenticated()决定是否允许访问。
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
        Subject subject = getSubject(request, response);
        return subject.isAuthenticated();
    }
  • AuthenticatingFilter:除了主要的isAccessAllowedcleanup方法之外,还实现了executeLogin方法,主要是从请求中获取登录的用户名密码,通过shiro进行登录验证
  • FormAuthenticationFilter:最后具体看一下FormAuthenticationFilter
public class FormAuthenticationFilter extends AuthenticatingFilter {

    //TODO - complete JavaDoc

    public static final String DEFAULT_ERROR_KEY_ATTRIBUTE_NAME = "shiroLoginFailure";

    public static final String DEFAULT_USERNAME_PARAM = "username";
    public static final String DEFAULT_PASSWORD_PARAM = "password";
    public static final String DEFAULT_REMEMBER_ME_PARAM = "rememberMe";

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

    private String usernameParam = DEFAULT_USERNAME_PARAM;
    private String passwordParam = DEFAULT_PASSWORD_PARAM;
    private String rememberMeParam = DEFAULT_REMEMBER_ME_PARAM;

    private String failureKeyAttribute = DEFAULT_ERROR_KEY_ATTRIBUTE_NAME;

    public FormAuthenticationFilter() {
        setLoginUrl(DEFAULT_LOGIN_URL);
    }

    @Override
    public void setLoginUrl(String loginUrl) {
        String previous = getLoginUrl();
        if (previous != null) {
            this.appliedPaths.remove(previous);
        }
        super.setLoginUrl(loginUrl);
        if (log.isTraceEnabled()) {
            log.trace("Adding login url to applied paths.");
        }
        this.appliedPaths.put(getLoginUrl(), null);
    }

    public String getUsernameParam() {
        return usernameParam;
    }


    public void setUsernameParam(String usernameParam) {
        this.usernameParam = usernameParam;
    }

    public String getPasswordParam() {
        return passwordParam;
    }


    public void setPasswordParam(String passwordParam) {
        this.passwordParam = passwordParam;
    }

    public String getRememberMeParam() {
        return rememberMeParam;
    }


    public void setRememberMeParam(String rememberMeParam) {
        this.rememberMeParam = rememberMeParam;
    }

    public String getFailureKeyAttribute() {
        return failureKeyAttribute;
    }

    public void setFailureKeyAttribute(String failureKeyAttribute) {
        this.failureKeyAttribute = failureKeyAttribute;
    }

    //
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
        //如果isAccessAllowed校验失败,则判断是否是登录请求
        if (isLoginRequest(request, response)) {
            //如果是表单提交的登录请求,则执行登录,
            if (isLoginSubmission(request, response)) {
                if (log.isTraceEnabled()) {
                    log.trace("Login submission detected.  Attempting to execute login.");
                }
                //登录
                return executeLogin(request, response);
            } else {
                if (log.isTraceEnabled()) {
                    log.trace("Login page view.");
                }
                //allow them to see the login page ;)
                return true;
            }
        } else {
            if (log.isTraceEnabled()) {
                log.trace("Attempting to access a path which requires authentication.  Forwarding to the " +
                        "Authentication url [" + getLoginUrl() + "]");
            }

            saveRequestAndRedirectToLogin(request, response);
            return false;
        }
    }

    @SuppressWarnings({"UnusedDeclaration"})
    protected boolean isLoginSubmission(ServletRequest request, ServletResponse response) {
        return (request instanceof HttpServletRequest) && WebUtils.toHttp(request).getMethod().equalsIgnoreCase(POST_METHOD);
    }

    protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) {
        String username = getUsername(request);
        String password = getPassword(request);
        return createToken(username, password, request, response);
    }

    protected boolean isRememberMe(ServletRequest request) {
        return WebUtils.isTrue(request, getRememberMeParam());
    }

    //定义了一些重定向动作
    protected boolean onLoginSuccess(AuthenticationToken token, Subject subject,
                                     ServletRequest request, ServletResponse response) throws Exception {
        issueSuccessRedirect(request, response);
        //we handled the success redirect directly, prevent the chain from continuing:
        return false;
    }

    protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e,
                                     ServletRequest request, ServletResponse response) {
        if (log.isDebugEnabled()) {
            log.debug( "Authentication exception", e );
        }
        setFailureAttribute(request, e);
        //login failed, let request continue back to the login page:
        return true;
    }

    protected void setFailureAttribute(ServletRequest request, AuthenticationException ae) {
        String className = ae.getClass().getName();
        request.setAttribute(getFailureKeyAttribute(), className);
    }

    protected String getUsername(ServletRequest request) {
        return WebUtils.getCleanParam(request, getUsernameParam());
    }

    protected String getPassword(ServletRequest request) {
        return WebUtils.getCleanParam(request, getPasswordParam());
    }


}

总结

shiro通过FilterRegistrationBean添加了ShiroFilter。ShiroFilter在针对需要登录验证的请求,将原始的fiterChain进行代理,从而集成了shiro权限验证的filter。

Guess you like

Origin www.cnblogs.com/insaneXs/p/11041877.html