0、寫在前面的話
最近在考慮權限相關的東西,因而就找到了Shiro,開濤老師的Shiro教程博客(《
跟我學Shiro》)寫得實在很好還帶全部源碼,因此我也就沒有本身再總結各個階段的筆記,只在這裏對整個框架的核心類和部分執行過程進行了梳理和概述,以做備忘。
一、Shiro的主要特性
Shiro提供瞭如上圖所示的特性,其中主要特性(其開發團隊稱之爲應用安全的四大基石)以下:
- Authentication - 身份認證 (與登錄相關,肯定用戶是誰)
- Authorization - 確認權限 (肯定用戶能訪問什麼)
- Session Management - 會話管理
- Cryptography - 數據加密
二、Shiro如何工做
2.1 從外部看Shiro
應用代碼的交互對象是 「Subject」,該對象表明了當前 「用戶」,而全部用戶的安全操做都會交給 SecurityManager 來管理,而管理過程當中會從 Realm 中獲取用戶對應的角色和權限,能夠把 Realm 堪稱是安全數據源。
也就是說,咱們要使用最簡單的 Shiro 應用:
- 經過 Subject 來進行認證和受權,而 Subject 又委託給了 SecurityManager 進行管理
- 咱們須要給 SecurityManager 注入 Realm 以便其獲取用戶和權限進行判斷
- (也即,Shiro 不提供用戶和權限的維護,須要由開發者自行經過 Realm 注入)
2.2 從內部看Shiro
如上所述,也就能夠明白 Shiro 內部的架構以下:
三、Shiro身份認證概述
先來看一段簡單的代碼,shiro.ini爲配置文件,類爲用於說明流程的代碼測試類:
#shiro.ini
[users]
zhang=123
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();
}
能夠看到,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 的靈活程度可見一斑。
四、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"]
而 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);
}
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);
}
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
實際上你會發現,基於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>
@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;
}
五、參考連接