1. 程式人生 > 實用技巧 >shiro框架---shiro配置介紹

shiro框架---shiro配置介紹

shiro在springboot專案中的配置步驟

1、引入依賴

  首先shiro的應用,引入的依賴僅僅只有一個,即下邊這個。

 <dependency>
   <groupId>org.apache.shiro</groupId>
      <artifactId>shiro-all</artifactId>
      <version>1.3.2</version>
  </dependency>
  • 1
  • 2
  • 3
  • 4
  • 5

2、shiro在springboot專案中的位置:

  以下是shiro在springboot專案中的位置:


  主要的檔案有四個ShiroConfigRetryLimitHashedCredentialsMatcherUserRealmMShiroFilterFactoryBean。在這裡`UserOAuthMatcher沒有用到,其實它跟RetryLimitHashedCredentialsMatcher是一樣的意思,都是實現的同一個shiro的介面,用哪一個都可以,我在下邊的連結裡就不放進UserOAuthMatcher了。

3、以上配置檔案的主要功能:

(1)shiroConfig
  關於shiroConfig的配置主要內容,其實就是下圖的這些:

  以下是shiroConfig
檔案的全部配置。其他的配置檔案都是圍繞這個檔案展開工作的。
package microservice.fpzj.shiro;
import org.apache.shiro.authc.credential.CredentialsMatcher;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.filter.authc.AnonymousFilter;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.web.filter.DelegatingFilterProxy;
import javax.servlet.DispatcherType;
import javax.servlet.Filter;
import java.util.LinkedHashMap;
import java.util.Map;
@Configuration
public class ShiroConfig {
    @Bean
    public FilterRegistrationBean filterRegistrationBean() {
        FilterRegistrationBean filterRegistration = new FilterRegistrationBean();
        filterRegistration.setFilter(new DelegatingFilterProxy("shiroFilter")); 
        filterRegistration.setEnabled(true);
        filterRegistration.addUrlPatterns("/*"); //過濾規則,即所有的請求
        filterRegistration.setDispatcherTypes(DispatcherType.REQUEST);
        return filterRegistration;
    }

    /**
     * 這個即是上邊呼叫的shiroFilter過濾器,也就是shiro配置的過濾器
     * @return
     */
    @Bean(name = "shiroFilter")
    public ShiroFilterFactoryBean shiroFilter(){
        /**
        *MShiroFilterFactoryBean指向自定義過濾器,自定義過濾器對js/css等忽
        *略
        **/
        ShiroFilterFactoryBean bean = new MShiroFilterFactoryBean(); 
        bean.setSecurityManager(securityManager());
        bean.setLoginUrl("/login");
        bean.setUnauthorizedUrl("/unauthor");
        Map<String, Filter>filters = new LinkedHashMap<>();
//      filters.put("perms", urlPermissionsFilter());
        filters.put("anon", new AnonymousFilter());
        bean.setFilters(filters);
        //shiro配置過濾規則少量的話可以用hashMap,數量多了要用LinkedHashMap,保證有序,原因未知
        Map<String, String> chains = new LinkedHashMap<>();
        chains.put("/login", "anon");
        chains.put("/unauthor", "anon");
        chains.put("/logout", "anon");
        chains.put("/weblogin", "anon");
        chains.put("/**", "authc");
        bean.setFilterChainDefinitionMap(chains);
        return bean;
    }

    /**
     * @see org.apache.shiro.mgt.SecurityManager
     * @return
     */
    @Bean(name="securityManager")
    public DefaultWebSecurityManager securityManager() {
        DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
        manager.setRealm(userRealm());
        //manager.setCacheManager(cacheManager());
        manager.setSessionManager(defaultWebSessionManager());
        return manager;
    }

    /**
     * @see DefaultWebSessionManager
     * @return
     */
    @Bean(name="sessionManager")
    public DefaultWebSessionManager defaultWebSessionManager() {
        DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
        //sessionManager.setCacheManager(cacheManager());
        sessionManager.setGlobalSessionTimeout(1800000);
        sessionManager.setDeleteInvalidSessions(true);
        sessionManager.setSessionValidationSchedulerEnabled(true);
        sessionManager.setDeleteInvalidSessions(true);
        sessionManager.setSessionIdCookie(getSessionIdCookie());
        return sessionManager;
    }
    /**
     * 給shiro的sessionId預設的JSSESSIONID名字改掉
     * @return
     */
    @Bean(name="sessionIdCookie")
    public SimpleCookie getSessionIdCookie(){
        SimpleCookie simpleCookie = new SimpleCookie("webcookie");
        /**
         * HttpOnly標誌的引入是為了防止設定了該標誌的cookie被JavaScript讀取,
         * 但事實證明設定了這種cookie在某些瀏覽器中卻能被JavaScript覆蓋,
         * 可被攻擊者利用來發動session fixation攻擊
         */
        simpleCookie.setHttpOnly(true);
        /**
         * 設定瀏覽器cookie過期時間,如果不設定預設為-1,表示關閉瀏覽器即過期
         * cookie的單位為秒 比如60*60為1小時
         */
        simpleCookie.setMaxAge(-1);
        return simpleCookie;
    }

    /**
     * @see UserRealm--->AuthorizingRealm
     * @return
     */
    @Bean
    @DependsOn(value="lifecycleBeanPostProcessor")
    public UserRealm userRealm() {
        UserRealm userRealm = new UserRealm();
        userRealm.setCredentialsMatcher(credentialsMatcher());
        //userRealm.setCacheManager(cacheManager());
        return userRealm;
    }
    @Bean(name="credentialsMatcher")
    public CredentialsMatcher credentialsMatcher() {
        return new RetryLimitHashedCredentialsMatcher();
    }


    /*@Bean
    public EhCacheManager cacheManager() {
        EhCacheManager cacheManager = new EhCacheManager();
        cacheManager.setCacheManagerConfigFile("classpath:ehcache.xml");
        return cacheManager;
    }*/

    @Bean
    public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
        return new LifecycleBeanPostProcessor();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137

下邊針對於shiroConfig檔案中的配置,一一說明。
  首先該檔案的第一個配置為FilterRegistrationBean。 springboot注入過濾器有多種方式,一種是最簡單的@WebFilter註解,一種就是下邊的這種,寫一個FilterRegistrationBean,然後將自定義過濾器set進去,下邊是通過DelegatingFilterProxy代理的方式,注入容器中名字為shiroFilter的過濾器,最後設定過濾器的規則。

@Bean
    public FilterRegistrationBean filterRegistrationBean() {
        FilterRegistrationBean filterRegistration = new FilterRegistrationBean();
        /**
        *DelegatingFilterProxy做的事情是代理Filter的方法,從application
        *context裡獲得bean,從下邊可以理解到,它是將容器中名字為shiroFilter
        *的過濾器加入到過濾器註冊bean中
        **/
        filterRegistration.setFilter(new DelegatingFilterProxy("shiroFilter")); 
        filterRegistration.setEnabled(true);
        filterRegistration.addUrlPatterns("/*"); //過濾規則,即所有的請求
        filterRegistration.setDispatcherTypes(DispatcherType.REQUEST);
        return filterRegistration;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

  既然上邊注入的是名字為shiroFilter的過濾器,那下邊就是shiroFilter的配置

/**
     * 這個即是上邊呼叫的shiroFilter過濾器,也就是shiro配置的過濾器
     * @return
     */
    @Bean(name = "shiroFilter")
    public ShiroFilterFactoryBean shiroFilter(){
        /**
        *MShiroFilterFactoryBean指向自定義過濾器,自定義過濾器對js/css等忽
        *略
        **/
        ShiroFilterFactoryBean bean = new MShiroFilterFactoryBean(); 
        bean.setSecurityManager(securityManager());
        bean.setLoginUrl("/login");
        bean.setUnauthorizedUrl("/unauthor");
        Map<String, Filter>filters = new LinkedHashMap<>();
//      filters.put("perms", urlPermissionsFilter());
        /**
        * shiro自己的過濾器,anon,表示不攔截的路徑,authc,表示攔截的路徑
        **/
        filters.put("anon", new AnonymousFilter());
        bean.setFilters(filters);
        /*
        *shiro配置過濾規則少量的話可以用hashMap,數量多了要用
        *LinkedHashMap,保證有序,原因未知。
        *,anon,表示不攔截的路徑,authc,表示攔截的路徑。匹配時,首先匹配
        *anon的,然後最後匹配authc
        **/
        Map<String, String> chains = new LinkedHashMap<>();
        chains.put("/login", "anon");
        chains.put("/unauthor", "anon");
        chains.put("/logout", "anon");
        chains.put("/weblogin", "anon");
        chains.put("/**", "authc"); 
        bean.setFilterChainDefinitionMap(chains);
        return bean;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37

  以上的shiroFilter配置中,又引入了我們的第二個配置檔案,名字為MShiroFilterFactoryBean,該類繼承了ShiroFilterFactoryBean類,通過名字,應該大體能知道,它是shiro的過濾器工廠類,而我們的MShiroFilterFactoryBean類就是一個自定義的shiro過濾器,為什麼要自己寫一個過濾器呢?
  在當前的shiro框架中,無法攔截那種.js.css.html.jsp等等帶有.的請求路徑,對於前三種這種靜態資源,我們可以不納入shiro攔截,即可以在不登入的情況下訪問成功,但是我們現在有這樣的需求,即對.jsp也要攔截起來,那就需要自定義shiro過濾器了,即現在MShiroFilterFactoryBean類存在的意義。關於該類的解釋,在後邊再說,我們繼續shiroConfig檔案的介紹。
  在上邊的配置中,其實就是自定義了一個shiro過濾器,然後對其進行了一些操作,其中bean.setLoginUrl("/login")是在專案啟動後,如果沒有登入的情況下,會被shiro強制請求的路徑,即為/login
  另外,bean.setSecurityManager(securityManager());這句的配置,即引入設定shiro的控制中心,即securityManager,安全管理器;即所有與安全有關的操作都會與securityManager互動;且它管理著所有Subject;可以看出它是Shiro的核心,它負責與後邊介紹的其他元件進行互動,如果學習過SpringMVC,你可以把它看成DispatcherServlet前端控制器;關於subject,你就理解為是每一個訪問系統的使用者物件即可,所有的訪問使用者的情況都是一種subject的體現,它們又統一被securityManager管理,這個在第一篇裡已經說過。
  通過上邊的shiroFilter的配置之後,然後再看securityManager

/**
     * @see org.apache.shiro.mgt.SecurityManager
     * @return
     */
    @Bean(name="securityManager")
    public DefaultWebSecurityManager securityManager() {
        DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
        manager.setRealm(userRealm());
        //manager.setCacheManager(cacheManager());
        manager.setSessionManager(defaultWebSessionManager());
        return manager;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

  以上又引入了我們的第三個配置檔案,即UserRealm檔案,改檔案又引出了我們的一個概念,Realm:域,Shiro從從Realm獲取安全資料(如使用者、角色、許可權),就是說SecurityManager要驗證使用者身份,那麼它需要從Realm獲取相應的使用者進行比較以確定使用者身份是否合法;也需要從Realm得到使用者相應的角色/許可權進行驗證使用者是否能進行操作;可以把Realm看成DataSource,即安全資料來源。
  在這裡的UserRealm類繼承於AuthorizingRealm,該類的作用其實有使用者密碼驗證、許可權授權等。這個也會在後邊貼出來,先繼續講shiroConfig檔案。
  通過上邊的manager.setSessionManager(defaultWebSessionManager());然後引入下邊的配置

/**
     * @see DefaultWebSessionManager
     * @return
     */
    @Bean(name="sessionManager")
    public DefaultWebSessionManager defaultWebSessionManager() {
        DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
        //sessionManager.setCacheManager(cacheManager());
        sessionManager.setGlobalSessionTimeout(1800000);
        sessionManager.setDeleteInvalidSessions(true);
        sessionManager.setSessionValidationSchedulerEnabled(true);
        sessionManager.setDeleteInvalidSessions(true);
        sessionManager.setSessionIdCookie(getSessionIdCookie());
        return sessionManager;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

  上邊的配置,主要是對session的配置,比如,超時時間的設定等,基本上都是跟session相關的配置。另外,上邊還有getSessionIdCookie()方法的引用,眾所周知,瀏覽器與後臺系統互動的方式,是以後臺儲存session,然後將該session對應key,以字串的形式返給瀏覽器,並在瀏覽器中以cookie的形式記錄起來,方便後續的訪問,如果瀏覽器丟失了這個cookie,那就會失去與後臺系統的聯絡,必須重新登入,才能重新再生成這個cookie。而getSessionIdCookie()方法,即是對cookie在瀏覽器那裡的名字的定義,如下:

/**
     * 給shiro的sessionId預設的JSSESSIONID名字改掉
     * @return
     */
    @Bean(name="sessionIdCookie")
    public SimpleCookie getSessionIdCookie(){
        SimpleCookie simpleCookie = new SimpleCookie("webcookie");
        /**
         * HttpOnly標誌的引入是為了防止設定了該標誌的cookie被JavaScript讀取,
         * 但事實證明設定了這種cookie在某些瀏覽器中卻能被JavaScript覆蓋,
         * 可被攻擊者利用來發動session fixation攻擊
         */
        simpleCookie.setHttpOnly(true);
        /**
         * 設定瀏覽器cookie過期時間,如果不設定預設為-1,表示關閉瀏覽器即過期
         * cookie的單位為秒 比如60*60為1小時
         */
        simpleCookie.setMaxAge(-1);
        return simpleCookie;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

  以上配置起的名字為webcookie,這個webcookie就是瀏覽器那邊儲存後臺傳入過來的cookie的key。整個的大體流程,我理解的如下:

  以後再請求,只要瀏覽器沒有清除cookie,上邊關於session的超時時間沒有超時,就可以正常訪問系統。之所以,這裡要給sessionid起一個名字webcookie這是防止瀏覽器訪問多個系統的時候,恰巧碰上兩個系統在瀏覽器那邊儲存sessionid對應的key正好相同,即session汙染,造成訪問系統出現問題。如果不設定,shiro預設的sessionid在前端瀏覽器的名字為cookie
  關於session汙染的異常,我遇到的是下邊這個:

 Found 'sid' cookie value [1a22b751-0542-4e74-a8e7-59942692f6ae]
22:13:37 DEBUG net.sf.ehcache.Cache - mx-master-SessionCache cache - Miss
22:13:37 DEBUG o.a.shiro.mgt.DefaultSecurityManager - Resolved SubjectContext context session is invalid.  Ignoring and creating an anonymous (session-less) Subject instance.
org.apache.shiro.session.UnknownSessionException: There is no session with id [1a22b751-0542-4e74-a8e7-59942692f6ae]
  • 1
  • 2
  • 3
  • 4

  如果出現There is no session with id的異常,不出意外的話,就是上邊的配置有問題,需要有cookie的配置,相關的文章,你可以看這一篇一個專案兩個web模組會導致shiro的session汙染,可以得到解釋。
  繼續shiroconfig檔案的配置,然後再後邊就是如下配置:

    @Bean
    @DependsOn(value="lifecycleBeanPostProcessor")
    public UserRealm userRealm() {
        UserRealm userRealm = new UserRealm();
        userRealm.setCredentialsMatcher(credentialsMatcher());
        //userRealm.setCacheManager(cacheManager());
        return userRealm;
    }
    @Bean(name="credentialsMatcher")
    public CredentialsMatcher credentialsMatcher() {
        return new RetryLimitHashedCredentialsMatcher();
    }
    @Bean
    public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
        return new LifecycleBeanPostProcessor();
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

  上邊有userRealm類的注入,而在前邊securityManager的配置的時候,它引入了userRealm,就是在這裡對userRealm類注入的。
  另外,上邊userRealm方法中設定了一個credentialsMatcher(),該方法對應的就是new RetryLimitHashedCredentialsMatcher()類,這裡就引入了我們第四個配置檔案RetryLimitHashedCredentialsMatcher類,該類,繼承於HashedCredentialsMatcher類,而HashedCredentialsMatcher你如果往上找,其實就是實現了CredentialsMatcher介面,所以這裡注入的時候,可以以自定義的RetryLimitHashedCredentialsMatcher類注入成CredentialsMatcher,該類的功能主要是將使用者輸入的密碼與查詢到的密碼進行比較,也就是密碼比較器。
  這個shiroConfig類寫的有點多,我理解的也有點不足,有些片面,如果有不對的地方,請讀者幫我指正,我及時改過來。