SpringMVC整合Shiro,Shiro是一個強大易用的Java安全框架,提供了認證、受權、加密和會話管理等功能。前端
Authentication:身份認證/登陸,驗證用戶是否是擁有相應的身份;java
Authorization:受權,即權限驗證,驗證某個已認證的用戶是否擁有某個權限;即判斷用戶是否能作事情,常見的如:驗證某個用戶是否擁有某個角色。或者細粒度的驗證某個用戶對某個資源是否具備某個權限;git
Session Manager:會話管理,即用戶登陸後就是一次會話,在沒有退出以前,它的全部信息都在會話中;會話能夠是普通JavaSE環境的,也能夠是如Web環境的;github
Cryptography:加密,保護數據的安全性,如密碼加密存儲到數據庫,而不是明文存儲;web
Web Support:Web支持,能夠很是容易的集成到Web環境;spring
Caching:緩存,好比用戶登陸後,其用戶信息、擁有的角色/權限沒必要每次去查,這樣能夠提升效率;數據庫
Concurrency:shiro支持多線程應用的併發驗證,即如在一個線程中開啓另外一個線程,能把權限自動傳播過去;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服務器);
SessionDAO:DAO你們都用過,數據訪問對象,用於會話的CRUD,好比咱們想把Session保存到數據庫,那麼能夠實現本身的SessionDAO,經過如JDBC寫到數據庫;好比想把Session放到Memcached中,能夠實現本身的Memcached SessionDAO;另外SessionDAO中可使用Cache進行緩存,以提升性能;
CacheManager:緩存控制器,來管理如用戶、角色、權限等的緩存的;由於這些數據基本上不多去改變,放到緩存中後能夠提升訪問的性能
Cryptography:密碼模塊,Shiro提升了一些常見的加密組件用於如密碼加密/解密的。
public class ShiroRealm extends AuthorizingRealm{
}
1、ShiroRealm父類AuthorizingRealm將獲取Subject相關信息分紅兩步:獲取身份驗證信息(doGetAuthenticationInfo)及受權信息(doGetAuthorizationInfo);
2、doGetAuthenticationInfo獲取身份驗證相關信息:首先根據傳入的用戶名獲取User信息;而後若是user爲空,那麼拋出沒找到賬號異常UnknownAccountException;若是user找到但鎖定了拋出鎖定異常LockedAccountException;最後生成AuthenticationInfo信息,交給間接父類AuthenticatingRealm使用CredentialsMatcher進行判斷密碼是否匹配,若是不匹配將拋出密碼錯誤異常IncorrectCredentialsException;另外若是密碼重試此處太多將拋出超出重試次數異常ExcessiveAttemptsException;在組裝SimpleAuthenticationInfo信息時,須要傳入:身份信息(用戶名)、憑據(密文密碼)、鹽(username+salt),CredentialsMatcher使用鹽加密傳入的明文密碼和此處的密文密碼進行匹配。
3、doGetAuthorizationInfo獲取受權信息:PrincipalCollection是一個身份集合,由於咱們如今就一個Realm,因此直接調用getPrimaryPrincipal獲得以前傳入的用戶名便可;而後根據用戶名調用UserService接口獲取角色及權限信息。
AuthenticationToken用於收集用戶提交的身份(如用戶名)及憑據(如密碼):
擴展接口RememberMeAuthenticationToken:提供了「boolean isRememberMe()」現「記住我」的功能;
擴展接口是HostAuthenticationToken:提供了「String getHost()」方法用於獲取用戶「主機」的功能。
Shiro提供了一個直接拿來用的UsernamePasswordToken,用於實現用戶名/密碼Token組,另外其實現了RememberMeAuthenticationToken和HostAuthenticationToken,能夠實現記住我及主機驗證的支持。
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便可。
由於咱們能夠在Shiro中同時配置多個Realm,因此呢身份信息可能就有多個;所以其提供了PrincipalCollection用於聚合這些身份信息:
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用於聚合受權信息的:
當咱們使用AuthorizingRealm時,若是身份驗證成功,在進行受權時就經過doGetAuthorizationInfo方法獲取角色/權限信息用於受權驗證。
Shiro提供了一個實現SimpleAuthorizationInfo,大多數時候使用這個便可。
對於Account及SimpleAccount,以前的【6.3 AuthenticationInfo】已經介紹過了,用於SimpleAccountRealm子類,實現動態角色/權限維護的。
Subject是Shiro的核心對象,基本全部身份驗證、受權都是經過Subject完成。
1、身份信息獲取
Java代碼
2、身份驗證
Java代碼
經過login登陸,若是登陸失敗將拋出相應的AuthenticationException,若是登陸成功調用isAuthenticated就會返回true,即已經經過身份驗證;若是isRemembered返回true,表示是經過記住我功能登陸的而不是調用login方法登陸的。isAuthenticated/isRemembered是互斥的,即若是其中一個返回true,另外一個返回false。
3、角色受權驗證
Java代碼
hasRole*進行角色驗證,驗證後返回true/false;而checkRole*驗證失敗時拋出AuthorizationException異常。
4、權限受權驗證
Java代碼
10. void checkPermissions(Collection<Permission> permissions) throws AuthorizationException;
isPermitted*進行權限驗證,驗證後返回true/false;而checkPermission*驗證失敗時拋出AuthorizationException。
5、會話
Java代碼
相似於Web中的會話。若是登陸成功就至關於創建了會話,接着可使用getSession獲取;若是create=false若是沒有會話將返回null,而create=true若是沒有會話會強制建立一個。
6、退出
Java代碼
7、RunAs
Java代碼
RunAs即實現「容許A假設爲B身份進行訪問」;經過調用subject.runAs(b)進行訪問;接着調用subject.getPrincipals將獲取到B的身份;此時調用isRunAs將返回true;而a的身份須要經過subject. getPreviousPrincipals獲取;若是不須要RunAs了調用subject. releaseRunAs便可。
8、多線程
Java代碼
實現線程之間的Subject傳播,由於Subject是線程綁定的;所以在多線程執行中須要傳播到相應的線程才能獲取到相應的Subject。最簡單的辦法就是經過execute(runnable/callable實例)直接調用;或者經過associateWith(runnable/callable實例)獲得一個包裝後的實例;它們都是經過:一、把當前線程的Subject綁定過去;二、在線程執行結束後自動釋放。
Subject本身不會實現相應的身份驗證/受權邏輯,而是經過DelegatingSubject委託給SecurityManager實現;及能夠理解爲Subject是一個面門。
對於Subject的構建通常不必咱們去建立;通常經過SecurityUtils.getSubject()獲取:
Java代碼
即首先查看當前線程是否綁定了Subject,若是沒有經過Subject.Builder構建一個而後綁定到現場返回。
若是想自定義建立,能夠經過:
Java代碼
這種能夠建立相應的Subject實例了,而後本身綁定到線程便可。在new Builder()時若是沒有傳入SecurityManager,自動調用SecurityUtils.getSecurityManager獲取;也能夠本身傳入一個實例。
Shiro提供了JSTL標籤用於在JSP/GSP頁面進行權限控制,如根據登陸用戶顯示相應的頁面按鈕。
導入標籤庫
Java代碼
標籤庫定義在shiro-web.jar包下的META-INF/shiro.tld中定義。
guest標籤
Java代碼
用戶沒有身份驗證時顯示相應信息,即遊客訪問信息。
user標籤
Java代碼
用戶已經身份驗證/記住我登陸後顯示相應的信息。
authenticated標籤
Java代碼
用戶已經身份驗證經過,即Subject.login登陸成功,不是記住我登陸的。
notAuthenticated標籤
<shiro:notAuthenticated>
未身份驗證(包括記住我)
</shiro:notAuthenticated>
用戶已經身份驗證經過,即沒有調用Subject.login進行登陸,包括記住我自動登陸的也屬於未進行身份驗證。
principal標籤
<shiro: principal/>
顯示用戶身份信息,默認調用Subject.getPrincipal()獲取,即Primary Principal。
Java代碼
至關於Subject.getPrincipals().oneByType(String.class)。
Java代碼
至關於Subject.getPrincipals().oneByType(String.class)。
Java代碼
至關於((User)Subject.getPrincipals()).getUsername()。
hasRole標籤
Java代碼
若是當前Subject有角色將顯示body體內容。
hasAnyRoles標籤
Java代碼
若是當前Subject有任意一個角色(或的關係)將顯示body體內容。
lacksRole標籤
Java代碼
若是當前Subject沒有角色將顯示body體內容。
hasPermission標籤
Java代碼
若是當前Subject有權限將顯示body體內容。
lacksPermission標籤
Java代碼
若是當前Subject沒有權限將顯示body體內容。
另外又提供了幾個權限控制相關的標籤:
與spring集成:在Web.xml中
12. </filter-mapping>
DelegatingFilterProxy做用是自動到spring容器查找名字爲shiroFilter(filter-name)的bean並把全部Filter的操做委託給它。而後將ShiroFilter配置到spring容器便可:
13. </bean>
15. <!-- Realm實現 -->
16. <bean id="userRealm" class="com.github.zhangkaitao.shiro.chapter12.realm.UserRealm">
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">
33. </bean>
34. <!-- 會話驗證調度器 -->
35. <bean id="sessionValidationScheduler"
36. class="org.apache.shiro.session.mgt.quartz.QuartzSessionValidationScheduler">
39. </bean>
40. <!-- 會話管理器 -->
41. <bean id="sessionManager" class="org.apache.shiro.session.mgt.DefaultSessionManager">
47. </bean>
48. <!-- 安全管理器 -->
49. <bean id="securityManager" class="org.apache.shiro.mgt.DefaultSecurityManager">
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"/>
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應用和普通JavaSE應用的某些配置是相似的,此處只提供一些不同的配置,詳細配置能夠參考spring-shiro-web.xml。
17. </bean>
18. <!-- 安全管理器 -->
19. <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
20. <property name="realm" ref="userRealm"/>
23. </bean>
一、sessionIdCookie是用於生產Session ID Cookie的模板;
二、會話管理器使用用於web環境的DefaultWebSessionManager;
三、安全管理器使用用於web環境的DefaultWebSecurityManager。
27. </bean>
一、formAuthenticationFilter爲基於Form表單的身份驗證過濾器;此處能夠再添加本身的Filter bean定義;
二、shiroFilter:此處使用ShiroFilterFactoryBean來建立ShiroFilter過濾器;filters屬性用於定義本身的過濾器,即ini配置中的[filters]部分;filterChainDefinitions用於聲明url和filter的關係,即ini配置中的[urls]部分。
在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權限註解的支持:
如上配置用於開啓Shiro Spring AOP權限註解的支持;<aop:config proxy-target-class="true">表示代理類。
接着就能夠在相應的控制器(AnnotationController)中使用以下方式進行註解:
訪問hello2方法的前提是當前用戶有admin角色。
當驗證失敗,其會拋出UnauthorizedException異常,此時可使用Spring的ExceptionHandler(DefaultExceptionHandler)來進行攔截處理:
權限註解
Java代碼
表示當前Subject已經經過login進行了身份驗證;即Subject. isAuthenticated()返回true。
Java代碼
表示當前Subject已經身份驗證或者經過記住我登陸的。
Java代碼
表示當前Subject沒有身份驗證或經過記住我登陸過,便是遊客身份。
Java代碼
表示當前Subject須要角色admin和user。
Java代碼
表示當前Subject須要權限user:a或user:b。
第一步:配置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);
}
}
}
}