Shiro 那点事儿
2019獨角獸企業重金招聘Python工程師標準>>>
前幾天我遇見了一位美女,真有種相逢恨晚的感覺。她皮膚白皙、氣質優雅、楚楚動人,擁有苗條的身材,卻又不失豐滿之軀,正所謂“該大的地方大,該小的地方小”,她就是我朝思夢想的情人。
她就是 Apache 組織下的名媛 —— Shiro(希羅),一款輕量級 Java 安全框架。如果您已經玩膩了豐滿的 Spring Security,想品嘗一下新口味的話,建議先到她的裙下一睹風光吧:
Apache Shiro 官網: http://shiro.apache.org/從官網上,我們基本上可以了解到,她提供的服務非常明確:
Authentication(認證)
Authorization(授權)
Session Management(會話管理)
Cryptography(加密)
首先,她提供了?Authentication(認證)服務,也就是說,通過她可以完成身份認證,讓她去判斷您是否為真實的會員。
其次,她還提供了?Authorization(授權)服務,其實說白了就是“訪問控制”服務,也就是讓她來識別您是否可以做某件事情,畢竟不同的會員是擁有不同的權限的。
更有特色的是,她還提供了?Session Management(會話管理)服務,這個就厲害了,這并不是您熟知的?HTTP Session,而是一個獨立的 Session 管理框架,不管是否為?Web 應用,都可以用這套框架。
最后(但并不是最不重要的),她還提供了?Cryptography(加密)服務,封裝了許多密碼學算法,有您知道的,也有您不知道的,總之琳瑯滿目,應有盡有。
除了以上 4 個基本服務以外,她也提供很好的系統集成方案,您可以輕松將其運用到 Web 應用中,可能這也是您最關心的。此外,還可以集成第三方框架,例如:Spring、Guice、CAS 等。
想必您已經了解了 Shiro 的主要功能,那么如何才能真正占有她呢?不妨先主動跟她打聲招呼吧:Hello Shiro!
Hello Shiro
如果您與我一樣,也使用 Maven 的話,可以將以下配置復制到您的 pom.xml 文件中:
<!--?SLFJ?--> <dependency><groupId>org.slf4j</groupId><artifactId>slf4j-log4j12</artifactId><version>1.6.4</version> </dependency> <!--?Shiro?--> <dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-core</artifactId><version>1.2.3</version> </dependency>需要說明的是,Shiro 依賴于?SLFJ 日志框架,而?SLFJ 只是一個接口,并沒有提供具體的實現,您可以選擇 Log4J 作為它的實現,正好?SLFJ 也提供了一個?slf4j-log4j12 的 Artifact,所以這就用上了。
下面是 hello 項目的 Maven 依賴圖:
既然使用了 Log4J,那么就應該在 classpath 下提供一個 log4j.properties 文件:
log4j.rootLogger?=?INFO,?consolelog4j.appender.console?=?org.apache.log4j.ConsoleAppender log4j.appender.console.layout?=?org.apache.log4j.PatternLayout log4j.appender.console.layout.ConversionPattern?=?%-5p?%c(%L)?-?%m%n通過上面的配置將日志輸出到控制臺上,并配置了日志輸出格式。
同樣,既然使用了 Shiro,那么就應該在 classpath 下提供一個 shiro.ini 文件:
[users] shiro?=?201314很簡單的配置,我們配置了一個用戶名為 shiro,密碼為“愛你一生一世”的用戶。當然,這里僅為演示,在您的實際項目中肯定不會把用戶信息定義在配置文件中,除非這個項目的用戶只有您自己。
我們就用這個用戶來見識一下 Shiro 的認證服務功能吧,不妨寫一個 main 方法試試:
import?org.apache.shiro.SecurityUtils; import?org.apache.shiro.authc.AuthenticationException; import?org.apache.shiro.authc.UsernamePasswordToken; import?org.apache.shiro.config.IniSecurityManagerFactory; import?org.apache.shiro.mgt.SecurityManager; import?org.apache.shiro.subject.Subject; import?org.apache.shiro.util.Factory; import?org.slf4j.Logger; import?org.slf4j.LoggerFactory;public?class?HelloShiro?{private?static?final?Logger?logger?=?LoggerFactory.getLogger(HelloShiro.class);public?static?void?main(String[]?args)?{//?初始化?SecurityManagerFactory<SecurityManager>?factory?=?new?IniSecurityManagerFactory("classpath:shiro.ini");SecurityManager?securityManager?=?factory.getInstance();SecurityUtils.setSecurityManager(securityManager);//?獲取當前用戶Subject?subject?=?SecurityUtils.getSubject();//?登錄UsernamePasswordToken?token?=?new?UsernamePasswordToken("shiro",?"201314");try?{subject.login(token);}?catch?(AuthenticationException?ae)?{logger.info("登錄失敗!");return;}logger.info("登錄成功!Hello?"?+?subject.getPrincipal());//?注銷subject.logout();} }我們分析一下這個 HelloShiro 吧:
需要讀取 classpath 下的?shiro.ini 配置文件,并通過工廠類創建?SecurityManager 對象,最終將其放入?SecurityUtils 中,供 Shiro 框架隨時獲取。
同樣通過 SecurityUtils 類獲取 Subject 對象,其實就是當前用戶,只不過在 Shiro 的世界里優雅地將其稱為 Subject(主體)。
首先使用一個 Username 與 Password,來創建一個 UsernamePasswordToken 對象,然后我們通過這個 Token 對象調用 Subject 對象的 login 方法,讓 Shiro 進行用戶身份認證。
當登錄失敗時,您可以使用?AuthenticationException 來捕獲這個異常;當登錄成功時,您可以調用 Subject 對象的 getPrincipal 方法來獲取 Username,此時 Shiro 已經為您創建了一個 Session。
最后還是通過 Subject 對象的 logout 方法來注銷本次 Session。
感覺還不錯吧?您只需要知道以上幾個?Shiro 的核心成員的基本用法,Shiro 就是您的了。
其實,Shiro 的內部調用流程也不難理解:
通過 Subject 調用 SecurityManager,通過 SecurityManager 調用 Realm。這個 Realm 感覺有點生僻,其實就是提供用戶信息的數據源了,在以上例子在 shiro.ini 里配置的用戶信息就是一種 Realm,在 Shiro 中叫做 IniRealm。除此以外,Shiro 還提供了其它幾種 Realm:PropertiesRealm、JdbcRealm、JndiLdapRealm、ActiveDirectoryReam 等,當然也可以定制 Realm 來滿足您的業務需求。
不難發現,SecurityManager 是才是 Shiro 的真正的核心,您只需通過 Subject 就可以操作?SecurityManager,尤其是在 Web 應用中,您甚至都可以忘記?SecurityManager 的存在。
那么,在 Web 中應該如何使用 Shiro 呢?我們繼續吧!
在 Web 開發中使用 Shiro
您可以直接在 Web 應用中使用 Shiro 官方提供的?Web 模塊 —— shiro-web。
只需在您的 pom.xml 文件中增加如下配置:
<dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-web</artifactId><version>1.2.3</version> </dependency>隨后,在您的 web.xml 中添加一個 Listener 與一個 Filter:
<?xml?version="1.0"?encoding="UTF-8"?> <web-app?xmlns="http://java.sun.com/xml/ns/javaee"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://java.sun.com/xml/ns/javaeehttp://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"version="3.0"><listener><listener-class>org.apache.shiro.web.env.EnvironmentLoaderListener</listener-class></listener><filter><filter-name>ShiroFilter</filter-name><filter-class>org.apache.shiro.web.servlet.ShiroFilter</filter-class></filter><filter-mapping><filter-name>ShiroFilter</filter-name><url-pattern>/*</url-pattern></filter-mapping></web-app>實際上就是通過?EnvironmentLoaderListener 這個監聽器來初始化 SecurityManager 的,并且通過?ShiroFilter 來完成認證與授權的。
可以使用以下數據表結構來存放您的用戶及其權限相關數據,這就是傳說中的?RBAC 模型:
然后通過 Shiro 的 JdbcReam 來進行認證與授權,這一切都是那么的簡單,只需您在 shiro.ini 中做如下配置:
[main] authc.loginUrl = /loginds?=?org.apache.commons.dbcp.BasicDataSource ds.driverClassName?=?com.mysql.jdbc.Driver ds.url?=?jdbc:mysql://localhost:3306/sample ds.username?=?root ds.password?=?rootjdbcRealm?=?org.apache.shiro.realm.jdbc.JdbcRealm jdbcRealm.dataSource?=?$ds jdbcRealm.authenticationQuery?=?select?password?from?user?where?username?=?? jdbcRealm.userRolesQuery?=?select?r.role_name?from?user?u,?user_role?ur,?role?r?where?u.id?=?ur.user_id?and?r.id?=?ur.role_id?and?u.username?=?? jdbcRealm.permissionsQuery?=?select?p.permission_name?from?role?r,?role_permission?rp,?permission?p?where?r.id?=?rp.role_id?and?p.id?=?rp.permission_id?and?r.role_name?=?? jdbcRealm.permissionsLookupEnabled?=?true securityManager.realms?=?$jdbcRealm[urls] /?=?anon /space/**?=?authc對以上配置解釋如下:
首先,在 [main] 片段中,我們定義了一個“authc.loginUrl=/login”,用于配置當需要認證時需要跳轉的 URL 地址,這里表示重定向到 /login 請求,通過 Servlet 映射后可定位到 login 頁面。
然后,定義了一個 DBCP 的 DataSource,用于獲取 JDBC 數據庫連接。
然后,定義 JdbcRealm 并指定 DataSource,通過配置以下幾條 SQL 來完成認證與授權:
-
authenticationQuery:該 SQL 語句用于提供身份認證,即通過 username 查詢 password。
-
userRolesQuery:該 SQL 語句用于提供基于角色的授權驗證(屬于粗粒度級別),即通過 username 查詢 role_name。
-
permissionQuery:該 SQL 語句用于提供基于權限的授權驗證(屬于細粒度級別),即通過 role_name 查詢 permission_name,此時需要開啟?permissionsLookupEnabled 開關,默認是關閉的。
最后,在 [urls] 片段中,我們定義了一系列的 URL 過濾規則,Shiro 已經提供了一些默認的 Filter(過濾器),便于我們隨時使用,當然您也可以擴展其它的過濾器。就本例配置而言,解釋如下:
-
/ = anon:對于“/”請求(首頁)可以匿名訪問的。
-
/space/** = authc:對于以“/space/”開頭的請求,均由 authc 過濾器處理,也就是完成身份認證操作。
還需要補充說明的是,在 permission 表中存放了所有的權限名,實際上是一個權限字符串,推薦使用“資源:操作”這種格式來命名,例如:product:view(查看產品權限)、product:edit(產品編輯權限)、product:delete(產品刪除權限)等。
這些默認的過濾器包括:
| 過濾器名稱 | 功能 | 配置項(及其默認值) |
| anon | 確保未登錄(匿名)的用戶發送的請求才能通過 | |
| authc | 確保已認證的用戶發送的請求才能通過(若未認證,則跳轉到登錄頁面) | authc.loginUrl = /login.jsp authc.successUrl = / authc.usernameParam = username authc.passwordParam = password authc.rememberMeParam = rememberMe authc.failureKeyAttribute = shiroLoginFailure |
| authcBasic | 提供 Basic HTTP 認證功能(在瀏覽器中彈出一個登錄對話框) | authcBasic.applicationName = application |
| logout | 接收結束會話的請求 | logout.redirectUrl = / |
| noSessionCreation | 提供 No Session 解決方案(若有 Session 就會報錯) | |
| perms | 確保擁有特定權限的用戶發送的請求才能通過 | |
| port | 確保特定端口的請求才能通過 | port = 80 |
| rest | 提供 REST 解決方案(根據 REST URL 計算權限字符串) | |
| roles | 確保擁有特定角色的用戶發送的請求才能通過 | |
| ssl | 確保只有 HTTPS 的請求才能通過 | |
| user | 確保已登錄的用戶發送的請求才能通過(包括:已認證或已記住) | |
可以在 index.jsp(首頁)中這樣來判斷該用戶是否已游客還是已登錄的用戶:
<%@?page?pageEncoding="UTF-8"?%> <%@?taglib?prefix="c"?uri="http://java.sun.com/jsp/jstl/core"?%> <%@?taglib?prefix="fmt"?uri="http://java.sun.com/jsp/jstl/fmt"?%> <%@?taglib?prefix="shiro"?uri="http://shiro.apache.org/tags"?%> <html> <head><title>首頁</title> </head> <body><h1>首頁</h1><shiro:guest><p>身份:游客</p><a?href="<c:url?value="/login"/>">登錄</a><a?href="<c:url?value="/register"/>">注冊</a> </shiro:guest><shiro:user><p>身份:<shiro:principal/></p><a?href="<c:url?value="/space"/>">空間</a><a?href="<c:url?value="/logout"/>">退出</a> </shiro:user></body> </html>需要使用 Shiro 提供的 JSP 標簽:
<%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>
隨后就可以使用 shiro 標簽的相關功能了,它們包括:
| 標簽 | 功能 |
| <shiro:guest>...</shiro:guest> | 判斷當前用戶是否為游客 |
| <shiro:user>...</shiro:user> | 判斷當前用戶是否已登錄(包括:已認證或已記住) |
| <shiro:authenticated>...</shiro:authenticated> | 判斷當前用戶是否已認證通過(不包括已記住) |
| <shiro:notAuthenticated>...</shiro:notAuthenticated> | 判斷當前用戶是否未認證通過 |
| <shiro:principal /> | 獲取當前用戶的相關信息,例如:用戶名 |
| <shiro:hasRole name="foo">...</shiro:hasRole> | 判斷當前用戶是否具有某種角色 |
| <shiro:lacksRole name="foo">...</shiro:lacksRole> | 判讀當前用戶是否缺少某種角色 |
| <shiro:hasAnyRoles name="foo, bar">...< /shiro:hasAnyRoles> | 判斷當前用戶是否具有任意一種角色(foo 或 bar) |
| <shiro:hasPermission name="foo">...</shiro:hasPermission> | 判斷當前用戶是否具有某種權限 |
| <shiro:lacksPermission name="foo">...</shiro:lacksPermission> | 判斷當前用戶是否缺少某種權限 |
如果 Shiro 再提供如下幾個 JSP 標簽那就完美了:
| 標簽 | 功能 |
| <shiro:hasAllRoles name="foo, bar">...< /shiro:hasAllRoles> | 判斷當前用戶是否具同時具有每種角色(foo 與 bar) |
| <shiro:hasAnyPermission name="foo, bar">...</shiro:hasAnyPermission> | 判斷當前用戶是否具有任意一種權限(foo 或 bar) |
| <shiro:hasAllPermission name="foo, bar">...</shiro:hasAllPermission> | 判斷當前用戶是否具同時具有每種權限(foo 與 bar) |
實現以上這些標簽并不是一件困難的事情,Shiro 最迷人的地方就是擴展了,有時候我們需要看源碼并“依葫蘆畫瓢”的,相信這一定是一件非常有趣的事情。
除了 JSP 標簽以外,Shiro 還為您提供了 Java 注解,只需將這些注解定義在您想要安全控制的方法上即可。
| 注解 | 功能 |
| RequiresGuest | 確保被標注的方法可被匿名用戶訪問 |
| RequiresUser | 確保被標注的方法只能被已登錄的用戶訪問(包括:已認證或已記住) |
| RequiresAuthentication | 確保被標注的方法只能被已認證的用戶訪問(不包括已記住) |
| RequiresRoles | 確保被標注的方法僅被指定角色的用戶訪問 |
| RequiresPermissions | 確保被標注的方法僅被指定權限的用戶訪問 |
注意,當您使用了?RequiresRoles 與?RequiresPermissions 注解,也就意味著您把代碼寫死了,這樣如果數據庫里的 Role 或 Permission 更改了,您的代碼也就無效了,這或許是 Shiro 的一點點不完美的地方吧,不過瑕不掩瑜了。
每次認證與授權都需要與數據庫打交道,這會對性能產生一定的開銷,關于這一點 Shiro 也為我們想到了,您只需在 [main] 片段中增加如下配置即可:
cacheManager?=?org.apache.shiro.cache.MemoryConstrainedCacheManager securityManager.cacheManager?=?$cacheManager此時 Shiro 就會在內存使用一個 Map 來緩存您的查詢結果,從而減少了數據庫的操作次數,提高了查詢方面的性能。Shiro 也提供了 EhCache 的擴展,為緩存提供了更加高大上的解決方案。
目前在數據庫里保存的是明文的密碼,這樣不太安全,如何將其加密呢?Shiro 同樣提供了非常優雅的解決方案,您只需在 [main] 片段下增加如下配置即可:
passwordMatcher?=?org.apache.shiro.authc.credential.PasswordMatcher jdbcRealm.credentialsMatcher?=?$passwordMatcher其實,Shiro 對密碼的加密與解密提供了非常強大的支持,這里僅僅是一種最簡單的情況。需要確保在創建密碼的時候使用對應的加密算法,Shiro 給我們提供了 PasswordService 接口,您可以這樣來使用:
PasswordService?passwordService?=?new?DefaultPasswordService(); String?encryptedPassword?=?passwordService.encryptPassword(plaintextPassword);只需將這個?encryptedPassword 存入數據庫即可。
其實關于 Shiro 的那點事兒還有很多,不可能通過一篇文章就能完全覆蓋,后續我會繼續與大家分享,包括:Shiro 架構分析,在 Shiro 中使用 EhCache,將 Shiro 與 Spring 集成,將 Shiro 與 CAS 集成,將 Shiro 與 Smart 集成,等等。
本文示例代碼:http://git.oschina.net/huangyong/shiro_demo
歡迎閱讀《Shiro 源碼分析》:http://my.oschina.net/huangyong/blog/209339
轉載于:https://my.oschina.net/huangyong/blog/208783
總結
以上是生活随笔為你收集整理的Shiro 那点事儿的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Playframework2 标签速记
- 下一篇: java笔记之字符串,gc