0、寫在前面的話
最近在考慮權限相關的東西,於是就找到了Shiro,開濤老師的Shiro教程博客(《
跟我學Shiro》)寫得實在很好還帶所有源碼,所以我也就沒有自己再總結各個階段的筆記,只在這里對整個框架的核心類和部分執行過程進行了梳理和概述,以作備忘。
1、Shiro的主要特性

Shiro提供了如上圖所示的特性,其中主要特性(其開發團隊稱之為應用安全的四大基石)如下:
- Authentication - 身份認證 (與登陸相關,確定用戶是誰)
- Authorization - 確認權限 (確定用戶能訪問什么)
- Session Management - 會話管理
- Cryptography - 數據加密
2、Shiro如何工作
2.1 從外部看Shiro

應用代碼的交互對象是 “Subject”,該對象代表了當前 “用戶”,而所有用戶的安全操作都會交給 SecurityManager 來管理,而管理過程中會從 Realm 中獲取用戶對應的角色和權限,可以把 Realm 堪稱是安全數據源。
也就是說,我們要使用最簡單的 Shiro 應用:
- 通過 Subject 來進行認證和授權,而 Subject 又委托給了 SecurityManager 進行管理
- 我們需要給 SecurityManager 注入 Realm 以便其獲取用戶和權限進行判斷
- (也即,Shiro 不提供用戶和權限的維護,需要由開發者自行通過 Realm 注入)
2.2 從內部看Shiro
如上所述,也就可以明白 Shiro 內部的架構如下:

3、Shiro身份認證概述
先來看一段簡單的代碼,shiro.ini為配置文件,類為用於說明流程的代碼測試類:
#shiro.ini
[users]
zhang=123
wang=123
4
1
#shiro.ini
2
[users]
3
zhang=123
4
wang=123
@Test
public void testHelloWorld() {
//獲取SecurityManager工廠,使用shiro.ini配置文件進行初始化
Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
//得到SecurityManager實例,並綁定給SecurityUtils
SecurityManager securityManager = factory.getInstance();
SecurityUtils.setSecurityManager(securityManager);
//得到Subject及創建用戶名/密碼身份驗證token(即用戶身份/憑證)
Subject subject = SecurityUtils.getSubject();
AuthenticationToken token = new UsernamePasswordToken("zhang", "123");
try {
//登陸
subject.login(token);
} catch (AuthenticationException e) {
//身份驗證失敗
e.printStackTrace();
}
//斷言用戶已經登陸
Assert.assertEquals(true, subject.isAuthenticated());
//退出
subject.logout();
}
27
1
2
public void testHelloWorld() {
3
//獲取SecurityManager工廠,使用shiro.ini配置文件進行初始化
4
Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
5
6
//得到SecurityManager實例,並綁定給SecurityUtils
7
SecurityManager securityManager = factory.getInstance();
8
SecurityUtils.setSecurityManager(securityManager);
9
10
//得到Subject及創建用戶名/密碼身份驗證token(即用戶身份/憑證)
11
Subject subject = SecurityUtils.getSubject();
12
AuthenticationToken token = new UsernamePasswordToken("zhang", "123");
13
14
try {
15
//登陸
16
subject.login(token);
17
} catch (AuthenticationException e) {
18
//身份驗證失敗
19
e.printStackTrace();
20
}
21
22
//斷言用戶已經登陸
23
Assert.assertEquals(true, subject.isAuthenticated());
24
25
//退出
26
subject.logout();
27
}
可以看到,SecurityManager通過配置文件進行實例化(通過工廠類產出),該配置文件中簡單配置了用戶名和密碼,實際上配置文件上還有很多內容可以配置諸如自定義的各種類等,都可以注入到Shiro中進行替換,此處就不再展開詳述。
SecurityManager 是 Shiro 的核心,這里把 SecurityManager 綁定到 SecurityUtils 中,只是為了方便后續調用一些方法。比如登陸方法 login(),看似是 subject.login() 在調用,實際上其內部也是調用了 SecurityManager 的 login() 方法。
用戶的登陸信息是封裝到 AuthenticationToken 實現類中進行傳遞的,這里使用了 Shiro 中內置的一個簡單實現類 UsernamePasswordToken,然后通過 login() 方法層層調用,最終用這個 token 做了下面的事情:
- 確定 Realm 的數量,根據 Realm 是否單一來確定執行方法 doSingleRealmAuthentication() 或 doMultiRealmAuthentication()
- 不論哪個方法都會要求通過 Realm 和 token(getAuthenticationInfo(AuthenticationToken token)) 返回認證信息 AuthenticationInfo
- 而如何確定並返回這個信息,也即是確認用戶登錄信息和授權信息的過程,是由開發者自定義(如通過數據庫抓取信息對比判斷等)
- 自定義 Realm 必須實現 Realm 接口,更簡單快捷的方式是繼承抽象類 AuthenticatingRealm
- 繼承 AuthenticatingRealm 則需要分別實現認證方法doGetAuthenticationInfo() 和 授權方法 doGetAuthorizationInfo()
在 doGetAuthenticationInfo() 中還可以自定義密碼匹配策略 CredentialsMatcher,將會進一步調用 assertCredentialsMatch() 進行密碼匹配判定。當然,這些都是自行擴展,也由此 Shiro 的靈活程度可見一斑。
4、Shiro 的權限控制
Shiro 的權限控制是通過過濾器來實現的,所以其核心對象 ShiroFilter 就是整個 Shiro Web 中的門戶,所有請求都會被 ShiroFilter 過濾並進行相應的鏈式處理。
這個處理流程是這樣的:
- ShiroFilter 執行過濾器鏈
- 通過原始過濾器鏈獲取新的過濾器鏈
- FilterChainResolver 解析 url,找到對應的新的 FilterChain 過濾器鏈
- 執行新的過濾器鏈
AbstractShiroFilter
//如ShiroFilter/ SpringShiroFilter都繼承該Filter
- doFilter //Filter的doFilter
- doFilterInternal //轉調doFilterInternal
- executeChain(request, response, chain) //執行過濾器鏈
- FilterChain chain = getExecutionChain(request, response, origChain) //使用原始過濾器鏈獲取新的過濾器鏈
- chain.doFilter(request, response) //執行新組裝的過濾器鏈
- getExecutionChain(request, response, origChain) //獲取過濾器鏈流程
- FilterChainResolver resolver = getFilterChainResolver(); //獲取相應的FilterChainResolver
- FilterChain resolved = resolver.getChain(request, response, origChain); //通過FilterChainResolver根據當前請求解析到新的FilterChain過濾器鏈
注:FilterChainResolver 的實現類中往往通過 FilterChainManager (核心屬性 filters / filterChains)維護過濾器關系鏈
4.1 默認過濾器
Shiro 內部提供了一個路徑匹配的 FilterChainResolver 實現:PathMatchingFilterChainResolver,它會解析 shiro.ini 配置文件中 [urls] 的url模式:
[urls]
#authc 需要通過身份驗證
#anon 匿名訪問(即不需要登陸)
#roles 有角色限制,如roles[admin]表示需要admin角色才能訪問
#perms 有權限限制,如perms["user:create"]表示要有"user:create"權限才能訪問
/login=anon
/unauthorized=anon
/static/**=anon
/authenticated=authc
/role=authc,roles[admin]
/permission=authc,perms["user:create"]
11
1
[urls]
2
#authc 需要通過身份驗證
3
#anon 匿名訪問(即不需要登陸)
4
#roles 有角色限制,如roles[admin]表示需要admin角色才能訪問
5
#perms 有權限限制,如perms["user:create"]表示要有"user:create"權限才能訪問
6
/login=anon
7
/unauthorized=anon
8
/static/**=anon
9
/authenticated=authc
10
/role=authc,roles[admin]
11
/permission=authc,perms["user:create"]
而 PathMatchingFilterChainResolver 內部通過 FilterChainManager 維護着過濾器鏈,比如 DefaultFilterChainManager 實現維護着url模式與過濾器鏈的關系。因此我們可以通過 FilterChainManager 進行動態增加url模式與過濾器鏈的關系。
DefaultFilterChainManager 在實例化時會通過構造函數默認添加 DefaultFilter 中聲明的過濾器:
public DefaultFilterChainManager(FilterConfig filterConfig) {
this.filters = new LinkedHashMap<String, Filter>();
this.filterChains = new LinkedHashMap<String, NamedFilterList>();
setFilterConfig(filterConfig);
//默認添加 DefaultFilter 中聲明的過濾器
addDefaultFilters(true);
}
7
1
public DefaultFilterChainManager(FilterConfig filterConfig) {
2
this.filters = new LinkedHashMap<String, Filter>();
3
this.filterChains = new LinkedHashMap<String, NamedFilterList>();
4
setFilterConfig(filterConfig);
5
//默認添加 DefaultFilter 中聲明的過濾器
6
addDefaultFilters(true);
7
}
public enum DefaultFilter {
anon(AnonymousFilter.class),
authc(FormAuthenticationFilter.class),
authcBasic(BasicHttpAuthenticationFilter.class),
logout(LogoutFilter.class),
noSessionCreation(NoSessionCreationFilter.class),
perms(PermissionsAuthorizationFilter.class),
port(PortFilter.class),
rest(HttpMethodPermissionFilter.class),
roles(RolesAuthorizationFilter.class),
ssl(SslFilter.class),
user(UserFilter.class);
}
15
1
public enum DefaultFilter {
2
3
anon(AnonymousFilter.class),
4
authc(FormAuthenticationFilter.class),
5
authcBasic(BasicHttpAuthenticationFilter.class),
6
logout(LogoutFilter.class),
7
noSessionCreation(NoSessionCreationFilter.class),
8
perms(PermissionsAuthorizationFilter.class),
9
port(PortFilter.class),
10
rest(HttpMethodPermissionFilter.class),
11
roles(RolesAuthorizationFilter.class),
12
ssl(SslFilter.class),
13
user(UserFilter.class);
14
15
}
4.2 自定義過濾器
如果要注冊自定義過濾器,IniSecurityManagerFactory / WebIniSecurityManagerFactory 在啟動時會自動掃描ini配置文件中的 [filters] / [main] 部分並注冊這些過濾器到 DefaultFilterChainManager;且創建相應的url模式與其過濾器關系鏈。
在 DefaultFilterChainManager 中有兩個屬性 Map<String, Filter> filters 和 Map<String, NamedFilterList> filterChains,這意味着我們即可注入自定義的 “過濾器” 和 “過濾器關系鏈(匹配鏈,即什么url對應執行什么filter)”
而我們自定義過濾器要做的兩件事:
- 完成自定義過濾器的編寫
- 將自定義過濾器注入到 filterChains 中去
顯然在讀取配置文件 shiro.ini 時就將其中的 [filters] 部分的自定義過濾器載入了:
[filters]
myFilter1=com.github.zhangkaitao.shiro.chapter8.web.filter.MyOncePerRequestFilter
myFilter2=com.github.zhangkaitao.shiro.chapter8.web.filter.MyAdviceFilter
[urls]
/**=myFilter1,myFilter2
1
1
[filters]
2
myFilter1=com.github.zhangkaitao.shiro.chapter8.web.filter.MyOncePerRequestFilter
3
myFilter2=com.github.zhangkaitao.shiro.chapter8.web.filter.MyAdviceFilter
4
[urls]
5
/**=myFilter1,myFilter2
實際上你會發現,基於Spring或者SpringBoot,這種套路也是類似的,不過是面向對象(面向Bean)而已:
<!-- spring -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager" />
<property name="loginUrl" value="/security/login.html" />
<property name="successUrl" value="/home.html" />
<property name="unauthorizedUrl" value="/security/unauthorized.html" />
<property name="filters">
<map>
<entry key="anyRoles" value-ref="anyRolesAuthorizationFilter" />
</map>
</property>
<property name="filterChainDefinitions">
<value>
/admin = anyRoles[admin1,admin2]
/** = anon
</value>
</property>
</bean>
1
18
1
<!-- spring -->
2
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
3
<property name="securityManager" ref="securityManager" />
4
<property name="loginUrl" value="/security/login.html" />
5
<property name="successUrl" value="/home.html" />
6
<property name="unauthorizedUrl" value="/security/unauthorized.html" />
7
<property name="filters">
8
<map>
9
<entry key="anyRoles" value-ref="anyRolesAuthorizationFilter" />
10
</map>
11
</property>
12
<property name="filterChainDefinitions">
13
<value>
14
/admin = anyRoles[admin1,admin2]
15
/** = anon
16
</value>
17
</property>
18
</bean>
@Bean
public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager){
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
//設置SecurityManager
shiroFilterFactoryBean.setSecurityManager(securityManager);
//自定義過濾器
Map<String, Filter> filtersMap = new LinkedHashMap<String, Filter>();
filtersMap.put("myAccessControlFilter", new MyAccessControlFilter());
shiroFilterFactoryBean.setFilters(filtersMap);
//過濾器
Map<String,String> filterChainDefinitionMap = new LinkedHashMap<String,String>();
filterChainDefinitionMap.put("/createPermission", "anon");
filterChainDefinitionMap.put("/**", "myAccessControlFilter");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
1
2
public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager){
3
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
4
5
//設置SecurityManager
6
shiroFilterFactoryBean.setSecurityManager(securityManager);
7
//自定義過濾器
8
Map<String, Filter> filtersMap = new LinkedHashMap<String, Filter>();
9
filtersMap.put("myAccessControlFilter", new MyAccessControlFilter());
10
shiroFilterFactoryBean.setFilters(filtersMap);
11
12
//過濾器
13
Map<String,String> filterChainDefinitionMap = new LinkedHashMap<String,String>();
14
15
filterChainDefinitionMap.put("/createPermission", "anon");
16
filterChainDefinitionMap.put("/**", "myAccessControlFilter");
17
18
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
19
return shiroFilterFactoryBean;
20
}