Shiro核心概述


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;
}


五、參考連接

相關文章
相關標籤/搜索