• 项目启动时shiro加载过程


    1.web下的shiro启动入口(shiro1.2及之后版本)

    web入口web.xml配置

    <!--- shiro 1.2 -->
        <listener>
            <listener-class>org.apache.shiro.web.env.EnvironmentLoaderListener</listener-class>
        </listener>
        <context-param>
            <param-name>shiroEnvironmentClass</param-name>
            <param-value>org.apache.shiro.web.env.IniWebEnvironment</param-value><!-- 默认先从/WEB-INF/shiro.ini,如果没有找classpath:shiro.ini -->
        </context-param>
        <context-param>
            <param-name>shiroConfigLocations</param-name>
            <param-value>classpath:shiro.ini</param-value>
        </context-param>
        <filter>
            <filter-name>shiroFilter</filter-name>
            <filter-class>org.apache.shiro.web.servlet.ShiroFilter</filter-class>
        </filter>
        <filter-mapping>
            <filter-name>shiroFilter</filter-name>
            <url-pattern>/*</url-pattern>
        </filter-mapping>
    EnvironmentLoaderListener是一个ServletContextListener,会在容器启动和销毁的时候执行对应方法。实际上具体的方法在父类EnvironmentLoader中实现
    public class EnvironmentLoaderListener extends EnvironmentLoader implements ServletContextListener {
    
       //servlet容器启动的时候调用
        public void contextInitialized(ServletContextEvent sce) {
            initEnvironment(sce.getServletContext());
        }
    
        //servlet容器销毁的时候调用
        public void contextDestroyed(ServletContextEvent sce) {
            destroyEnvironment(sce.getServletContext());
        }
    }

    下面详细看一下EnvironmentLoader中启动初始化方法initEnvironment(源码EnvironmentLoader类119行)

        public WebEnvironment initEnvironment(ServletContext servletContext) throws IllegalStateException {
    
    //防止已经被初始化过了,这里做个校验
    if (servletContext.getAttribute(ENVIRONMENT_ATTRIBUTE_KEY) != null) { String msg = "There is already a Shiro environment associated with the current ServletContext. " + "Check if you have multiple EnvironmentLoader* definitions in your web.xml!"; throw new IllegalStateException(msg); } servletContext.log("Initializing Shiro environment"); log.info("Starting Shiro environment initialization."); long startTime = System.currentTimeMillis(); try {
           //重点是这,创建WebEnvironment WebEnvironment environment
    = createEnvironment(servletContext); servletContext.setAttribute(ENVIRONMENT_ATTRIBUTE_KEY, environment); log.debug("Published WebEnvironment as ServletContext attribute with name [{}]", ENVIRONMENT_ATTRIBUTE_KEY); if (log.isInfoEnabled()) { long elapsed = System.currentTimeMillis() - startTime; log.info("Shiro environment initialized in {} ms.", elapsed); } return environment; } catch (RuntimeException ex) { log.error("Shiro environment initialization failed", ex); servletContext.setAttribute(ENVIRONMENT_ATTRIBUTE_KEY, ex); throw ex; } catch (Error err) { log.error("Shiro environment initialization failed", err); servletContext.setAttribute(ENVIRONMENT_ATTRIBUTE_KEY, err); throw err; } }

    下面看一下创建WebEnvironment过程(源码EnvironmentLoader类,193行)

        protected WebEnvironment createEnvironment(ServletContext sc) {
            
    //加载IniWebEnvironment Class
    <?> clazz = determineWebEnvironmentClass(sc); if (!MutableWebEnvironment.class.isAssignableFrom(clazz)) { throw new ConfigurationException("Custom WebEnvironment class [" + clazz.getName() + "] is not of required type [" + WebEnvironment.class.getName() + "]"); }
    //获取shiro配置路径 String configLocations
    = sc.getInitParameter(CONFIG_LOCATIONS_PARAM); boolean configSpecified = StringUtils.hasText(configLocations); if (configSpecified && !(ResourceConfigurable.class.isAssignableFrom(clazz))) { String msg = "WebEnvironment class [" + clazz.getName() + "] does not implement the " + ResourceConfigurable.class.getName() + "interface. This is required to accept any " + "configured " + CONFIG_LOCATIONS_PARAM + "value(s)."; throw new ConfigurationException(msg); } MutableWebEnvironment environment = (MutableWebEnvironment) ClassUtils.newInstance(clazz); environment.setServletContext(sc); if (configSpecified && (environment instanceof ResourceConfigurable)) { ((ResourceConfigurable) environment).setConfigLocations(configLocations); } customizeEnvironment(environment);
    //重点是初始化过程 LifecycleUtils.init(environment);
    return environment; }

    上面代码加载IniWebEnvironment的就是web.xml中shiroEnvironmentClass配置的org.apache.shiro.web.env.IniWebEnvironment,加载过程如下(源码EnvironmentLoader类,165行),ENVIRONMENT_CLASS_PARAM=shiroEnvironmentClass,CONFIG_LOCATIONS_PARAM=shiroConfigLocations(就是web.xml配置的两个全局参数)

        protected Class<?> determineWebEnvironmentClass(ServletContext servletContext) {
            String className = servletContext.getInitParameter(ENVIRONMENT_CLASS_PARAM);
            if (className != null) {
                try {
                    return ClassUtils.forName(className);
                } catch (UnknownClassException ex) {
                    throw new ConfigurationException(
                            "Failed to load custom WebEnvironment class [" + className + "]", ex);
                }
            } else {
                return IniWebEnvironment.class;
            }
        }

    下面分析初始化过程
    LifecycleUtils.init(environment),最终会调用IniWebEnvironment的init()方法(源代码IniWebEnvironment类,62行)
        public void init() {
            //这里暂时还没有配置文件,需要从前面加载的configLocations路径中加载
    Ini ini
    = getIni(); String[] configLocations = getConfigLocations(); if (log.isWarnEnabled() && !CollectionUtils.isEmpty(ini) && configLocations != null && configLocations.length > 0) { log.warn("Explicit INI instance has been provided, but configuration locations have also been " + "specified. The {} implementation does not currently support multiple Ini config, but this may " + "be supported in the future. Only the INI instance will be used for configuration.", IniWebEnvironment.class.getName()); } if (CollectionUtils.isEmpty(ini)) { log.debug("Checking any specified config locations.");
    //从配置路径下加载shiro配置 ini
    = getSpecifiedIni(configLocations); } if (CollectionUtils.isEmpty(ini)) { log.debug("No INI instance or config locations specified. Trying default config locations."); ini = getDefaultIni(); } if (CollectionUtils.isEmpty(ini)) { String msg = "Shiro INI configuration was either not found or discovered to be empty/unconfigured."; throw new ConfigurationException(msg); } setIni(ini);
    //重点又来了,shiro需要的类是从这里开始加载的 configure(); }

    下面分析configure()方法,(源代码IniWebEnvironment类,95行)

        protected void configure() {
    
            this.objects.clear();
            //1创建securityManager,这里创建的是DefaultWebSecurityManager实例
            WebSecurityManager securityManager = createWebSecurityManager();
            setWebSecurityManager(securityManager);
    
    //2从配置里(这里是.ini配置文件)生成拦截器链 FilterChainResolver resolver
    = createFilterChainResolver(); if (resolver != null) { setFilterChainResolver(resolver); } }

    构造securityManager过程和生成url拦截器链列表都很简单,这里就不展开了,感兴趣的同学可以自己debug一下。源码debug入口参考开涛大神跟我学shiro第七章实例。

    简单的总结一下原生的java web项目启动时配置shiro的过程:

    (1)通过EnvironmentLoaderListener监听sevlet容器启动,触发shiro初始化操作。初始化动作实际上交给其父类EnvironmentLoader实现。

    (2)EnvironmentLoader主要动作是创建WebEnvironment的实现类IniWebEnvironment。

    (3)IniWebEnvironment该类的作用是加载shiro初始化配置文件,然后配置SecurityManager和FilterChainResolver

    (4)启动完成后,配置shiroFilter,所有路径的请求都会走该拦截器。

    补充一下shiroFilter初始化

    public class ShiroFilter extends AbstractShiroFilter {
    
        /**
         * Configures this instance based on the existing {@link org.apache.shiro.web.env.WebEnvironment} instance
         * available to the currently accessible {@link #getServletContext() servletContext}.
         *
         * @see org.apache.shiro.web.env.EnvironmentLoaderListener
         * @since 1.2
         */
        @Override
        public void init() throws Exception {
    //(1)获取webEnvironment实例 WebEnvironment env
    = WebUtils.getRequiredWebEnvironment(getServletContext()); //(2)绑定securityManager setSecurityManager(env.getWebSecurityManager()); //(3)绑定filterChainResolver FilterChainResolver resolver = env.getFilterChainResolver(); if (resolver != null) { setFilterChainResolver(resolver); } } }

    shiroFilter拦截器的实现主要在父类AbstractShiroFilter中实现,关于shiro拦截器的实现计划另有篇幅讨论,这里不深入研究,主要看一下初始化过程。

    shiroFilter初始化第一步是获取WebEnvironment实例,也就是我们前面分析的IniWebEnvironment实例,该实例有两个属性SecurityManager和FilterChainResolver,shiroFilter给自己set。

    然而现在项目基本不会这么使用了,了解基础的使用是为了更好的掌握shiro原理,这样结合框加就不会知其然不知其所以然,下面我们一起分析spring boot下的shiro启动入口。

    2.spring boot下的shiro启动入口

    maven 依赖

            <dependency>
                <groupId>org.apache.shiro</groupId>
                <artifactId>shiro-spring</artifactId>
            </dependency>

     本次实例使用java方式配置shiro,完整的配置代码如下。cacheManager和sessionDao可自行实现,例如基于redis

    @Configuration
    public class ShiroConfig {
    
        @Value("${shiro.session.expire.time}")
        private int globalSessionTimeOut;
    
        @Value("${shiro.cache.key}")
        private String shiroCacheKey;
    
        @Value("${shiro.login.url}")
        private String shiroLoginUrl;
    
        @Value("${shiro.auth.switch}")
        private boolean authSwitch;
    
    
        @Bean(name = "shiroFilter")
        public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
    
            ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
            shiroFilterFactoryBean.setSecurityManager(securityManager);
    
            // 没有登陆的用户只能访问登陆页面
            shiroFilterFactoryBean.setLoginUrl(shiroLoginUrl);
    
    
            //配置URL权限控制
            Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
    
            if (authSwitch) {
                filterChainDefinitionMap.put(API_BASE + "/auth/login", "anon");
                filterChainDefinitionMap.put(API_BASE + "/auth/logout", "anon");
                filterChainDefinitionMap.put(API_BASE + "/auth/noauth", "anon");
                filterChainDefinitionMap.put(API_BASE + "/auth/check", "anon");
                filterChainDefinitionMap.put(API_BASE + "/auth/kickout", "anon");
                filterChainDefinitionMap.put(API_ANON_BASE + "/**", "anon");
                filterChainDefinitionMap.put("/**", "authc");
            }
            shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
    
            return shiroFilterFactoryBean;
        }
    
        @Bean
        public FilterRegistrationBean filterRegistrationBean() {
            FilterRegistrationBean filterRegistration = new FilterRegistrationBean();
            filterRegistration.setFilter(new DelegatingFilterProxy("shiroFilter"));
            filterRegistration.addInitParameter("targetFilterLifecycle", "true");
            filterRegistration.setEnabled(true);
            filterRegistration.addUrlPatterns("/*");
            return filterRegistration;
        }
    
    
        @Bean
        public Realm realm(CacheManager cacheManager, CredentialsMatcher credentialsMatcher) {
            MyRealm myRealm = new MyRealm();
            myRealm.setCredentialsMatcher(credentialsMatcher);
            myRealm.setCacheManager(cacheManager);
            return myRealm;
        }
    
    
        @Bean
        public CredentialsMatcher credentialsMatcher() {
            HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
            matcher.setHashAlgorithmName(PcdnConst.HASH_ALGORITHM);
            matcher.setHashIterations(PcdnConst.HASH_ITERATIONS);
            return matcher;
        }
    
    
        @Bean
        public SecurityManager securityManager(Realm realm, CacheManager cacheManager, SessionManager sessionManager) {
            DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
            // 设置realm.
            securityManager.setRealm(realm);
            // 设置缓存
            securityManager.setCacheManager(cacheManager);
            // 配置session管理
            securityManager.setSessionManager(sessionManager);
            return securityManager;
        }
    
    
        /**
         * Session Manager
         * 使用的是shiro-redis开源插件
         */
        @Bean
        public SessionManager sessionManager(SessionListener sessionListener,
                                             SessionDAO sessionDAO,
                                             CacheManager cacheManager,
                                             Cookie sessionIdCookie) {
            DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
            sessionManager.setCacheManager(cacheManager);
            sessionManager.setSessionDAO(sessionDAO);
            sessionManager.setGlobalSessionTimeout(globalSessionTimeOut * 1000L);
            sessionManager.setSessionIdCookie(sessionIdCookie);
            sessionManager.setSessionListeners(new ArrayList<SessionListener>() {{
                add(sessionListener);
            }});
            return sessionManager;
        }
    
        @Bean("sessionIdCookie")
        public Cookie simpleCookie() {
            SimpleCookie cookie = new SimpleCookie(ShiroHttpSession.DEFAULT_SESSION_ID_NAME);
            //sessionIdCookie过期时间这里设置和会话有效期一样,不设置的话默认为-1,cookie关闭浏览器过期
            cookie.setMaxAge(globalSessionTimeOut);
            return cookie;
        }/**
         * Shiro生命周期处理器,注意这里使用static修饰,@Value值不能初始化(原因可自行研究)
         */
        @Bean
        public static LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() {
            return new LifecycleBeanPostProcessor();
        }
    
        @Bean
        public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() {
            DefaultAdvisorAutoProxyCreator daap = new DefaultAdvisorAutoProxyCreator();
            daap.setProxyTargetClass(true);
            return daap;
        }
    
        @Bean
        public AuthorizationAttributeSourceAdvisor getAuthorizationAttributeSourceAdvisor(org.apache.shiro.mgt.SecurityManager securityManager) {
            AuthorizationAttributeSourceAdvisor aasa = new AuthorizationAttributeSourceAdvisor();
            aasa.setSecurityManager(securityManager);
            return aasa;
        }
    }

    加载配置的入口类ShiroFilterFactoryBean,从名字可以猜测这是一个spring的工厂类,

    类签名:public class ShiroFilterFactoryBean implements FactoryBean, BeanPostProcessor

    spring在实例化的时候会调用其getObject实现,源代码ShiroFilterFactoryBean ,第341行

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

    createInstance会创建一个AbstractShiroFilter的实现类SpringShiroFilter的实例,源代码ShiroFilterFactoryBean ,第422行

        protected AbstractShiroFilter createInstance() throws Exception {
    
            log.debug("Creating Shiro Filter instance.");
    
    //(1)获取前面shiroConfig中配置的securityManager 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); }
    //(2)添加shiro的11个默认filter和自定义的filter 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:

    //(3)创建一个springShiroFilter实例 return new SpringShiroFilter((WebSecurityManager) securityManager, chainResolver); }

    上面代码的(1)(2)步骤是不是有点眼熟,和原生java web加载shiro配置的功能类似。最后(3)会生成springShiroFilter实例托管给spring容器。

    为什么要将ShiroFilter托管给spring容器?

    很好理解,我们是基于spring配置来接入shiro的,理所应当的可以使用spring的ioc特性来管理shiro相关bean。配置中需要使用spring容器中的实例时可以直接注入。

    所以

    这里shiroFile不能使用前面原生的java web的web.xml中的org.apache.shiro.web.servlet.ShiroFilter,因为init()方法没法初始化。相关配置需要从spring容器中获取,所以使用了DelegatingFilterProxy来代理shiroFilter。

    DelegatingFilterProxy是spring对于servlet filter的通用代理类,指定targetBeanName后,可以从容器中获取filter实例。

  • 相关阅读:
    潘石屹出售上海外滩金融中心股权 4年没新增投资
    【BZOJ4036】【洛谷3175】【HAOI2015】—按位或(FMT+期望dp)
    信托配资清理“逃生通道”隐情
    中国在移动端的营销水平已超国外
    “土豪”们的新人生模型
    苏宁的逆市“O2O进化论”
    信汇中正领导力打造“必读12篇”之路
    你是否真的需要说声“谢谢”?
    新主管如何快速上手
    雷军和黄章又掐架了 不就是“不服跑个分”嘛
  • 原文地址:https://www.cnblogs.com/ouym/p/14780367.html
Copyright © 2020-2023  润新知