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專案中的位置:
主要的檔案有四個
ShiroConfig
、RetryLimitHashedCredentialsMatcher
、UserRealm
、MShiroFilterFactoryBean
。在這裡`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
類寫的有點多,我理解的也有點不足,有些片面,如果有不對的地方,請讀者幫我指正,我及時改過來。