shiro框架

Shiro

Shiro簡介

SpringMVC整合Shiro,Shiro是一個強大易用的Java安全框架,提供了認證、受權、加密和會話管理等功能。前端

 

 

Authentication身份認證/登陸,驗證用戶是否是擁有相應的身份;java

Authorization受權,即權限驗證,驗證某個已認證的用戶是否擁有某個權限;即判斷用戶是否能作事情,常見的如:驗證某個用戶是否擁有某個角色。或者細粒度的驗證某個用戶對某個資源是否具備某個權限;git

Session Manager會話管理,即用戶登陸後就是一次會話,在沒有退出以前,它的全部信息都在會話中;會話能夠是普通JavaSE環境的,也能夠是如Web環境的;github

Cryptography加密,保護數據的安全性,如密碼加密存儲到數據庫,而不是明文存儲;web

Web SupportWeb支持,能夠很是容易的集成到Web環境;spring

Caching:緩存,好比用戶登陸後,其用戶信息、擁有的角色/權限沒必要每次去查,這樣能夠提升效率;數據庫

Concurrencyshiro支持多線程應用的併發驗證,即如在一個線程中開啓另外一個線程,能把權限自動傳播過去;apache

Testing提供測試支持;瀏覽器

Run As容許一個用戶僞裝爲另外一個用戶(若是他們容許)的身份進行訪問;spring-mvc

Remember Me記住我,這個是很是常見的功能,即一次登陸後,下次再來的話不用登陸了。

記住一點,Shiro不會去維護用戶、維護權限;這些須要咱們本身去設計/提供;而後經過相應的接口注入給Shiro便可。

 

首先,咱們從外部來看Shiro吧,即從應用程序角度的來觀察如何使用Shiro完成工做。以下圖:

 

能夠看到:應用代碼直接交互的對象是Subject,也就是說Shiro的對外API核心就是Subject;其每一個API的含義:

Subject主體,表明了當前「用戶」,這個用戶不必定是一個具體的人,與當前應用交互的任何東西都是Subject,如網絡爬蟲,機器人等;即一個抽象概念;全部Subject都綁定到SecurityManager,與Subject的全部交互都會委託給SecurityManager;能夠把Subject認爲是一個門面;SecurityManager纔是實際的執行者;

SecurityManager安全管理器;即全部與安全有關的操做都會與SecurityManager交互;且它管理着全部Subject;能夠看出它是Shiro的核心,它負責與後邊介紹的其餘組件進行交互,若是學習過SpringMVC,你能夠把它當作DispatcherServlet前端控制器;

Realm域,Shiro從從Realm獲取安全數據(如用戶、角色、權限),就是說SecurityManager要驗證用戶身份,那麼它須要從Realm獲取相應的用戶進行比較以肯定用戶身份是否合法;也須要從Realm獲得用戶相應的角色/權限進行驗證用戶是否能進行操做;能夠把Realm當作DataSource,即安全數據源。

 

       接下來咱們來從Shiro內部來看下Shiro的架構,以下圖所示:

 

 

Subject主體,能夠看到主體能夠是任何能夠與應用交互的「用戶」;

SecurityManager至關於SpringMVC中的DispatcherServlet或者Struts2中的FilterDispatcher;是Shiro的心臟;全部具體的交互都經過SecurityManager進行控制;它管理着全部Subject、且負責進行認證和受權、及會話、緩存的管理。

Authenticator認證器,負責主體認證的,這是一個擴展點,若是用戶以爲Shiro默認的很差,能夠自定義實現;其須要認證策略(Authentication Strategy),即什麼狀況下算用戶認證經過了;

Authrizer受權器,或者訪問控制器,用來決定主體是否有權限進行相應的操做;即控制着用戶能訪問應用中的哪些功能;

Realm能夠有1個或多個Realm,能夠認爲是安全實體數據源,即用於獲取安全實體的;能夠是JDBC實現,也能夠是LDAP實現,或者內存實現等等;由用戶提供;注意:Shiro不知道你的用戶/權限存儲在哪及以何種格式存儲;因此咱們通常在應用中都須要實現本身的Realm;

SessionManager若是寫過Servlet就應該知道Session的概念,Session呢須要有人去管理它的生命週期,這個組件就是SessionManager;而Shiro並不只僅能夠用在Web環境,也能夠用在如普通的JavaSE環境、EJB等環境;全部呢,Shiro就抽象了一個本身的Session來管理主體與應用之間交互的數據;這樣的話,好比咱們在Web環境用,剛開始是一臺Web服務器;接着又上了臺EJB服務器;這時想把兩臺服務器的會話數據放到一個地方,這個時候就能夠實現本身的分佈式會話(如把數據放到Memcached服務器);

SessionDAODAO你們都用過,數據訪問對象,用於會話的CRUD,好比咱們想把Session保存到數據庫,那麼能夠實現本身的SessionDAO,經過如JDBC寫到數據庫;好比想把Session放到Memcached中,能夠實現本身的Memcached SessionDAO;另外SessionDAO中可使用Cache進行緩存,以提升性能;

CacheManager緩存控制器,來管理如用戶、角色、權限等的緩存的;由於這些數據基本上不多去改變,放到緩存中後能夠提升訪問的性能

Cryptography密碼模塊,Shiro提升了一些常見的加密組件用於如密碼加密/解密的。

自定義Realm

public class ShiroRealm extends AuthorizingRealm{

 

}

1ShiroRealm父類AuthorizingRealm將獲取Subject相關信息分紅兩步:獲取身份驗證信息(doGetAuthenticationInfo)及受權信息(doGetAuthorizationInfo);

2doGetAuthenticationInfo獲取身份驗證相關信息:首先根據傳入的用戶名獲取User信息;而後若是user爲空,那麼拋出沒找到賬號異常UnknownAccountException;若是user找到但鎖定了拋出鎖定異常LockedAccountException;最後生成AuthenticationInfo信息,交給間接父類AuthenticatingRealm使用CredentialsMatcher進行判斷密碼是否匹配,若是不匹配將拋出密碼錯誤異常IncorrectCredentialsException;另外若是密碼重試此處太多將拋出超出重試次數異常ExcessiveAttemptsException;在組裝SimpleAuthenticationInfo信息時,須要傳入:身份信息(用戶名)、憑據(密文密碼)、鹽(username+salt),CredentialsMatcher使用鹽加密傳入的明文密碼和此處的密文密碼進行匹配。

3doGetAuthorizationInfo獲取受權信息:PrincipalCollection是一個身份集合,由於咱們如今就一個Realm,因此直接調用getPrimaryPrincipal獲得以前傳入的用戶名便可;而後根據用戶名調用UserService接口獲取角色及權限信息。

AuthenticationToken

 

 

AuthenticationToken用於收集用戶提交的身份(如用戶名)及憑據(如密碼):

  1. public interface AuthenticationToken extends Serializable {  
  2.     Object getPrincipal(); //身份  
  3.     Object getCredentials(); //憑據  
  4. }  

擴展接口RememberMeAuthenticationToken:提供了「boolean isRememberMe()」現「記住我」的功能;

擴展接口是HostAuthenticationToken:提供了「String getHost()」方法用於獲取用戶「主機」的功能。

 

Shiro提供了一個直接拿來用的UsernamePasswordToken,用於實現用戶名/密碼Token組,另外其實現了RememberMeAuthenticationToken和HostAuthenticationToken,能夠實現記住我及主機驗證的支持。

AuthenticationInfo

 

 

AuthenticationInfo有兩個做用:

一、若是Realm是AuthenticatingRealm子類,則提供給AuthenticatingRealm內部使用的CredentialsMatcher進行憑據驗證;(若是沒有繼承它須要在本身的Realm中本身實現驗證);

二、提供給SecurityManager來建立Subject(提供身份信息);

 

MergableAuthenticationInfo用於提供在多Realm時合併AuthenticationInfo的功能,主要合併Principal、若是是其餘的如credentialsSalt,會用後邊的信息覆蓋前邊的。

 

好比HashedCredentialsMatcher,在驗證時會判斷AuthenticationInfo是不是SaltedAuthenticationInfo子類,來獲取鹽信息。

 

Account至關於咱們以前的User,SimpleAccount是其一個實現;在IniRealm、PropertiesRealm這種靜態建立賬號信息的場景中使用,這些Realm直接繼承了SimpleAccountRealm,而SimpleAccountRealm提供了相關的API來動態維護SimpleAccount;便可以經過這些API來動態增刪改查SimpleAccount;動態增刪改查角色/權限信息。及若是您的賬號不是特別多,可使用這種方式,具體請參考SimpleAccountRealm Javadoc。

 

其餘狀況通常返回SimpleAuthenticationInfo便可。

PrincipalCollection

 

 

由於咱們能夠在Shiro中同時配置多個Realm,因此呢身份信息可能就有多個;所以其提供了PrincipalCollection用於聚合這些身份信息:

  1. public interface PrincipalCollection extends Iterable, Serializable {  
  2.     Object getPrimaryPrincipal(); //獲得主要的身份  
  3.     <T> T oneByType(Class<T> type); //根據身份類型獲取第一個  
  4.     <T> Collection<T> byType(Class<T> type); //根據身份類型獲取一組  
  5.     List asList(); //轉換爲List  
  6.     Set asSet(); //轉換爲Set  
  7.     Collection fromRealm(String realmName); //根據Realm名字獲取  
  8.     Set<String> getRealmNames(); //獲取全部身份驗證經過的Realm名字  
  9.     boolean isEmpty(); //判斷是否爲空  

10. }  

由於PrincipalCollection聚合了多個,此處最須要注意的是getPrimaryPrincipal,若是隻有一個Principal那麼直接返回便可,若是有多個Principal,則返回第一個(由於內部使用Map存儲,因此能夠認爲是返回任意一個);oneByType / byType根據憑據的類型返回相應的Principal;fromRealm根據Realm名字(每一個Principal都與一個Realm關聯)獲取相應的Principal。

目前Shiro只提供了一個實現SimplePrincipalCollection,還記得以前的AuthenticationStrategy實現嘛,用於在多Realm時判斷是否知足條件的,在大多數實現中(繼承了AbstractAuthenticationStrategy)afterAttempt方法會進行AuthenticationInfo(實現了MergableAuthenticationInfo)的merge,好比SimpleAuthenticationInfo會合並多個Principal爲一個PrincipalCollection。

AuthorizationInfo

 

 

AuthorizationInfo用於聚合受權信息的:

  1. public interface AuthorizationInfo extends Serializable {  
  2.     Collection<String> getRoles(); //獲取角色字符串信息  
  3.     Collection<String> getStringPermissions(); //獲取權限字符串信息  
  4.     Collection<Permission> getObjectPermissions(); //獲取Permission對象信息  
  5. }   

 

當咱們使用AuthorizingRealm時,若是身份驗證成功,在進行受權時就經過doGetAuthorizationInfo方法獲取角色/權限信息用於受權驗證。

 

Shiro提供了一個實現SimpleAuthorizationInfo,大多數時候使用這個便可。

 

對於Account及SimpleAccount,以前的【6.3 AuthenticationInfo】已經介紹過了,用於SimpleAccountRealm子類,實現動態角色/權限維護的。

Subject

 

 

Subject是Shiro的核心對象,基本全部身份驗證、受權都是經過Subject完成。

1、身份信息獲取

Java代碼  

  1. Object getPrincipal(); //Primary Principal  
  2. PrincipalCollection getPrincipals(); // PrincipalCollection   

 

2、身份驗證

Java代碼  

  1. void login(AuthenticationToken token) throws AuthenticationException;  
  2. boolean isAuthenticated();  
  3. boolean isRemembered();  

經過login登陸,若是登陸失敗將拋出相應的AuthenticationException,若是登陸成功調用isAuthenticated就會返回true,即已經經過身份驗證;若是isRemembered返回true,表示是經過記住我功能登陸的而不是調用login方法登陸的。isAuthenticated/isRemembered是互斥的,即若是其中一個返回true,另外一個返回false。

  

3、角色受權驗證 

Java代碼  

  1. boolean hasRole(String roleIdentifier);  
  2. boolean[] hasRoles(List<String> roleIdentifiers);  
  3. boolean hasAllRoles(Collection<String> roleIdentifiers);  
  4. void checkRole(String roleIdentifier) throws AuthorizationException;  
  5. void checkRoles(Collection<String> roleIdentifiers) throws AuthorizationException;  
  6. void checkRoles(String... roleIdentifiers) throws AuthorizationException;   

hasRole*進行角色驗證,驗證後返回true/false;而checkRole*驗證失敗時拋出AuthorizationException異常。 

 

4、權限受權驗證

Java代碼  

  1. boolean isPermitted(String permission);  
  2. boolean isPermitted(Permission permission);  
  3. boolean[] isPermitted(String... permissions);  
  4. boolean[] isPermitted(List<Permission> permissions);  
  5. boolean isPermittedAll(String... permissions);  
  6. boolean isPermittedAll(Collection<Permission> permissions);  
  7. void checkPermission(String permission) throws AuthorizationException;  
  8. void checkPermission(Permission permission) throws AuthorizationException;  
  9. void checkPermissions(String... permissions) throws AuthorizationException;  

10. void checkPermissions(Collection<Permission> permissions) throws AuthorizationException;  

isPermitted*進行權限驗證,驗證後返回true/false;而checkPermission*驗證失敗時拋出AuthorizationException。

 

5、會話

Java代碼  

  1. Session getSession(); //至關於getSession(true)  
  2. Session getSession(boolean create);    

相似於Web中的會話。若是登陸成功就至關於創建了會話,接着可使用getSession獲取;若是create=false若是沒有會話將返回null,而create=true若是沒有會話會強制建立一個。

 

6、退出 

Java代碼  

  1. void logout();  

 

7RunAs  

Java代碼  

  1. void runAs(PrincipalCollection principals) throws NullPointerException, IllegalStateException;  
  2. boolean isRunAs();  
  3. PrincipalCollection getPreviousPrincipals();  
  4. PrincipalCollection releaseRunAs();   

RunAs即實現「容許A假設爲B身份進行訪問」;經過調用subject.runAs(b)進行訪問;接着調用subject.getPrincipals將獲取到B的身份;此時調用isRunAs將返回true;而a的身份須要經過subject. getPreviousPrincipals獲取;若是不須要RunAs了調用subject. releaseRunAs便可。

 

8、多線程

Java代碼  

  1. <V> V execute(Callable<V> callable) throws ExecutionException;  
  2. void execute(Runnable runnable);  
  3. <V> Callable<V> associateWith(Callable<V> callable);  
  4. Runnable associateWith(Runnable runnable);   

實現線程之間的Subject傳播,由於Subject是線程綁定的;所以在多線程執行中須要傳播到相應的線程才能獲取到相應的Subject。最簡單的辦法就是經過execute(runnable/callable實例)直接調用;或者經過associateWith(runnable/callable實例)獲得一個包裝後的實例;它們都是經過:一、把當前線程的Subject綁定過去;二、在線程執行結束後自動釋放。

 

Subject本身不會實現相應的身份驗證/受權邏輯,而是經過DelegatingSubject委託給SecurityManager實現;及能夠理解爲Subject是一個面門。

 

對於Subject的構建通常不必咱們去建立;通常經過SecurityUtils.getSubject()獲取:

Java代碼  

  1. public static Subject getSubject() {  
  2.     Subject subject = ThreadContext.getSubject();  
  3.     if (subject == null) {  
  4.         subject = (new Subject.Builder()).buildSubject();  
  5.         ThreadContext.bind(subject);  
  6.     }  
  7.     return subject;  
  8. }   

即首先查看當前線程是否綁定了Subject,若是沒有經過Subject.Builder構建一個而後綁定到現場返回。

 

若是想自定義建立,能夠經過:

Java代碼  

  1. new Subject.Builder().principals(身份).authenticated(true/false).buildSubject()  

這種能夠建立相應的Subject實例了,而後本身綁定到線程便可。在new Builder()時若是沒有傳入SecurityManager,自動調用SecurityUtils.getSecurityManager獲取;也能夠本身傳入一個實例。

Shiro的jstl標籤

Shiro提供了JSTL標籤用於在JSP/GSP頁面進行權限控制,如根據登陸用戶顯示相應的頁面按鈕。

 

導入標籤庫

Java代碼  

  1. <%@taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>  

標籤庫定義在shiro-web.jar包下的META-INF/shiro.tld中定義。

 

guest標籤 

Java代碼  

  1. <shiro:guest>  
  2. 歡迎遊客訪問,<a href="${pageContext.request.contextPath}/login.jsp">登陸</a>  
  3. </shiro:guest>   

用戶沒有身份驗證時顯示相應信息,即遊客訪問信息。

 

user標籤 

Java代碼  

  1. <shiro:user>  
  2. 歡迎[<shiro:principal/>]登陸,<a href="${pageContext.request.contextPath}/logout">退出</a>  
  3. </shiro:user>   

用戶已經身份驗證/記住我登陸後顯示相應的信息。

  

authenticated標籤 

Java代碼  

  1. <shiro:authenticated>  
  2.     用戶[<shiro:principal/>]已身份驗證經過  
  3. </shiro:authenticated>   

用戶已經身份驗證經過,即Subject.login登陸成功,不是記住我登陸的。    

 

notAuthenticated標籤

<shiro:notAuthenticated>

    未身份驗證(包括記住我)

</shiro:notAuthenticated> 

用戶已經身份驗證經過,即沒有調用Subject.login進行登陸,包括記住我自動登陸的也屬於未進行身份驗證。 

 

principal標籤 

<shiro: principal/>

顯示用戶身份信息,默認調用Subject.getPrincipal()獲取,即Primary Principal。

 

Java代碼 

  1. <shiro:principal type="java.lang.String"/>  

至關於Subject.getPrincipals().oneByType(String.class)。 

 

Java代碼 

  1. <shiro:principal type="java.lang.String"/>  

至關於Subject.getPrincipals().oneByType(String.class)。

 

Java代碼 

  1. <shiro:principal property="username"/>  

至關於((User)Subject.getPrincipals()).getUsername()。   

 

hasRole標籤 

Java代碼 

  1. <shiro:hasRole name="admin">  
  2.     用戶[<shiro:principal/>]擁有角色admin<br/>  
  3. </shiro:hasRole>   

若是當前Subject有角色將顯示body體內容。

 

hasAnyRoles標籤 

Java代碼 

  1. <shiro:hasAnyRoles name="admin,user">  
  2.     用戶[<shiro:principal/>]擁有角色admin或user<br/>  
  3. </shiro:hasAnyRoles>   

若是當前Subject有任意一個角色(或的關係)將顯示body體內容。 

 

lacksRole標籤 

Java代碼 

  1. <shiro:lacksRole name="abc">  
  2.     用戶[<shiro:principal/>]沒有角色abc<br/>  
  3. </shiro:lacksRole>   

若是當前Subject沒有角色將顯示body體內容。 

  

hasPermission標籤

Java代碼 

  1. <shiro:hasPermission name="user:create">  
  2.     用戶[<shiro:principal/>]擁有權限user:create<br/>  
  3. </shiro:hasPermission>   

若是當前Subject有權限將顯示body體內容。 

  

lacksPermission標籤

Java代碼 

  1. <shiro:lacksPermission name="org:create">  
  2.     用戶[<shiro:principal/>]沒有權限org:create<br/>  
  3. </shiro:lacksPermission>   

若是當前Subject沒有權限將顯示body體內容。

 

另外又提供了幾個權限控制相關的標籤:

Shiro與web

與spring集成:在Web.xml中

  1. <filter>  
  2.     <filter-name>shiroFilter</filter-name>  
  3.     <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>  
  4.     <init-param>  
  5.         <param-name>targetFilterLifecycle</param-name>  
  6.         <param-value>true</param-value>  
  7.     </init-param>  
  8. </filter>  
  9. <filter-mapping>  
  10. 10.     <filter-name>shiroFilter</filter-name>  
  11. 11.     <url-pattern>/*</url-pattern>  

12. </filter-mapping>   

DelegatingFilterProxy做用是自動到spring容器查找名字爲shiroFilter(filter-name)的bean並把全部Filter的操做委託給它。而後將ShiroFilter配置到spring容器便可:

Shiro集成spring

  1. <!-- 緩存管理器 使用Ehcache實現 -->  
  2. <bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">  
  3.     <property name="cacheManagerConfigFile" value="classpath:ehcache.xml"/>  
  4. </bean>  
  5.   
  6. <!-- 憑證匹配器 -->  
  7. <bean id="credentialsMatcher" class="  
  8. com.github.zhangkaitao.shiro.chapter12.credentials.RetryLimitHashedCredentialsMatcher">  
  9.     <constructor-arg ref="cacheManager"/>  
  10. 10.     <property name="hashAlgorithmName" value="md5"/>  
  11. 11.     <property name="hashIterations" value="2"/>  
  12. 12.     <property name="storedCredentialsHexEncoded" value="true"/>  

13. </bean>  

  1. 14.   

15. <!-- Realm實現 -->  

16. <bean id="userRealm" class="com.github.zhangkaitao.shiro.chapter12.realm.UserRealm">  

  1. 17.     <property name="userService" ref="userService"/>  
  2. 18.     <property name="credentialsMatcher" ref="credentialsMatcher"/>  
  3. 19.     <property name="cachingEnabled" value="true"/>  
  4. 20.     <property name="authenticationCachingEnabled" value="true"/>  
  5. 21.     <property name="authenticationCacheName" value="authenticationCache"/>  
  6. 22.     <property name="authorizationCachingEnabled" value="true"/>  
  7. 23.     <property name="authorizationCacheName" value="authorizationCache"/>  

24. </bean>  

25. <!-- 會話ID生成器 -->  

26. <bean id="sessionIdGenerator"   

27. class="org.apache.shiro.session.mgt.eis.JavaUuidSessionIdGenerator"/>  

28. <!-- 會話DAO -->  

29. <bean id="sessionDAO"   

30. class="org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO">  

  1. 31.     <property name="activeSessionsCacheName" value="shiro-activeSessionCache"/>  
  2. 32.     <property name="sessionIdGenerator" ref="sessionIdGenerator"/>  

33. </bean>  

34. <!-- 會話驗證調度器 -->  

35. <bean id="sessionValidationScheduler"   

36. class="org.apache.shiro.session.mgt.quartz.QuartzSessionValidationScheduler">  

  1. 37.     <property name="sessionValidationInterval" value="1800000"/>  
  2. 38.     <property name="sessionManager" ref="sessionManager"/>  

39. </bean>  

40. <!-- 會話管理器 -->  

41. <bean id="sessionManager" class="org.apache.shiro.session.mgt.DefaultSessionManager">  

  1. 42.     <property name="globalSessionTimeout" value="1800000"/>  
  2. 43.     <property name="deleteInvalidSessions" value="true"/>  
  3. 44.     <property name="sessionValidationSchedulerEnabled" value="true"/>  
  4. 45.    <property name="sessionValidationScheduler" ref="sessionValidationScheduler"/>  
  5. 46.     <property name="sessionDAO" ref="sessionDAO"/>  

47. </bean>  

48. <!-- 安全管理器 -->  

49. <bean id="securityManager" class="org.apache.shiro.mgt.DefaultSecurityManager">  

  1. 50.     <property name="realms">  
  2. 51.         <list><ref bean="userRealm"/></list>  
  3. 52.     </property>  
  4. 53.     <property name="sessionManager" ref="sessionManager"/>  
  5. 54.     <property name="cacheManager" ref="cacheManager"/>  

55. </bean>  

56. <!-- 至關於調用SecurityUtils.setSecurityManager(securityManager) -->  

57. <bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">  

58. <property name="staticMethod"   

59. value="org.apache.shiro.SecurityUtils.setSecurityManager"/>  

  1. 60.     <property name="arguments" ref="securityManager"/>  

61. </bean>  

62. <!-- Shiro生命週期處理器-->  

63. <bean id="lifecycleBeanPostProcessor"   

64. class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>  

能夠看出,只要把以前的ini配置翻譯爲此處的spring xml配置方式便可,無須多解釋。LifecycleBeanPostProcessor用於在實現了Initializable接口的Shiro bean初始化時調用Initializable接口回調,在實現了Destroyable接口的Shiro bean銷燬時調用 Destroyable接口回調。如UserRealm就實現了Initializable,而DefaultSecurityManager實現了Destroyable。具體能夠查看它們的繼承關係。

Web應用:

Web應用和普通JavaSE應用的某些配置是相似的,此處只提供一些不同的配置,詳細配置能夠參考spring-shiro-web.xml。 

  1. <!-- 會話Cookie模板 -->  
  2. <bean id="sessionIdCookie" class="org.apache.shiro.web.servlet.SimpleCookie">  
  3.     <constructor-arg value="sid"/>  
  4.     <property name="httpOnly" value="true"/>  
  5.     <property name="maxAge" value="180000"/>  
  6. </bean>  
  7. <!-- 會話管理器 -->  
  8. <bean id="sessionManager"   
  9. class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">  
  10. 10.     <property name="globalSessionTimeout" value="1800000"/>  
  11. 11.     <property name="deleteInvalidSessions" value="true"/>  
  12. 12.     <property name="sessionValidationSchedulerEnabled" value="true"/>  
  13. 13.     <property name="sessionValidationScheduler" ref="sessionValidationScheduler"/>  
  14. 14.     <property name="sessionDAO" ref="sessionDAO"/>  
  15. 15.     <property name="sessionIdCookieEnabled" value="true"/>  
  16. 16.     <property name="sessionIdCookie" ref="sessionIdCookie"/>  

17. </bean>  

18. <!-- 安全管理器 -->  

19. <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">  

20. <property name="realm" ref="userRealm"/>  

  1. 21.     <property name="sessionManager" ref="sessionManager"/>  
  2. 22.     <property name="cacheManager" ref="cacheManager"/>  

23. </bean>   


一、sessionIdCookie是用於生產Session ID Cookie的模板;

二、會話管理器使用用於web環境的DefaultWebSessionManager;

三、安全管理器使用用於web環境的DefaultWebSecurityManager。

 

  1. <!-- 基於Form表單的身份驗證過濾器 -->  
  2. <bean id="formAuthenticationFilter"   
  3. class="org.apache.shiro.web.filter.authc.FormAuthenticationFilter">  
  4.     <property name="usernameParam" value="username"/>  
  5.     <property name="passwordParam" value="password"/>  
  6.     <property name="loginUrl" value="/login.jsp"/>  
  7. </bean>  
  8. <!-- Shiro的Web過濾器 -->  
  9. <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">  
  10. 10.     <property name="securityManager" ref="securityManager"/>  
  11. 11.     <property name="loginUrl" value="/login.jsp"/>  
  12. 12.     <property name="unauthorizedUrl" value="/unauthorized.jsp"/>  
  13. 13.     <property name="filters">  
  14. 14.         <util:map>  
  15. 15.             <entry key="authc" value-ref="formAuthenticationFilter"/>  
  16. 16.         </util:map>  
  17. 17.     </property>  
  18. 18.     <property name="filterChainDefinitions">  
  19. 19.         <value>  
  20. 20.             /index.jsp = anon  
  21. 21.             /unauthorized.jsp = anon  
  22. 22.             /login.jsp = authc  
  23. 23.             /logout = logout  
  24. 24.             /** = user  
  25. 25.         </value>  
  26. 26.     </property>  

27. </bean>   

 

一、formAuthenticationFilter爲基於Form表單的身份驗證過濾器;此處能夠再添加本身的Filter bean定義;

二、shiroFilter:此處使用ShiroFilterFactoryBean來建立ShiroFilter過濾器;filters屬性用於定義本身的過濾器,即ini配置中的[filters]部分;filterChainDefinitions用於聲明url和filter的關係,即ini配置中的[urls]部分。

Shiro權限註解

注意:

在spring中須要開啓權限註解與aop:

<!-- AOP式方法級權限檢查  -->
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"
      depends-on="lifecycleBeanPostProcessor"/>

<!-- 啓用shrio受權註解攔截方式 -->
<bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
    <property name="securityManager" ref="securityManager"/>
</bean>

 

 

Shiro提供了相應的註解用於權限控制,若是使用這些註解就須要使用AOP的功能來進行判斷,如Spring AOP;Shiro提供了Spring AOP集成用於權限註解的解析和驗證。

爲了測試,此處使用了Spring MVC來測試Shiro註解,固然Shiro註解不只僅能夠在web環境使用,在獨立的JavaSE中也是能夠用的,此處只是以web爲例了。

 

在spring-mvc.xml配置文件添加Shiro Spring AOP權限註解的支持:

  1. <aop:config proxy-target-class="true"></aop:config>  
  2. <bean class="  
  3. org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">  
  4.     <property name="securityManager" ref="securityManager"/>  
  5. </bean>   

如上配置用於開啓Shiro Spring AOP權限註解的支持;<aop:config proxy-target-class="true">表示代理類。

 

接着就能夠在相應的控制器(AnnotationController)中使用以下方式進行註解: 

  1. @RequiresRoles("admin")  
  2. @RequestMapping("/hello2")  
  3. public String hello2() {  
  4.     return "success";  
  5. }  

訪問hello2方法的前提是當前用戶有admin角色。

 

當驗證失敗,其會拋出UnauthorizedException異常,此時可使用Spring的ExceptionHandler(DefaultExceptionHandler)來進行攔截處理:

  1. @ExceptionHandler({UnauthorizedException.class})  
  2. @ResponseStatus(HttpStatus.UNAUTHORIZED)  
  3. public ModelAndView processUnauthenticatedException(NativeWebRequest request, UnauthorizedException e) {  
  4.     ModelAndView mv = new ModelAndView();  
  5.     mv.addObject("exception", e);  
  6.     mv.setViewName("unauthorized");  
  7.     return mv;  
  8. }   

權限註解      

Java代碼  

  1. @RequiresAuthentication  

表示當前Subject已經經過login進行了身份驗證;即Subject. isAuthenticated()返回true。 

 

Java代碼  

  1. @RequiresUser  

表示當前Subject已經身份驗證或者經過記住我登陸的。

 

Java代碼  

  1. @RequiresGuest  

表示當前Subject沒有身份驗證或經過記住我登陸過,便是遊客身份。  

 

Java代碼  

  1. @RequiresRoles(value={「admin」, 「user」}, logical= Logical.AND)  

表示當前Subject須要角色admin和user。

 

Java代碼  

  1. @RequiresPermissions (value={「user:a」, 「user:b」}, logical= Logical.OR)  

表示當前Subject須要權限user:a或user:b。  

Shiro 完整項目配置

第一步:配置web.xml

<!-- 配置Shiro過濾器,先讓Shiro過濾系統接收到的請求 -->  

<!-- 這裏filter-name必須對應applicationContext.xml中定義的<bean id="shiroFilter"/> -->  

<!-- 使用[/*]匹配全部請求,保證全部的可控請求都通過Shiro的過濾 -->  

<!-- 一般會將此filter-mapping放置到最前面(即其餘filter-mapping前面),以保證它是過濾器鏈中第一個起做用的 -->  

<filter>  

    <filter-name>shiroFilter</filter-name>  

    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>  

    <init-param>  

    <!-- 該值缺省爲false,表示生命週期由SpringApplicationContext管理,設置爲true則表示由ServletContainer管理 -->  

    <param-name>targetFilterLifecycle</param-name>  

    <param-value>true</param-value>  

    </init-param>  

</filter>  

<filter-mapping>  

        <filter-name>shiroFilter</filter-name>  

        <url-pattern>/*</url-pattern>  

</filter-mapping>

第二步:配置applicationContext.xml

<!-- 繼承自AuthorizingRealm的自定義Realm,即指定Shiro驗證用戶登陸的類爲自定義的ShiroDbRealm.java -->  

<bean id="myRealm" class="com.jadyer.realm.MyRealm"/>  

  

<!-- Shiro默認會使用Servlet容器的Session,可經過sessionMode屬性來指定使用Shiro原生Session -->  

<!-- 即<property name="sessionMode" value="native"/>,詳細說明見官方文檔 -->  

<!-- 這裏主要是設置自定義的單Realm應用,如有多個Realm,可以使用'realms'屬性代替 -->  

<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">  

    <property name="realm" ref="myRealm"/>  

</bean>  

  

<!-- Shiro主過濾器自己功能十分強大,其強大之處就在於它支持任何基於URL路徑表達式的、自定義的過濾器的執行 -->  

<!-- Web應用中,Shiro可控制的Web請求必須通過Shiro主過濾器的攔截,Shiro對基於Spring的Web應用提供了完美的支持 -->  

<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">  

    <!-- Shiro的核心安全接口,這個屬性是必須的 -->  

    <property name="securityManager" ref="securityManager"/>  

    <!-- 要求登陸時的連接(可根據項目的URL進行替換),非必須的屬性,默認會自動尋找Web工程根目錄下的"/login.jsp"頁面 -->  

    <property name="loginUrl" value="/"/>  

    <!-- 登陸成功後要跳轉的鏈接(本例中此屬性用不到,由於登陸成功後的處理邏輯在LoginController裏硬編碼爲main.jsp了) -->  

    <!-- <property name="successUrl" value="/system/main"/> -->  

    <!-- 用戶訪問未對其受權的資源時,所顯示的鏈接 -->  

    <!-- 若想更明顯的測試此屬性能夠修改它的值,如unauthor.jsp,而後用[玄玉]登陸後訪問/admin/listUser.jsp就看見瀏覽器會顯示unauthor.jsp -->  

    <property name="unauthorizedUrl" value="/"/>  

    <!-- Shiro鏈接約束配置,即過濾鏈的定義 -->  

    <!-- 此處可配合個人這篇文章來理解各個過濾連的做用http://blog.csdn.net/jadyer/article/details/12172839 -->  

    <!-- 下面value值的第一個'/'表明的路徑是相對於HttpServletRequest.getContextPath()的值來的 -->  

    <!-- anon:它對應的過濾器裏面是空的,什麼都沒作,這裏.do和.jsp後面的*表示參數,比方說login.jsp?main這種 -->  

    <!-- authc:該過濾器下的頁面必須驗證後才能訪問,它是Shiro內置的一個攔截器org.apache.shiro.web.filter.authc.FormAuthenticationFilter -->  

    <property name="filterChainDefinitions">  

        <value>  

             /mydemo/login=anon  

             /mydemo/getVerifyCodeImage=anon  

             /main**=authc  

             /user/info**=authc  

             /admin/listUser**=authc,perms[admin:manage]  

        </value>  

    </property>

</bean>  

  

<!-- 保證明現了Shiro內部lifecycle函數的bean執行 -->  

<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>  

  

<!-- 開啓Shiro的註解(如@RequiresRoles,@RequiresPermissions),需藉助SpringAOP掃描使用Shiro註解的類,並在必要時進行安全邏輯驗證 -->  

<!-- 配置如下兩個bean便可實現此功能 -->  

<!-- Enable Shiro Annotations for Spring-configured beans. Only run after the lifecycleBeanProcessor has run -->  

<!-- 因爲本例中並未使用Shiro註解,故註釋掉這兩個bean(我的以爲將權限經過註解的方式硬編碼在程序中,查看起來不是很方便,不必使用) -->  

<!--   

<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" depends-on="lifecycleBeanPostProcessor"/>  

  <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">  

    <property name="securityManager" ref="securityManager"/>  

  </bean>  

-->

 

第三步:自定義的Realm

public class MyRealm extends AuthorizingRealm {  

    /** 

     * 爲當前登陸的Subject授予角色和權限 

     * @see  經測試:本例中該方法的調用時機爲需受權資源被訪問時 

     * @see  經測試:而且每次訪問需受權資源時都會執行該方法中的邏輯,這代表本例中默認並未啓用AuthorizationCache 

     * @see  我的感受若使用了Spring3.1開始提供的ConcurrentMapCache支持,則可靈活決定是否啓用AuthorizationCache 

     * @see  好比說這裏從數據庫獲取權限信息時,先去訪問Spring3.1提供的緩存,而不使用Shior提供的AuthorizationCache 

     */  

    @Override  

    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals){  

        //獲取當前登陸的用戶名,等價於(String)principals.fromRealm(this.getName()).iterator().next()  

        String currentUsername = (String)super.getAvailablePrincipal(principals);  

//      List<String> roleList = new ArrayList<String>();  

//      List<String> permissionList = new ArrayList<String>();  

//      //從數據庫中獲取當前登陸用戶的詳細信息  

//      User user = userService.getByUsername(currentUsername);  

//      if(null != user){  

//          //實體類User中包含有用戶角色的實體類信息  

//          if(null!=user.getRoles() && user.getRoles().size()>0){  

//              //獲取當前登陸用戶的角色  

//              for(Role role : user.getRoles()){  

//                  roleList.add(role.getName());  

//                  //實體類Role中包含有角色權限的實體類信息  

//                  if(null!=role.getPermissions() && role.getPermissions().size()>0){  

//                      //獲取權限  

//                      for(Permission pmss : role.getPermissions()){  

//                          if(!StringUtils.isEmpty(pmss.getPermission())){  

//                              permissionList.add(pmss.getPermission());  

//                          }  

//                      }  

//                  }  

//              }  

//          }  

//      }else{  

//          throw new AuthorizationException();  

//      }  

//      //爲當前用戶設置角色和權限  

//      SimpleAuthorizationInfo simpleAuthorInfo = new SimpleAuthorizationInfo();  

//      simpleAuthorInfo.addRoles(roleList);  

//      simpleAuthorInfo.addStringPermissions(permissionList);  

        SimpleAuthorizationInfo simpleAuthorInfo = new SimpleAuthorizationInfo();  

        //實際中可能會像上面註釋的那樣從數據庫取得  

        if(null!=currentUsername && "mike".equals(currentUsername)){  

            //添加一個角色,不是配置意義上的添加,而是證實該用戶擁有admin角色    

            simpleAuthorInfo.addRole("admin");  

            //添加權限  

            simpleAuthorInfo.addStringPermission("admin:manage");  

            System.out.println("已爲用戶[mike]賦予了[admin]角色和[admin:manage]權限");  

            return simpleAuthorInfo;  

        }

        //若該方法什麼都不作直接返回null的話,就會致使任何用戶訪問/admin/listUser.jsp時都會自動跳轉到unauthorizedUrl指定的地址  

        //詳見applicationContext.xml中的<bean id="shiroFilter">的配置  

        return null;  

    }  

  

      

    /** 

     * 驗證當前登陸的Subject 

     * @see  經測試:本例中該方法的調用時機爲LoginController.login()方法中執行Subject.login()時 

     */  

    @Override  

    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken) throws AuthenticationException {  

        //獲取基於用戶名和密碼的令牌  

        //實際上這個authcToken是從LoginController裏面currentUser.login(token)傳過來的  

        //兩個token的引用都是同樣的

        UsernamePasswordToken token = (UsernamePasswordToken)authcToken;  

        System.out.println("驗證當前Subject時獲取到token爲" + ReflectionToStringBuilder.toString(token, ToStringStyle.MULTI_LINE_STYLE));  

//      User user = userService.getByUsername(token.getUsername());  

//      if(null != user){  

//          AuthenticationInfo authcInfo = new SimpleAuthenticationInfo(user.getUsername(), user.getPassword(), user.getNickname());  

//          this.setSession("currentUser", user);  

//          return authcInfo;  

//      }else{  

//          return null;  

//      }  

        //此處無需比對,比對的邏輯Shiro會作,咱們只需返回一個和令牌相關的正確的驗證信息  

        //說白了就是第一個參數填登陸用戶名,第二個參數填合法的登陸密碼(能夠是從數據庫中取到的,本例中爲了演示就硬編碼了)  

        //這樣一來,在隨後的登陸頁面上就只有這裏指定的用戶和密碼才能經過驗證  

        if("mike".equals(token.getUsername())){  

            AuthenticationInfo authcInfo = new SimpleAuthenticationInfo("mike", "mike", this.getName());  

            this.setSession("currentUser", "mike");  

            return authcInfo;  

        }

        //沒有返回登陸用戶名對應的SimpleAuthenticationInfo對象時,就會在LoginController中拋出UnknownAccountException異常  

        return null;  

    }  

      

      

    /** 

     * 將一些數據放到ShiroSession中,以便於其它地方使用 

     * @see  好比Controller,使用時直接用HttpSession.getAttribute(key)就能夠取到 

     */  

    private void setSession(Object key, Object value){  

        Subject currentUser = SecurityUtils.getSubject();  

        if(null != currentUser){  

            Session session = currentUser.getSession();  

            System.out.println("Session默認超時時間爲[" + session.getTimeout() + "]毫秒");  

            if(null != session){  

                session.setAttribute(key, value);  

            }  

        }  

    }  

}

相關文章
相關標籤/搜索