項目啟動時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實例。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM