• Shiro(二):Spring-boot如何集成Shiro(上)


    这篇文章主要介绍了spring-boot是如何集成shiro的authentication流程的。

    从shiro-spring-boot-web-starter说起

    shiro-spring-boot-web-starter是shiro在web环境下快速集成至spring-boot的配置包。其本身引入了shiro的必要模块。并在Configuration中以@Bean的形式声明了Shiro各组件,交由spring容器统一管理。先看META-INF定义了配置类:

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

    可以看到主要有两个配置:ShiroWebAutoConfiguration和ShiroWebFilterConfiguration。

    • ShiroWebAutoConfiguration
      ShiroWebAutoConfiguration声明了Shiro需要的各个对象,在需要shiro框架进行处理的地方,可以方便注入,从而到达应用shiro特性的目的。同理,我们也可以根据需要替换上述部分Bean,让Shiro的流程更负责自己的业务需求。
    /**
     * @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
      以往spring项目的权限控制大多也是通过过滤器或是拦截器实现的,即请求在到达控制器之前,先经过过滤器或拦截器的处理,如果校验成功,再将数据发送至控制器。Shiro也是利用同样的道理。
    
    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;
        }
    }
    
    

    FilterRegistrationBean主要设置了要添加的Filter
    是由ShiroFilterFactoryBean所创建。因此,我们重点关注ShiroFilterFactoryBean。

    ShiroFilterFactoryBean

    ShiroFilterFactoryBean同时实现了FactoryBeanBeanPostProcessor。说明他同时有创建Bean和Bean的后置处理两种能力。简单看一下源代码:

    
    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);
                }
            }
        }
    }
    
    

    最重要的步骤是在createInstance()中,该方法主要用来创建ShiroFilter的实例,大致过程分成三步:

    1. 通过createFilterChainManager()创建FilterChainManager对象:
      • 创建FilterChainManager
      • 加载Shiro默认的Filter
      • 加载用户添加的Filter
      • 根据URL和Filter的映射关系,创建过滤器链
    2. 创建FilterChainResolver对象,封装FilterChainManager
    3. 创建ShiroFilter对象,传入SecurityManagerFilterChainReslover

    SpringShiroFilter

    SpringShiroFilter就是我们通过容器添加的Filter对象。Shiro通过添加该过滤器实现了往集成Spring框架集成的目的。
    其实SpringShiroFilter只是单纯的集成了AbstractShiroFilter,在构造函数中增加了设置SecurityManagerFilterChainResolver的过程。所有的逻辑在AbstractShiroFilter中已经定义好了。先从UML图中了解下整个类的继承关系:

    其中从上到下看:

    • Filter
      Filter是Serlvet包提供的。由Servlet规范定义Filter基本的行为。
    • ServletContextSupport
      提供了操作ServletContext的能力
    • AbstractFilter
      初步实现了Filter,定义了init的过程(分成setFilterConfigonFilterConfigSet两部分),提供了设置和获取配置的方法。
    • Nameable
      增加了命名的能力
    • NameableFilter
      为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。

  • 相关阅读:
    Android 统一配置依赖管理
    Android图片压缩工具MCompressor
    Android Studio 打包自定义apk文件名
    sourceTree的下载与安装
    Mac环境下SVN的配置和使用
    AndroidStudio环境搭建
    设计模式之策略模式
    设计模式之状态模式
    设计模式之观察者模式
    mysql 查询小demo
  • 原文地址:https://www.cnblogs.com/insaneXs/p/11041877.html
Copyright © 2020-2023  润新知