Shiro学习笔记
Shiro教學視頻
源碼
文章目錄
- 1. 簡介
- 功能簡介
- Shiro 架構
- Shiro 架構(Shiro外部來看)
- Shiro 架構(Shiro內部來看)
- 2. HelloWorld
- shiro.ini配置文件解析
- Quickstart.java解析
- 運行結果
- 3. 集成Spring
- 配置web.xml
- 配置spring-servlet.xml
- 配置applicationContext.xml
- 4. Shiro工作流程
- 5. DelegatingFilterProxy
- 6. 權限URL配置細節
- URL 匹配模式
- Shiro中默認的過濾器
- URL 匹配順序
- 7. 認證思路分析
- 8. 實現認證流程
- 9. 實現認證Realm
- 10. 密碼的比對
- 11. 密碼的MD5加密
- 12. 密碼的MD5鹽值加密
- 13. 多Realm驗證
- 14. 認證策略
- 15. 把realms配置給SecurityManager
- 16. 權限配置
- Shiro 支持三種方式的授權
- 默認攔截器
- 身份驗證相關的攔截器
- 授權相關的攔截器
- 其他的攔截器
- 實操
- 17. 授權流程分析
- 18. 多Realm授權的通過標準
- 19. 實現授權Realm
- 20. 標簽
- 21. 權限注解
- 22. 從數據表中初始化資源和權限
- 23. 會話管理
- Session會話相關的 API
- 24. SessionDao
- 25. 緩存
- CacheManagerAware 接口
- Realm 緩存
- Session 緩存
- 26. 認證和記住我的區別
- 27. 實現Remember me
1. 簡介
- Apache Shiro 是 Java 的一個安全(權限)框架。
- Shiro 可以非常容易的開發出足夠好的應用,其不僅可以用在JavaSE 環境,也可以用在 JavaEE 環境。
- Shiro 可以完成:認證、授權、加密、會話管理、與Web集成、緩存等。
- Shiro官網
功能簡介
- Authentication:身份認證/登錄,驗證用戶是不是擁有相應的身份;
- Authorization:授權,即權限驗證,驗證某個已認證的用戶是否擁有某個權限;即判斷用戶是否能進行什么操作,如:驗證某個用戶是否擁有某個角色。或者細粒度的驗證某個用戶對某個資源是否具有某個權限;
- Session Manager:會話管理,即用戶登錄后就是一次會話,在沒有退出之前,它的所有信息都在會話中; 會話可以是普通JavaSE環境,也可以是 Web 環境的;
- Cryptography:加密,保護數據的安全性,如密碼加密存儲到數據庫,而不是明文存儲;
- Web Support:Web 支持,可以非常容易的集成到Web環境;
- Caching:緩存,比如用戶登錄后,其用戶信息、擁有的角色/權限不必每次去查,這樣可以提高效率;
- Concurrency: Shiro 支持多線程應用的并發驗證,即如在一個線程中開啟另一個線程,能
把權限自動傳播過去; - Testing:提供測試支持;
- Run As: 允許一個用戶假裝為另一個用戶(如果他們允許)的身份進行訪問;
- Remember Me: 記住我,這個是非常常見的功能,即一次登錄后,下次再來的話不用登錄了
authentication /???θent??ke??n/n. 身份驗證;認證;鑒定
authorization /???θ?r??ze??n/n. 批準;授權;批準書;授權書
Shiro 架構
Shiro 架構(Shiro外部來看)
從外部來看Shiro,即從應用程序角度的來觀察如何使用 Shiro 完成工作
- Subject:應用代碼直接交互的對象是Subject,也就是說 Shiro 的對外API核心就是Subject。Subject代表了當前“用戶”,這個用戶不一定是一個具體的人,與當前應用交互的任何東西都是Subject,如網絡爬蟲,機器人等; 與 Subject 的所有交互都會委托給SecurityManager;Subject 其實是一個門面, SecurityManager 才是實際的執行者;
- SecurityManager:安全管理器;即所有與安全有關的操作都會與SecurityManager 交互;且其管理著所有 Subject;可以看出它是 Shiro的核心,它負責與 Shiro 的其他組件進行交互,它相當于 SpringMVC 中DispatcherServlet 的角色
- Realm:Shiro從Realm(領域、場所) 獲取安全數據(如用戶、角色、權限),就是說SecurityManager 要驗證用戶身份,那么它需要從 Realm 獲取相應的用戶進行比較以確定用戶身份是否合法;也需要從 Realm 得到用戶相應的角色/權限進行驗證用戶是否能進行操作;可以把 Realm 看成 DataSource
Shiro 架構(Shiro內部來看)
2. HelloWorld
shiro.ini配置文件解析
shiro.ini配置文件主要內容為:
Quickstart.java解析
從Quickstart.java中,大概可看出如何使用Shiro進行認證、授權等安全操作。
public class Quickstart {public static void main(String[] args) {//創建并安全管理器,需要用到上述的shiro.ini配置文件Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");SecurityManager securityManager = factory.getInstance();SecurityUtils.setSecurityManager(securityManager);//獲取當前的Subject,這里Subject可理解為即將登陸的用戶Subject currentUser = SecurityUtils.getSubject();// 測試使用Session讀寫鍵值對,這個Session無需Web容器就可使用Session session = currentUser.getSession();session.setAttribute("someKey", "aValue");String value = (String) session.getAttribute("someKey");if (value.equals("aValue")) {log.info("---> Retrieved the correct value! [" + value + "]");}// 測試當前的用戶是否已經被認證,即是否已經登錄if (!currentUser.isAuthenticated()) {// 把用戶名和密碼封裝為 UsernamePasswordToken 對象UsernamePasswordToken token = new UsernamePasswordToken("lonestarr", "vespa");// remembermetoken.setRememberMe(true);try {//執行登錄. currentUser.login(token);} // 若沒有指定的賬戶,則拋出 UnknownAccountExceptioncatch (UnknownAccountException uae) {log.info("----> There is no user with username of " + token.getPrincipal());return; } // 若賬戶存在,但密碼不匹配,則拋出 IncorrectCredentialsException 異常catch (IncorrectCredentialsException ice) {log.info("----> Password for account " + token.getPrincipal() + " was incorrect!");return; } // 用戶被鎖定的異常 LockedAccountExceptioncatch (LockedAccountException lae) {log.info("The account for username " + token.getPrincipal() + " is locked. " +"Please contact your administrator to unlock it.");}// 所有認證時異常的父類AuthenticationException,也就是說LockedAccountException、IncorrectCredentialsException等繼承該父類catch (AuthenticationException ae) {//unexpected condition? error?}}//print their identifying principal (in this case, a username):log.info("----> User [" + currentUser.getPrincipal() + "] logged in successfully.");// 測試是否有某一個角色if (currentUser.hasRole("schwartz")) {log.info("----> May the Schwartz be with you!");} else {log.info("----> Hello, mere mortal.");return; }// 測試用戶是否具備某一個行為if (currentUser.isPermitted("lightsaber:weild")) {log.info("----> You may use a lightsaber ring. Use it wisely.");} else {log.info("Sorry, lightsaber rings are for schwartz masters only.");}//測試用戶是否具備某一個更具體的行為:if (currentUser.isPermitted("user:delete:zhangsan")) {log.info("----> You are permitted to 'drive' the winnebago with license plate (id) 'eagle5'. " +"Here are the keys - have fun!");} else {log.info("Sorry, you aren't allowed to drive the 'eagle5' winnebago!");}// 執行登出log.info("---->currentUser.isAuthenticated(): " + currentUser.isAuthenticated());currentUser.logout();log.info("---->currentUser.isAuthenticated(): " + currentUser.isAuthenticated());System.exit(0);} }principal [?pr?ns?pl]
adj. 最重要的;主要的
n. 大學校長;學院院長;本金;資本;主要演員;主角
運行結果
2020-07-11 18:43:05,687 INFO [org.apache.shiro.session.mgt.AbstractValidatingSessionManager] - Enabling session validation scheduler... 2020-07-11 18:43:06,051 INFO [com.lun.shiro.one.Quickstart] - Retrieved the correct value! [aValue] 2020-07-11 18:43:06,054 INFO [com.lun.shiro.one.Quickstart] - User [lonestarr] logged in successfully. 2020-07-11 18:43:06,055 INFO [com.lun.shiro.one.Quickstart] - May the Schwartz be with you! 2020-07-11 18:43:06,056 INFO [com.lun.shiro.one.Quickstart] - You may use a lightsaber ring. Use it wisely. 2020-07-11 18:43:06,056 INFO [com.lun.shiro.one.Quickstart] - You are permitted to 'drive' the winnebago with license plate (id) 'eagle5'. Here are the keys - have fun! 2020-07-11 18:43:06,056 INFO [com.lun.shiro.one.Quickstart] - currentUser.isAuthenticated(): true 2020-07-11 18:43:06,059 INFO [com.lun.shiro.one.Quickstart] - currentUser.isAuthenticated(): false3. 集成Spring
配置web.xml
配置web.xml主要目的:
- Shiro 提供了與 Web 集成的支持,其通過一個ShiroFilter 入口來攔截需要安全控制的URL,然后進行相應的控制
- ShiroFilter 類似于如 Strut2/SpringMVC 這種web 框架的前端控制器,是安全控制的入口點,其負責讀取配置(如ini 配置文件),然后判斷URL是否需要登錄/權限等工作。
配置spring-servlet.xml
在spring-servlet.xml配置SpringMVC信息
<beans ...><context:component-scan base-package="com.lun"></context:component-scan><bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"><property name="prefix" value="/"></property><property name="suffix" value=".jsp"></property></bean><mvc:annotation-driven></mvc:annotation-driven><mvc:default-servlet-handler/> </beans>配置applicationContext.xml
Shiro的配置信心主要在applicationContext.xml中。
4. Shiro工作流程
5. DelegatingFilterProxy
在web.xml中
<!-- 配置 Shiro 的 shiroFilterDelegatingFilterProxy 實際上是 Filter 的一個代理對象。 默認情況下, Spring 會到 IOC 容器中查找和 <filter-name> 對應的 filter bean。也可以通過 targetBeanName 的初始化參數來配置 filter bean 的 id。 --> <filter><filter-name>shiroFilter</filter-name><filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class><init-param><param-name>targetFilterLifecycle</param-name><param-value>true</param-value></init-param><!-- <init-param><param-name>targetBeanName</param-name><param-value>abc</param-value>也可以重新命名,但通常沒這個必要</init-param>--> </filter>在applicationContext.xml中
... <!-- 6. 配置 ShiroFilter. id 必須和 web.xml 文件中配置的 DelegatingFilterProxy 的 <filter-name> 一致.若不一致, 則會拋出: NoSuchBeanDefinitionException.因為 Shiro 會來 IOC 容器中查找和 <filter-name> 名字對應的 filter bean. --> <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"><property name="securityManager" ref="securityManager"/>...</bean> ...6. 權限URL配置細節
[urls]部分的配置格式是:“url=攔截器[參數],攔截器[參數]”。
如果當前請求的 url 匹配 [urls] 部分的某個 url 模式,將會執行其配置的攔截器。
攔截器例子如下:
- anon(anonymous)攔截器表示匿名訪問(即不需要登錄即可訪問)
- authc(authentication)攔截器表示需要身份認證通過后才能訪問
URL 匹配模式
url 模式使用 Ant 風格模式
Ant 路徑通配符支持 ?、 *、 **,注意通配符匹配不包括目錄分隔符“/”
- ?:匹配一個字符, 如 /admin? 將匹配 /admin1,但不匹配 /admin 或 /admin/;
- *:匹配零個或多個字符串, 如 /admin 將匹配 /admin、/admin123,但不匹配 /admin/1;
- **:匹配路徑中的零個或多個路徑, 如 /admin/** 將匹配 /admin/a 或 /admin/a/b
Shiro中默認的過濾器
Default Filters
URL 匹配順序
URL 權限采取第一次匹配優先的方式, 即從頭開始使用第一個匹配的 url 模式對應的攔截器鏈。如:
- /bb/**=filter1
- /bb/aa=filter2
- /**=filter3
如果請求的url是“/bb/aa”,因為按照聲明順序進行匹配,那么將使用 filter1 進行攔截。(先聲奪人)
7. 認證思路分析
認證說白就是登陸
8. 實現認證流程
9. 實現認證Realm
ShiroRealm
public class ShiroRealm extends AuthorizingRealm {@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {System.out.println("[FirstRealm] doGetAuthenticationInfo");//1. 把 AuthenticationToken 轉換為 UsernamePasswordToken UsernamePasswordToken upToken = (UsernamePasswordToken) token;//2. 從 UsernamePasswordToken 中來獲取 usernameString username = upToken.getUsername();//3. 調用數據庫的方法, 從數據庫中查詢 username 對應的用戶記錄System.out.println("從數據庫中獲取 username: " + username + " 所對應的用戶信息.");//4. 若用戶不存在, 則可以拋出 UnknownAccountException 異常if("unknown".equals(username)){throw new UnknownAccountException("用戶不存在!");}//5. 根據用戶信息的情況, 決定是否需要拋出其他的 AuthenticationException 異常. if("monster".equals(username)){throw new LockedAccountException("用戶被鎖定");}//6. 根據用戶的情況, 來構建 AuthenticationInfo 對象并返回. 通常使用的實現類為: SimpleAuthenticationInfo//以下信息是從數據庫中獲取的.//1). principal: 認證的實體信息. 可以是 username, 也可以是數據表對應的用戶的實體類對象. Object principal = username;//2). credentials: 密碼. Object credentials = "123456";//3). realmName: 當前 realm 對象的 name. 調用父類的 getName() 方法即可String realmName = getName();SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(principal, credentials, realmName);return info;} }10. 密碼的比對
密碼的比對:通過 AuthenticatingRealm 的 credentialsMatcher 屬性來進行的密碼的比對。
11. 密碼的MD5加密
如何把一個字符串加密為 MD5
替換當前 Realm 的 credentialsMatcher 屬性。直接使用 HashedCredentialsMatcher 對象, 并設置加密算法即可。
在applicationContext.xml中
<!-- 3. 配置 Realm,直接配置實現了 org.apache.shiro.realm.Realm 接口的 bean --><bean id="jdbcRealm" class="com.lun.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>既然對密碼了,ShiroRealm中的部分代碼稍作修改。
計算求出密碼加密后的密碼
public static void main(String[] args) {String hashAlgorithmName = "MD5";Object credentials = "123456";Object salt = null;//鹽值下一節將提到int hashIterations = 1024;Object result = new SimpleHash(hashAlgorithmName, credentials, salt, hashIterations);System.out.println(result); }得出fc1709d0a95a6be30bc5926fdb7f22f4
然后在ShiroRealm中的
Object credentials = "123456";改為
Object credentials = "fc1709d0a95a6be30bc5926fdb7f22f4";便可實現密碼加密,通過驗證。
12. 密碼的MD5鹽值加密
為什么使用 MD5 鹽值加密: 因為有時希望即使兩個原始密碼一樣,加密后的密碼也不一樣,這樣做相對安全些。
鹽是日常生活中的調料。
如何做到:
然后計算密碼加密加鹽的值
public static void main(String[] args) {String hashAlgorithmName = "MD5";Object credentials = "123456";Object salt = ByteSource.Util.bytes("user");int hashIterations = 1024;Object result = new SimpleHash(hashAlgorithmName, credentials, salt, hashIterations);System.out.println(result); }在ShiroRealm進行對應的修改
@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {....//2). credentials: 密碼. Object credentials = null; //"fc1709d0a95a6be30bc5926fdb7f22f4";if("admin".equals(username)){credentials = "038bdaf98f2037b31f1e75b5b4c9b26e";/*上面main()計算得出加密加鹽的值*/}else if("user".equals(username)){credentials = "098d2c478e9c11555ce2823231e02ec1";/*上面main()計算得出加密加鹽的值*/}//3). realmName: 當前 realm 對象的 name. 調用父類的 getName() 方法即可String realmName = getName();//4). 鹽值. ByteSource credentialsSalt = ByteSource.Util.bytes(username);SimpleAuthenticationInfo info = null; info = new SimpleAuthenticationInfo(principal, credentials, credentialsSalt, realmName);return info; }13. 多Realm驗證
realm /relm/ n. 領域; 場所; 王國
可設置多個Realm設置
applicationContext.xml
<!-- 1. 配置 SecurityManager --> <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><!-- 多Realm是要添加的多個認真策略 --> <bean id="authenticator" class="org.apache.shiro.authc.pam.ModularRealmAuthenticator"><property name="authenticationStrategy"><bean class="org.apache.shiro.authc.pam.AtLeastOneSuccessfulStrategy"></bean></property> </bean>創建添加第二個Realm,SecondRealm
applicationContext.xml實現SecondRealm的Bean
<bean id="secondRealm" class="com.lun.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>14. 認證策略
AuthenticationStrategy 接口的默認實現:
- FirstSuccessfulStrategy:只要有一個 Realm 驗證成功即可,只返回第一個 Realm 身份驗證成功的認證信息,其他的忽略;
- AtLeastOneSuccessfulStrategy:只要有一個Realm驗證成功即可, 和FirstSuccessfulStrategy 不同,將返回所有Realm身份驗證成功的認證信息;
- AllSuccessfulStrategy:所有Realm驗證成功才算成功,且返回所有Realm身份驗證成功的認證信息,如果有一個失敗就失敗了。
- ModularRealmAuthenticator 默認是 AtLeastOneSuccessfulStrategy策略
15. 把realms配置給SecurityManager
<bean id="authenticator" class="org.apache.shiro.authc.pam.ModularRealmAuthenticator"><property name="authenticationStrategy"><bean class="org.apache.shiro.authc.pam.AtLeastOneSuccessfulStrategy"></bean></property><property name="realms"><list><ref bean="jdbcRealm"/><ref bean="secondRealm"/></list></property> </bean>中的property name="realms"部分可以移動到bean id=“securityManager” 中,原因在查閱源碼中,securityManager 的authenticator是ModularRealmAuthenticator類的實例,就將securityManager的realms注入authenticator當中。
<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>這樣做可以放心為下一節授權做鋪墊。
16. 權限配置
- 授權,也叫訪問控制,即在應用中控制誰訪問哪些資源(如訪問頁面/編輯數據/頁面操作等)。在授權中需了解的幾個關鍵對象:主體( Subject)、資源( Resource)、權限( Permission)、角色( Role)。
- 主體(Subject):訪問應用的用戶, 在 Shiro 中使用 Subject代表該用戶。用戶只有授權后才允許訪問相應的資源。
- 資源(Resource):在應用中用戶可以訪問的 URL, 比如訪問 JSP 頁面、查看/編輯某些數據、訪問某個業務方法、打印文本等等都是資源。用戶只要授權后才能訪問。
- 權限(Permission):安全策略中的原子授權單位,通過權限我們可以表示在應用中用戶有沒有操作某個資源的權力。即權限表示在應用中用戶能不能訪問某個資源,如:訪問用戶列表頁面查看/新增/修改/刪除用戶數據(即很多時候都是CRUD(增查改刪)式權限控制)等。權限代表了用戶有沒有操作某個資源的權利,即反映在某個資源上的操作允不允許。
- Shiro 支持粗粒度權限(如用戶模塊的所有權限)和細粒度權限(操作某個用戶的權限,即實例級別的)
- 角色(Role):權限的集合,一般情況下會賦予用戶角色而不是權限,即這樣用戶可以擁有一組權限,賦予權限時比較方便。典型的如:項目經理、技術總監、CTO、開發工程師等都是角色,不同的角色擁有一組不同的權限。
Shiro 支持三種方式的授權
- 編程式:通過寫if/else 授權代碼塊完成
- 注解式:通過在執行的Java方法上放置相應的注解完成,沒有權限將拋出相應的異常
- JSP/GSP 標簽:在JSP/GSP 頁面通過相應的標簽完成
默認攔截器
Shiro 內置了很多默認的攔截器,比如身份驗證、授權等相關的。默認攔截器可以參考org.apache.shiro.web.filter.mgt.DefaultFilter中的枚舉攔截器:
Default Filters
身份驗證相關的攔截器
| authc | org.apache. shiro.web.filter.authc.FormAuthenticationFilter | 基于表單的攔截器;如"/**=authc" ,如果沒有登錄會跳到相應的登錄頁面登錄;主要屬性: usernameParam :表單提交的用戶名參數名(username); passwordParam :表單提交的密碼參數名(password); rememberMeParam :表單提交的密碼參數名(rememberMe); loginUrl :登錄頁面地址(/login.jsp); successUrl :登錄成功后的默認重定向地址; failureKeyAttribute :登錄失敗后錯誤信息存儲key( shirologinFailure); |
| authcBasic | org.apache. shiro.web.filter.authc.BasicHttpAuthenticationFilter | Basic HTTP身份驗證攔截器,主要屬性 :applicationName :彈出登錄框顯示的信息( application) ; |
| logout | org.apache.shiro.web.filter.authc.LogoutFilter | 退出攔截器,主要屬性: redirectUrl :退出成功后重定向的地址( / ) ;示例:"/logout=logout" |
| user | org.apache.shiro.web.filter.authc.UserFilter | 用戶攔截器,用戶已經身份驗證/記住我登錄的都可;示例"/** =user" |
| anon | org.apache.shiro.web.filter.authc.AnonymousFilter | 匿名攔截器,即不需要登錄即可訪問; -般用于靜態資源過濾;示例:/static/** = anon" |
授權相關的攔截器
| roles | org.apache.shiro.web.filter.authz.RolesAuthorizationFilter | 角色授權攔截器,驗證用戶是否擁有所有角色;主要屬性:loginUrl :登錄頁面地址(/login.jsp);unauthorizedUrl :未授權后重定向的地址;示例: “/admin/** =roles[admin]” |
| perms | org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter | 權限授權攔截器,驗證用戶是否擁有所有權限;屬性和roles一樣;示例"/user/**=perms[“user:create”]" |
| port | org.apache.shiro.web.filter.authz.PortFilter | 端口攔截器,主要屬性: port(80) :可以通過的端口;示例"/test= port[80]如果用戶訪問該頁面是非80 ,將自動將請求端口改為80并重定向到該80端口,其他路徑/參數等都一樣 |
| rest | org.apache.shiro.web.filter.authz.HttpMethodPermissionFilter | rest風格攔截器,自動根據請求方法構建權限字符串( GET=read,POST=create,PUT=update,DELETE=delete,HEAD=read,TRACE=read,OPTIONS =read, MKCOL=create )構建權限字符串;示例:/users=rest[user]” ,會自動拼出"user:read,user:create,user:update,user:delete"權限字符串進行權限匹配(所有都得匹配, isPermittedAll); |
| ssl | org.apache.shiro.web.filter.authz.SslFilter | SSL攔截器,只有請求協議是https才能通過;否則自動跳轉會https端口(443) ;其他和port攔截器一樣; |
其他的攔截器
| noSessionCreation | org.apache.shiro.web.filter.session. NoSessionCreationFilter | 不創建會話攔截器,調用subject.getSession(false)不會有什么問題,但是如果subject.getSession(true)將拋出 |
實操
新建兩頁面
- admin.jsp - 只可角色admin訪問的
- user.jsp - 只可角色user訪問的
在applicationContext.xml中進行配置
<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"/><!-- 配置哪些頁面需要受保護. 以及訪問這些頁面需要的權限. 1). anon 可以被匿名訪問2). authc 必須認證(即登錄)后才可能訪問的頁面. 3). logout 登出.4). roles 角色過濾器--><property name="filterChainDefinitions"><value>/login.jsp = anon/shiro/login = anon/shiro/logout = logout<!-- 本節關鍵這里 -->/user.jsp = roles[user]/admin.jsp = roles[admin]# everything else requires authentication:/** = authc</value></property> </bean>在ShiroRealm設置realmName字符串是用來設置角色
public class ShiroRealm extends AuthorizingRealm {@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {//...info = new SimpleAuthenticationInfo(principal, credentials, credentialsSalt, realmName);//realmName是設置角色的return info;} }17. 授權流程分析
調用詳細流程可以通過Debug Quickstart的currentUser.hasRole("schwartz")進行了解
18. 多Realm授權的通過標準
調用詳細流程可以通過Debug Quickstart的currentUser.hasRole("schwartz")進行了解
19. 實現授權Realm
ShiroRealm
public class ShiroRealm extends AuthorizingRealm {//...//授權會被 shiro 回調的方法@Overrideprotected 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;} }20. 標簽
Shiro 提供了 JSTL 標簽用于在 JSP 頁面進行權限控制,如根據登錄用戶顯示相應的頁面按鈕。
在JSP文件中引入時
<%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>guest 標簽:用戶沒有身份驗證時顯示相應信息,即游客訪問信息:
<shiro:guest>歡迎游客訪問</shiro:guest>user 標簽:用戶已經經過認證/記住我登錄后顯示相應的信息。
<shiro:user>歡迎</shiro:user>authenticated 標簽:用戶已經身份驗證通過,即Subject.login登錄成功, 不是記住我登錄的
<shiro:authenticated></shiro:authenticated>notAuthenticated 標簽: 用戶未進行身份驗證, 即沒有調用Subject.login進行登錄, 包括記住我自動登錄的也屬于未進行身份驗證。
<shiro:notAuthenticated></shiro:notAuthenticated>pincipal標簽: 顯示用戶身份信息,默認調用Subject.getPrincipal() 獲取, 即 Primary Principal。
<shiro:principal property="username"></shiro:principal>hasRole 標簽:如果當前 Subject 有角色將顯示 body 體內容:
<shiro:hasRole name="admin"><a href="admin.jsp">Admin Page</a> </shiro:hasRole>hasAnyRoles 標簽:如果當前Subject有任意一個角色(或的關系)將顯示body體內容
<shiro:hasAnyRoles name="user,admin"></shiro:hasAnyRoles>lacksRole:如果當前 Subject 沒有角色將顯示 body 體內容
<shiro:lacksRole name="admin"></shiro:lacksRole>hasPermission: 如果當前 Subject 有權限將顯示 body 體內容
<shiro:hasPermission name="user:create"></shiro:hasPermission>lacksPermission: 如果當前Subject沒有權限將顯示body體內容。
<shiro:lacksPermission name="org:create"></shiro:lacksPermission>21. 權限注解
- @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。
ShiroService
public class ShiroService {@RequiresRoles({"admin"})//這里使用到權限注解,這里RequiresRoles要用到admin角色權限public void testMethod(){System.out.println("testMethod, time: " + new Date());Session session = SecurityUtils.getSubject().getSession();Object val = session.getAttribute("key");System.out.println("Service SessionVal: " + val);}}在applicaContext.xml實現Bean
<bean id="shiroService"class="com.lun.shiro.services.ShiroService"></bean> @Controller @RequestMapping("/shiro") public class ShiroHandler {@Autowiredprivate ShiroService shiroService;@RequestMapping("/testShiroAnnotation")public String testShiroAnnotation(HttpSession session){session.setAttribute("key", "value12345");shiroService.testMethod();//該方法調用需要驗證權限,用到admin角色權限,否則,就會報錯return "redirect:/list.jsp";}//... }22. 從數據表中初始化資源和權限
在applicationContext.xml硬編碼分配權限角色,這不太好。通常情況下,是需要從數據庫中讀取權限角色的。
<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><!-- 配置哪些頁面需要受保護. 以及訪問這些頁面需要的權限. 1). anon 可以被匿名訪問2). authc 必須認證(即登錄)后才可能訪問的頁面. 3). logout 登出.4). roles 角色過濾器--> <!--<property name="filterChainDefinitions"><value>/login.jsp = anon/shiro/login = anon/shiro/logout = logout/user.jsp = roles[user]/admin.jsp = roles[admin]# everything else requires authentication:/** = authc</value></property>--> </bean>將property name="filterChainDefinitions"注釋掉,添加property name=“filterChainDefinitionMap” ref=“filterChainDefinitionMap”
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">...<property name="filterChainDefinitionMap" ref="filterChainDefinitionMap"></property>... </bean>然后,在applicationContext.xml中注冊工廠方法Bean:
<!-- 配置一個 bean, 該 bean 實際上是一個 Map. 通過實例工廠方法的方式 --> <bean id="filterChainDefinitionMap" factory-bean="filterChainDefinitionMapBuilder" factory-method="buildFilterChainDefinitionMap"></bean><bean id="filterChainDefinitionMapBuilder"class="com.lun.shiro.factory.FilterChainDefinitionMapBuilder"></bean>然后實現工廠類FilterChainDefinitionMapBuilder.java
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;}}23. 會話管理
Shiro 提供了完整的企業級會話管理功能, 不依賴于底層容器(如web容器tomcat),不管JavaSE還是JavaEE 環境都可以使用,提供了會話管理、會話事件監聽、會話存儲/持久化、容器無關的集群、失效/過期支持、對Web 的透明支持、 SSO 單點登錄的支持等特性。
Session會話相關的 API
- Subject.getSession():即可獲取會話;其等價于Subject.getSession(true),即如果當前沒有創建 Session 對象會創建一個; Subject.getSession(false),如果當前沒有創建 Session 則返回null
- 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):設置/獲取/刪除會話屬性;在整個會話范圍內都可以對這些屬性進行操作
ShiroHandler
@Controller @RequestMapping("/shiro") public class ShiroHandler {@Autowiredprivate ShiroService shiroService;@RequestMapping("/testShiroAnnotation")public String testShiroAnnotation(HttpSession session){session.setAttribute("key", "value12345");//寫入到SessionshiroService.testMethod();return "redirect:/list.jsp";}... }ShiroService
public class ShiroService {@RequiresRoles({"admin"})public void testMethod(){System.out.println("testMethod, time: " + new Date());//從Session中讀取Session session = SecurityUtils.getSubject().getSession();Object val = session.getAttribute("key");System.out.println("Service SessionVal: " + val);}... }24. SessionDao
SessionDao可讓Session在數據庫中CRUD。
- AbstractSessionDAO 提供了 SessionDAO 的基礎實現,如生成會話ID等
- CachingSessionDAO 提供了對開發者透明的會話緩存的功能,需要設置相應的 CacheManager
- MemorySessionDAO 直接在內存中進行會話維護
- EnterpriseCacheSessionDAO 提供了緩存功能的會話維護, 默認情況下使用 MapCache 實現,內部使用ConcurrentHashMap 保存緩存的會話。
配置示例
<!-- Session ID生成器-- > <bean id="sessionIdGenerator"class="org.apache.shiro.session.mgt.eis.JavaUuidsessionIdGenerator"/><!-- Session DAO. 繼承EnterpriseCacheSessionDAO --> <bean id="sessionDAO" class="com.lun.shiro.realms.MySessionDao"><property name="activeSessionsCacheName" value="shiro-activeSessionCache"/><property name="sessionIdGenerator" ref="sessionIdGenerator"/> </bean><!-- 會話管理器--> <bean id="sessionManager" class="org.apache.shiro.session.mgt.Defaul tSessionManager"><property name="globalSessionTimeout" value="1800000"/><property name="deleteInvalidSessions" value="true"/><property name="sessionValidationSchedulerEnabled" value="true"/><property name="sessionDAO" ref="sessionDAO"/> </bean> <cache name="shiro-activeSessionCache"maxEntriesLocalHeap="10000"overflowToDisk="false"eternal="false"diskPersistent="false"timeToLiveSeconds="0"timeToIdleSeconds="0"statistics="true"/> create table sessions (id varchar(200),session varchar(2000),constraint pk_ sessions primary key(id) ) charset=utf8 ENGINE= InnoDB;Session Dao
@Autowired private JdbcTemplate jdbcTemplate = nu1l;@Override protected Serializable doCreate(Session session){Serializable sessionId = generateSessionId(session);assignSessionId,(session, sessionId);String sql = "insert into sessions(id, session) values(?, ?)";|jdbcTemplate.update(sql,sessionId,serializableUtils. serialize (session);return session.getId(); }@Override protected Session doReadSession(Serializable sessionId){String sql = "select session from sessions where id=?";List<String> sessionStrList = jdbcTemplate.queryForList(sql, String.class, sessionId);if (sessionStrList.size() == 0)return null;return SerializableUtils.deserialize(sessionStrList.get(0)); }@Override protected void doUpdate (Session session) {if (session instanceof ValidatingSession&& ! ((ValidatingSession)session).isValid()) {return;String sql = "update sessions set session=? where id=?";jdbcTemplate.update(sql, SerializableUtils.serialize(session), session.getId()); }@Override protected void doDelete (Session session) {String sql = "delete from sessions where id=?";jdbcTemplate.update(sql, session.getId()); }SerializableUtils
public static String serialize (Session session) {tryByteArrayOutputStream bos = new ByteArrayOutputStream();ObjectoutputStream oos = new objectOutputStream(bos);oos.writeobject(session);return Base64.encodeTostring(bos.toByteArray());} catch(Exception e){throw new RuntimeException("serialize session error", e) ;} }public static Session deserialize (String sessionStr) {tryByteArrayInputStream bis = new ByteArrayInputStream();Base64.decode(sessionStr));ObjectInputstream ois = new objectInputstream(bis);return (Session)ois.readobject();} catch (Exception e) {throw new RuntimeException ("deserialize session error", e);} }25. 緩存
CacheManagerAware 接口
Shiro 內部相應的組件( DefaultSecurityManager)會自動檢測相應的對象(如Realm)是否實現了CacheManagerAware 并自動注入相應的CacheManager
Realm 緩存
Shiro 提供了 CachingRealm,其實現了CacheManagerAware 接口,提供了緩存的一些基礎實現;
AuthenticatingRealm 及 AuthorizingRealm 也分別提供了對AuthenticationInfo 和 AuthorizationInfo 信息的緩存。
Session 緩存
- 如 SecurityManager 實現了 SessionSecurityManager,其會判斷 SessionManager 是否實現了CacheManagerAware 接口,如果實現了會把CacheManager 設置給它。
- SessionManager 也會判斷相應的 SessionDAO(如繼承自CachingSessionDAO)是否實現了CacheManagerAware,如果實現了會把 CacheManager設置給它
- 設置了緩存的 SessionManager,查詢時會先查緩存,如果找不到才查數據庫。
ehcache.xml
<ehcache><diskStore path="java.io.tmpdir"/><!--Predefined caches. Add your cache configuration settings here.If you do not have a configuration for your cache a WARNING will be issued when theCacheManager startsThe following attributes are required for defaultCache:name - Sets the name of the cache. This is used to identify the cache. It must be unique.maxInMemory - Sets the maximum number of objects that will be created in memoryeternal - Sets whether elements are eternal. If eternal, timeouts are ignored and the elementis never expired.timeToIdleSeconds - Sets the time to idle for an element before it expires. Is only usedif the element is not eternal. Idle time is now - last accessed timetimeToLiveSeconds - Sets the time to live for an element before it expires. Is only usedif the element is not eternal. TTL is now - creation timeoverflowToDisk - Sets whether elements can overflow to disk when the in-memory cachehas reached the maxInMemory limit.--><cache name="authorizationCache"eternal="false"timeToIdleSeconds="3600"timeToLiveSeconds="0"overflowToDisk="false"statistics="true"></cache><cache name="authenticationCache"eternal="false"timeToIdleSeconds="3600"timeToLiveSeconds="0"overflowToDisk="false"statistics="true"></cache><cache name="shiro-activeSessionCache"eternal="false"timeToIdleSeconds="3600"timeToLiveSeconds="0"overflowToDisk="false"statistics="true"></cache>...</ehcache>具體怎么使用的,視頻沒說,還需查閱更多資料
26. 認證和記住我的區別
Shiro 提供了記住我( RememberMe)的功能,比如訪問如淘寶等一些網站時,關閉了瀏覽器,下次再打開時還是能記住你是誰,下次訪問時無需再登錄即可訪問,基本流程如下:
27. 實現Remember me
UsernamePasswordToken類中setRememberMe()
ShiroHandler
@Controller @RequestMapping("/shiro") public class ShiroHandler {...@RequestMapping("/login")public String login(@RequestParam("username") String username, @RequestParam("password") String password){Subject currentUser = SecurityUtils.getSubject();if (!currentUser.isAuthenticated()) {// 把用戶名和密碼封裝為 UsernamePasswordToken 對象UsernamePasswordToken token = new UsernamePasswordToken(username, password);// remembermetoken.setRememberMe(true);//設置remeberMetry {System.out.println("1. " + token.hashCode());...}}在applicationContext.xml中設置rememberMe的壽命
... <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">...<property name="rememberMeManager.cookie.maxAge" value="10"></property> </bean> ...總結
- 上一篇: 《Python Cookbook 3rd
- 下一篇: Github(4)-远程操作