1. 程式人生 > >Shiro框架基礎知識歸納

Shiro框架基礎知識歸納

1、URL基本配置:其格式URL=攔截器[引數],攔截器[引數];例如:
   /login.jsp = anon
  •  anno:(anonymous)攔截器表示匿名訪問(即不登入即可訪問)。
  • authc:(authentication)攔截器表示需要身份認證後才能訪問。

URL配置模式使用了Ant風格模式 :Ant路徑萬用字元支援?、*、**需要注意的是匹配符不包含目錄分割符/。

  • _?:匹配一個字元。
  • _*  :匹配零個或多個字串。
  • _** :匹配路徑中的零個或多個路徑。

注意:URL許可權採取第一次匹配優先的方式。

2、身份驗證:在Shrio中使用者需要提高principals(身份)和credentials(證明)給Shiro,從而能驗證使用者身份。

    基本流程:1)、呼叫securityManager獲取subject。

                     2)、驗證當前使用者是否登入,subject.isAuthenticated()。

                     3)、若未驗證,則將使用者名稱和密碼封裝為UsernamePasswordToken物件。

                     4)、呼叫subject.login(AutenticationToken)方法驗證,會呼叫5中的方法,實現使用者認證操作。

                     5)、自定義Realm,從資料庫中獲取使用者記錄,返回給Shiro。

                         5.1)實際上只需繼承 org.apache.shiro.realm.AuthenticatingRealm 類。

                         5.2)實現doGetAuthenticationInfo(AuthenticationToken) 方法。---->方法中的Token==login方法傳過來的Token。

                     6)、由Shiro完成密碼比對。

3、因為Shiro快取的原因,當用戶Token登入成功之後,你回退重新登入Token但是密碼錯誤時,發現扔能夠登入成功。所以當每次結束時需要出發logout命令,簡單辦法如下,在Shiro攔截其中新增如下程式碼。/shiro/logout==需要傳送的URL。

   /shiro/logout = logout
4、身份認證流程:
  1. 首先呼叫Subject.login(token)進行登入,其會委託給SecurityManager
  2. securityManager負責真正的身份驗證邏輯;他會委託給Authenticator進行身份認證。
  3. Authentication才是真正的身份驗證者,Shrio API中核心的身份認證入口點,此處可以插入自己的實現。
  4. Authenticator可能會委託給相應的AuthenticationStrategy進行多Realm身份認證,預設ModularRealmAuthenticator會呼叫AuthenticationStrategy進行多Real身份認證。
  5. Authenticator會把相應的token傳入Realm,從realm獲取身份認證資訊,如果沒有返回/丟擲異常表示認證失敗。此處可以配置多個realm,將按照相應的順序及策略進行訪問。

5、Realm:Shiro從Realm中獲取安全資料(如使用者,角色,許可權)即SecurityManager要驗證使用者身份,需要從Realm中獲取相應的使用者進行比較以確定使用者的身份是否合法;需要從使用者中得要相應的角色和許可權判斷使用者是否可以操作。

6、Realm一般繼承AuthorizingRealm(授權)即可;其繼承了AuthenticatingRealm(即身份認證),而他也間接繼承了CachingRealm(帶有快取實現)。

7、Authenticator的職責是驗證使用者賬號,是Shiro API中身份認證的核心入口點,如果驗證成功。將返回AuthenticationInfo驗證資訊;此資訊中包含身份及憑證;如果異常則會丟擲相應的AuthenticationException異常。

8、AuthenticationStrategy介面的預設實現:

  • FirstSuccessfulStrategy:只要有一個Realm驗證成功即可,只返回第一個Realm身份認證成功的認證資訊,其它忽略。
  • AtLeastOneSuccessfulStrategy:只要有一個Realm驗證成功即可,和FirstSuccessfulStrategy不同的是。將返回所有認證成功的Realm資訊。
  • AllSuccessfulStrategy:所有Realm認證成功才算成功,且返回所有Realm身份認證資訊,如果一個失敗就是失敗。

   **ModularRealmAuthenticator預設是AtLeastOneSusccessfulStrategy策略。

修改認證策略:

    <bean id="authenticator" 
    	class="org.apache.shiro.authc.pam.ModularRealmAuthenticator">
    	<property name="authenticationStrategy">
    		<bean class="org.apache.shiro.authc.pam.AllSuccessfulStrategy"></bean>
    	</property>
    </bean>

9、當用戶輸入密碼時,我們通常會對密碼進行加密:在Shiro中我們需要對輸入的密碼加密後認證比較時,只需要在配置檔案中配置密碼的加密方式:

替換當前 Realm 的 credentialsMatcher 屬性. 直接使用 HashedCredentialsMatcher 物件, 並設定加密演算法即可.

    <bean id="jdbcRealm" class="com.yintong.shiro.realms.ShiroRealm">
    	<property name="credentialsMatcher">
    		<bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
    			<property name="hashAlgorithmName" value="MD5"></property>//加密方式
    			<property name="hashIterations" value="1024"></property>//加密次數
    		</bean>
    	</property>
    </bean>
10、如何使兩個原始密碼相同的密碼,加密後不一樣。(MD5鹽值加密)我們將使用者名稱看做是唯一的元素。
    ByteSource credentialsSalt = ByteSource.Util.bytes(username);
    SimpleAuthenticationInfo info = null; 
    info = new SimpleAuthenticationInfo(principal, credentials, credentialsSalt, realmName);
** realmName: 當前 realm 物件的 name. 呼叫父類的 getName() 方法即可
** String realmName = getName();
  •  使用 ByteSource.Util.bytes() 來計算鹽值.
  •  鹽值需要唯一: 一般使用隨機字串或 user id
  •  使用 new SimpleHash(hashAlgorithmName, credentials, salt, hashIterations); 來計算鹽值加密後的密碼的值.
    String hashAlgorithmName = "MD5";
    Object credentials = "123456";
    Object salt = ByteSource.Util.bytes("user");;
    int hashIterations = 1024;
    Object result = new SimpleHash(hashAlgorithmName, credentials, salt, hashIterations);

11、有時候我們會將使用者密碼存入多個庫,用不同的加密方式。例如:
    <bean id="jdbcRealm" class="com.yintong.shiro.realms.ShiroRealm">
    	<property name="credentialsMatcher">
    		<bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
    			<property name="hashAlgorithmName" value="MD5"></property>
    			<property name="hashIterations" value="1024"></property>
    		</bean>
    	</property>
    </bean>
    
    <bean id="secondRealm" class="com.yintong.shiro.realms.SecondRealm">
    	<property name="credentialsMatcher">
    		<bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
    			<property name="hashAlgorithmName" value="SHA1"></property>
    			<property name="hashIterations" value="1024"></property>
    		</bean>
    	</property>
    </bean>

當配置多個Realm時,需要配置autenticator來存放。
    <bean id="authenticator" 
    	class="org.apache.shiro.authc.pam.ModularRealmAuthenticator">
    	      <property name="realms">
            <list>
                <ref bean="jdbcRealm"/>
                <ref bean="secondRealm"/>
            </list>
        </property>
    </bean>

最後將authentication配置在SecurityManager中。
    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <property name="cacheManager" ref="cacheManager"/>
        <property name="authenticator" ref="authenticator"/>
    </bean>
12、通常我們會將多個Reals配置在SecurityManager中:(因為系統初始化的時候,會通過認證是否為ModularRealmAuthenticator,如果是則將Realms的值賦給Authencator)
    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <property name="cacheManager" ref="cacheManager"/>
        <property name="authenticator" ref="authenticator"></property>
        
        <property name="realms">
        	<list>
    			<ref bean="jdbcRealm"/>
    			<ref bean="secondRealm"/>
    		</list>
        </property>
        
        <property name="rememberMeManager.cookie.maxAge" value="10"></property>
    </bean>

13、授權:也叫訪問控制,即在應用中控制誰訪問那些資源(如訪問頁面、編輯資料、頁面操作等)在授權中需要了解基本關鍵物件。
  • 主體(Subject):訪問應用的使用者。
  • 資源(Resource):在應用中使用者可以訪問的URL。
  • 許可權(Permission):在應用中使用者能不能訪問某個資源。
  • 角色(Role):許可權的集合。

Shrio支援三種授權方式:

  • 程式設計式:通過if/else程式碼塊來完成。
  • 註解式:通過在執行的方法上放置相應的註解來完成,沒有許可權則丟擲相應異常。(@RequiresRoles("admin"))
  • JSP/GSP:在JSP/GSP頁面通過相應的標籤完成。(<Shrio:hasRole name = "admin">)

14、Shrio內建了許多預設攔截器,比如身份認證、授權等相關的。(可參考org.apache.shiro.web.filter.mgt.DefaultFilter中的列舉攔截器)。授權相關攔截器如下:


15、Permissions:規則---->資源識別符號:操作:物件實力ID------>即對那個資源的那個例項可以進行什麼樣的操作。預設支援萬用字元:(:表示資源/操作/例項的分割)

  • ,表示操作的分割。(user:query, edit)例項訪問控制(user:edit:manager)
  • *表示任意資源/操作/例項。(user:*)例項訪問使用萬用字元(user:edit:*)

流程如下:

  •  1、首先呼叫 Subject.isPermitted*/hasRole* 介面,其會委託給SecurityManager,而 SecurityManager 接著會委託給 Authorizer;
  •  2、Authorizer是真正的授權者,如果呼叫如isPermitted(“user:view”),其首先會通過
  • PermissionResolver 把字串轉換成相應的 Permission 例項;
  • 3、在進行授權之前,其會呼叫相應的 Realm 獲取 Subject 相應的角色/許可權用於匹配傳入的角色/許可權;
  • 4、Authorizer 會判斷 Realm 的角色/許可權是否和傳入的匹配,如果有多個Realm,會委託給 ModularRealmAuthorizer 進行迴圈判斷,如果匹配如 isPermitted*/hasRole* 會返回true,否則返回false表示授權失敗。

**** 授權需要繼承 AuthorizingRealm 類, 並實現其 doGetAuthorizationInfo 方法。AuthorizingRealm 類繼承自 AuthenticatingRealm, 但沒有實現 AuthenticatingRealm 中的 doGetAuthenticationInfo, 所以認證和授權只需要繼承 AuthorizingRealm 就可以了. 同時實現他的兩個抽象方法.

16、ModularRealmAuthorizer 進行多 Realm 匹配流程:

      – 1、首先檢查相應的 Realm 是否實現了實現了Authorizer;

      – 2、如果實現了 Authorizer,那麼接著呼叫其相應的isPermitted*/hasRole* 介面進行匹配;

      – 3、如果有一個Realm匹配那麼將返回 true,否則返回 false。

	//授權會被 shiro 回撥的方法
	@Override
	protected AuthorizationInfo doGetAuthorizationInfo(
			PrincipalCollection principals) {
		//1. 從 PrincipalCollection 中來獲取登入使用者的資訊
		Object principal = principals.getPrimaryPrincipal();
		
		//2. 利用登入的使用者的資訊來偽造當前使用者的角色或許可權(可能需要查詢資料庫)
		Set<String> roles = new HashSet<>();
		roles.add("user");
		if("admin".equals(principal)){
			roles.add("admin");
		}
		
		//3. 建立 SimpleAuthorizationInfo, 並設定其 reles 屬性.
		SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(roles);
		
		//4. 返回 SimpleAuthorizationInfo 物件. 
		return info;
	}

17、Shiro提供了JSTL標籤用於在JSP頁面進行許可權控制,如通過登入使用者顯示相應的按鈕。

  • guest標籤:使用者沒有登入時顯示相應資訊,即遊客訪問資訊。<shiro:guest>歡迎遊客訪問</shiro:guest>
  • user標籤:使用者已經認證/記住我登入之後顯示相應資訊。<shiro:user>歡迎xx登入</shiro:user>
  • authenticated標籤:使用者已經身份認證通過,即shiro.login登入成功,不是記住我登入的。<shiro:authenticated>使用者已認證</shiro:authenticated>
  • notAuthenticated標籤:使用者未進行身份認證,即沒有呼叫Subject.login登入,包括記住我登入的也輸入未認證登入。<shiro:noAuthenticated></shiro:noAuthenticated>
  • pincipal標籤:顯示使用者身份資訊,預設呼叫Subject.getPrincipal()獲取,即Primary Principal。<shrio:principal property="username"/>
  • hasRole標籤:如果當前Subject有角色,將顯示body體內容:<shiro:hasRole name="admin">使用者[<shiro:principal>]使用者角色admin<br/></shiro:hasRole>
  • hasAnyRole標籤:如果Subject有任意一個角色(或的關係)將顯示body中的內容。<shiro:hasAnyRole name="admin,user">使用者XX角色</shiro:hasAmyRole>
  • lacksRole標籤:如果當前Subject沒有角色將顯示body中的內容。<shiro:lacksRole>沒有xx角色</shiro:lacksRole>
  • hasPermission:如果當前Subject有許可權將顯示body中的內容。<shiro:hasPermission>擁有許可權user:create</br></shiro:hasPermission>
  • lacksPermission:如果當前Subject沒有許可權將顯示body中的內容<shiro:lacksPermission>沒有許可權org:create</br></shiro:lacksPermission>

18、許可權註解:

  • @RequiresAuthentication:表示當前Subject已經通過login進行了身份驗證;即 Subject. isAuthenticated() 返回 true.
  • @RequiresUser:表示當前 Subject 已經身份驗證或者通過記住我登入的。
  • @RequiresGuest:表示當前Subject沒有身份驗證或通過記住我登入過,即是遊客身份。
  • @RequiresRoles(value={“admin”, “user”}, logical=Logical.AND):表示當前 Subject 需要角色 admin 和user
  • @RequiresPermissions (value={“user:a”, “user:b”},logical= Logical.OR):表示當前 Subject 需要許可權 user:a 或user:b。

19、自定義攔截器:通過自定義攔截器可以擴充套件功能,例如:動態url-角色/許可權訪問控制的實現、根據 Subject 身份資訊獲取使用者資訊繫結到 Request(即設定通用資料)、驗證碼驗證、線上使用者資訊的儲存等。

我們一般在獲取使用者許可權的時候,都是從資料庫中獲取的。所以在配置檔案中會通過工廠方式創建出需要的物件。根據程式碼跟蹤,我們發現獲取到使用者許可權的時候會呼叫filterChainDefinitionMap方法,得到我們在配置中配置的所有許可權,已LinkedHashMap的方式收集。所以我們需要重新配置filterChainDefinitionMap。如下:

 <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
        <property name="securityManager" ref="securityManager"/>
        <property name="loginUrl" value="/login.jsp"/>
        <property name="successUrl" value="/list.jsp"/>
        <property name="unauthorizedUrl" value="/unauthorized.jsp"/>
        
        <property name="filterChainDefinitionMap" ref="filterChainDefinitionMap"></property>
 </bean>
    
    <!-- 配置一個 bean, 該 bean 實際上是一個 Map. 通過例項工廠方法的方式 -->
 <bean id="filterChainDefinitionMap" 
    factory-bean="filterChainDefinitionMapBuilder" factory-method="buildFilterChainDefinitionMap"></bean>
    
 <bean id="filterChainDefinitionMapBuilder"
    class="com.yintong.shiro.factory.FilterChainDefinitionMapBuilder"></bean>
建立相應的實體類,建立一個方法用來返回我們需要的LinkedHashMap實體。在方法中通過獲取資料庫,拿到對應使用者的角色/許可權。
package com.yintong.shiro.factory;

import java.util.LinkedHashMap;

public class FilterChainDefinitionMapBuilder {

	public LinkedHashMap<String, String> buildFilterChainDefinitionMap(){
		LinkedHashMap<String, String> map = new LinkedHashMap<>();
		
		map.put("/login.jsp", "anon");
		map.put("/shiro/login", "anon");
		map.put("/shiro/logout", "logout");
		map.put("/user.jsp", "authc,roles[user]");
		map.put("/admin.jsp", "authc,roles[admin]");
		map.put("/list.jsp", "user");
		
		map.put("/**", "authc");
		
		return map;
	}
	
}

20、會話管理:Shiro 提供了完整的企業級會話管理功能,不依賴於底層容器(如web容器tomcat),不管 JavaSE 還是 JavaEE 環境都可以使用,提供了會話管理、會話事件監聽、會話儲存/持久化、容器無關的叢集、失效/過期支援、對Web 的透明支援、SSO 單點登入的支援等特性。
  • Subject.getSession():即可獲取會話;
  • session.getId():獲取當前會話的唯一標識
  • session.getHost():獲取當前Subject的主機地址
  • session.getTimeout() & session.setTimeout(毫秒):獲取/設定當前Session的過期時間
  • session.getStartTimestamp() & session.getLastAccessTime():獲取會話的啟動時間及最後訪問時間;如果是 JavaSE 應用需要自己定期呼叫 session.touch() 去更新最後訪問時間;如果是 Web 應用,每次進入 ShiroFilter 都會自動呼叫 session.touch() 來更新最後訪問時間。
  • session.touch() & session.stop():更新會話最後訪問時間及銷燬會話;當Subject.logout()時會自動呼叫 stop 方法來銷燬會話。如果在web中,呼叫 HttpSession. invalidate()也會自動呼叫Shiro Session.stop 方法進行銷燬Shiro 的會話
  • session.setAttribute(key, val) &session.getAttribute(key) &session.removeAttribute(key):設定/獲取/刪除會話屬性;在整個會話範圍內都可以對這些屬性進行操作

主要特點:web層的session通常在service層不能夠獲取,但是shiro的session能夠在service層獲取到。

SessionDao的種類:

  • AbstractSessionDAO 提供了 SessionDAO 的基礎實現,如生成會話ID等
  • CachingSessionDAO 提供了對開發者透明的會話快取的功能,需要設定相應的 CacheManager
  • MemorySessionDAO 直接在記憶體中進行會話維護
  • EnterpriseCacheSessionDAO 提供了快取功能的會話維護,預設情況下使用 MapCache 實現,內部使用ConcurrentHashMap 儲存快取的會話

會話驗證:

  • Shiro 提供了會話驗證排程器,用於定期的驗證會話是否已過期,如果過期將停止會話
  • 出於效能考慮,一般情況下都是獲取會話時來驗證會話是否過期並停止會話的;但是如在 web 環境中,如果使用者不主動退出是不知道會話是否過期的,因此需要定期的檢測會話是否過期,Shiro 提供了會話驗證排程器SessionValidationScheduler
  • Shiro 也提供了使用Quartz會話驗證排程器:QuartzSessionValidationScheduler

21、快取:CacheManagerAware 介面

  • Shiro 內部相應的元件(DefaultSecurityManager)會自動檢測相應的物件(如Realm)是否實現了CacheManagerAware 並自動注入相應的CacheManager。
  • Shiro 提供了 CachingRealm,其實現了CacheManagerAware 介面,提供了快取的一些基礎實現;
  • AuthenticatingRealm 及 AuthorizingRealm 也分別提供了對AuthenticationInfo 和 AuthorizationInfo 資訊的快取。

        Session快取:

  • 如 SecurityManager 實現了 SessionSecurityManager,其會判斷 SessionManager 是否實現了CacheManagerAware 介面,如果實現了會把CacheManager 設定給它。
  • SessionManager 也會判斷相應的 SessionDAO(如繼承自CachingSessionDAO)是否實現了CacheManagerAware,如果實現了會把 CacheManager設定給它
  • 設定了快取的 SessionManager,查詢時會先查快取,如果找不到才查資料庫

22、RememberMe:Shiro 提供了記住我(RememberMe)的功能,基本流程如下:

  • 首先在登入頁面選中 RememberMe 然後登入成功;如果是瀏覽器登入,一般會把 RememberMe 的Cookie 寫到客戶端並儲存下來;
  • 關閉瀏覽器再重新開啟;會發現瀏覽器還是記住你的;
  • 訪問一般的網頁伺服器端還是知道你是誰,且能正常訪問;
  • 但是比如我們訪問淘寶時,如果要檢視我的訂單或進行支付時,此時還是需要再進行身份認證的,以確保當前使用者還是你。

重點:認證和記住我的區別:

  • subject.isAuthenticated() 表示使用者進行了身份驗證登入的,即使有 Subject.login 進行了登入;
  • subject.isRemembered():表示使用者是通過記住我登入的,此時可能並不是真正的你(如你的朋友使用你的電腦,或者你的cookie 被竊取)在訪問的。
  • 兩者二選一,即 subject.isAuthenticated()==true,則subject.isRemembered()==false;反之一樣。

建議:

  1. 訪問一般網頁:如個人在主頁之類的,我們使用user 攔截器即可,user 攔截器只要使用者登入(isRemembered() || isAuthenticated())過即可訪問成功;
  2. 訪問特殊網頁:如我的訂單,提交訂單頁面,我們使用authc 攔截器即可,authc 攔截器會判斷使用者是否是通過Subject.login(isAuthenticated()==true)登入的,如果是才放行,否則會跳轉到登入頁面叫你重新登入。

實現:如果要自己做RememeberMe,需要在登入之前這樣建立Token:UsernamePasswordToken(使用者名稱,密碼,是否記住我),且呼叫UsernamePasswordToken 的:token.setRememberMe(true); 方法。

預設攔截器user表示記住我:

map.put("/list.jsp", "user");
表示list.jsp可以通過記住我登入後訪問。

23、設定記住我的有效時間:

   <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <property name="rememberMeManager.cookie.maxAge" value="10"></property>
    </bean>