Apache Shiro在Web中的應用
概述
Shiro 是一個 Apache Incubator 項目,旨在簡化身份驗證和授權。本文只是我對shiro的初步認識,有不對的請大蝦指正,謝謝!
基本概念
在對系統進行安全保障時,有兩個安全性元素非常重要:身份驗證和授權。雖然這兩個術語代表的是不同的含義,但出于它們在應用程序安全性方面各自的角色考慮,它們有時會被交換使用。
身份驗證 (我們平常接觸較多的就是登錄)指的是驗證用戶的身份。在驗證用戶身份時,需要確認用戶的身份的確如他們所聲稱的那樣。在大多數應用程序中,身份驗證是通 過用戶名和密碼的組合完成的。只要用戶選擇了他人很難猜到的密碼,那么用戶名和密碼的組合通常就足以確立身份。但是,還有其他的身份驗證方式可用,比如指 紋、證書和生成鍵。
一旦身份驗證過程成功地建立起身份,授權 (我理解的就是權限管理與控制)就會接管以便進行訪問的限制或允許。所以,有這樣的可能性:用戶雖然通過了身份驗證可以登錄到一個系統,但是未經過授權,不準做任何事情。還有一種可能是用戶雖然具有了某種程度的授權,卻并未經過身份驗證。
在為應用程序規劃安全性模型時,必須處理好這兩個元素以確保系統具有足夠的安全性。身份驗證是應用程序常見的問題(特別是在只有用戶和密碼組合的情 況下),所以讓框架來處理這項工作是一個很好的做法。合理的框架可提供經過測試和維護的優勢,讓您可以集中精力處理業務問題,而不是解決其解決方案已經實 現的問題。
Apache Shiro 提供了一個可用的安全性框架,各種客戶機都可將這個框架應用于它們的應用程序。本文中的這些例子旨在介紹 Shiro 并著重展示對用戶進行身份驗證的基本任務。
初識shiro
Shiro 是一個用 Java 語言實現的框架,通過一個簡單易用的 API 提供身份驗證和授權。使用 Shiro,您就能夠為您的應用程序提供安全性而又無需從頭編寫所有代碼。
由于 Shiro 提供具有諸多不同數據源的身份驗證,以及 Enterprise Session Management,所以是實現單點登錄(SSO)的理想之選 — 大型企業內的一個理想特性,因為在大型企業內,用戶需要在一天內經常登錄到并使用不同系統。這些數據源包括 JDBC、LDAP、 Kerberos 和 Microsoft? Active Directory? Directory Services (AD DS)。
Shiro 的 Session
對象允許無需 HttpSession
即可使用一個用戶會話。通過使用一個通用的Session
對象,即便該代碼沒有在一個 Web 應用程序中運行,仍可以使用相同的代碼。沒有對應用服務器或 Web
應用服務器會話管理的依賴,您甚至可以在命令行環境中使用 Shiro。換言之,使用 Shiro 的 API 編寫的代碼讓您可以構建連接到 LDAP
服務器的命令行應用程序并且與 web 應用程序內用來訪問
LDAP 服務器的代碼相同。
Shiro在Web中的應用
將shiro整合到Web中最簡單的方式就是在web.xml文件中配置一個Servlet ContextListener的監聽器和Filter過濾器。實例代碼如下:
<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>以上的配置是假定shiro.ini的配置文件是放在以下兩個位置下的(哪個先找到就用哪個):
1. /WEB-INF/shiro.ini
2. classpath的根目錄
以上的代碼做了如下的工作:
- EnvironmentLoaderListener初始化一個Shiro用的WebEnvironment實例(這個實例包括Shiro運行要用的任何東西,包含SecurityManager)并且讓ServletContext可以訪問它。可以在任何時刻調用WebUtils.getRequiredWebEnvironment(servletContext)語句獲取WebEnvironment。
- ShiroFilter將用WebEnvironment為所有的經過濾的request執行所有的安全保障操作。
- 最后,filter-mapping用以確保所有的請求通過ShiroFilter過濾。
注:建議將ShiroFilter filter-mapping聲明在其他的filter-mapping聲明之前,以確保Shiro也作用于那些filter。
Web INI 配置
默認情況下,初始化Shiro時,Shiro會自動按順序搜索/WEB-INF/shiro.ini和classpath:shiro.ini位置下的.ini配置,然后用最先找到的那個。
如果配置較少,可以不用另外的.ini文件,而將INI配置在web.xml中。下面的配置就是將shiro的配置配置在web.xml中:
<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> <init-param> <param-name>config</param-name> <param-value> [users] # format: username = password, role1, role2, ..., roleN User1 = 123456 Manager1 = 123456, Manager [filters] [urls] /* = authc </param-value> </init-param> </filter> <filter-mapping> <filter-name>ShiroFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>Web INI配置標準的有[main],[users],[roles],[urls]部分(具體如何配置可查看官方文檔),下面是一個連接MySql數據庫的配置示例:
[main] ds = com.mysql.jdbc.jdbc2.optional.MysqlDataSource ds.serverName = 127.0.0.1 ds.user = root ds.password = root ds.databaseName = test ds.url = jdbc:mysql://127.0.0.1:3306/test jdbcRealm = org.apache.shiro.realm.jdbc.JdbcRealm jdbcRealm.permissionsLookupEnabled = true jdbcRealm.authenticationQuery = SELECT password FROM ho_user WHERE name = ? jdbcRealm.userRolesQuery = SELECT role FROM ho_user WHERE name = ? jdbcRealm.permissionsQuery = SELECT permission FROM ho_user WHERE name = ? jdbcRealm.dataSource = $ds authc.loginUrl = /common/login.jsp perms.unauthorizedUrl = /common/login.jsp roles.unauthorizedUrl = /common/login.jsp [urls] /action/* = authc /admin/**=authc,perms[high] /system/**=authc,perms[high]
說明:
- jdbcRealm.dataSource = $ds指定jdbcRealm的數據源是前面配置的數據庫ds。
- jdbcRealm.authenticationQuery,jdbcRealm.userRolesQuery,jdbcRealm.permissionsQuery 配置行是jdbc的與查詢語句,它告訴shiro從何處獲取授權的配置。它們都是用查詢后的記錄的第一個字段進行驗證 的,authenticationQuery是查詢后取第一條記錄的第一個字段(這里是password字段)進行驗證;userRolesQuery是用查詢后的第一個字段(這里是role字段)作為所屬的角色的(可以有多條記錄即多個角色);permissionsQuery同userRolesQuery一樣查詢后可以有多條記錄,但也是取第一個字段作為權限字符串。
- authc.loginUrl = /common/login.jsp是指定如果驗證失敗則頁面跳轉到/common/login.jsp下,perms.unauthorizedUrl = /common/login.jsproles.unauthorizedUrl = /common/login.jsp同理。
- [urls] 里的配置就是對特定的url進行授權。/admin/**=authc,perms[high],是對匹配/admin/**的url配置權限,進入此 url須通過authc和perms[high]驗證(authc和perms都是系統內置的過濾器。authc告訴shiro,進入此url,必須是已 驗證的登錄用戶;perms[high] 是權限限定符,perms是內置的過濾器,high是通過jdbcRealm.permissionsQuery查詢出來的權限字符串,只有用戶擁有該字 符串的權限,才能獲得訪問授權。如果針對角色授權,可以是/admin/**=authc,roles[admin])。
附內置過濾器:
過濾器名 |
類 |
anon |
|
authc |
|
authcBasic |
org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter |
logout |
|
noSessionCreation |
|
perms |
org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter |
port |
|
rest |
org.apache.shiro.web.filter.authz.HttpMethodPermissionFilter |
roles |
|
ssl |
|
user |
Shiro應用
配置好Web INI后,就能將其應用在Web中了,我們可以看看有了Shiro后,安全驗證時多么的簡便。
假設有一個LoginAction,只需三句語句就能實現驗證
public String execute() throws Exception { try { AuthenticationToken token = new UsernamePasswordToken(username,password);//username和password是從表單提交過來的 Subject currentUser = SecurityUtils.getSubject(); currentUser.login(token); return SUCCESS; }catch (Exeception e){ Return ERROR; } }只需兩句話就能實現LogoutAction的動作
public String execute() throws Exception { Subject currentUser = SecurityUtils.getSubject(); currentUser.logout(); return SUCCESS; }
注:SecurityUtils
對象是一個 singleton,這意味著不同的對象可以使用它來獲得對當前用戶的訪問。一旦成功地設置了這個SecurityManager
,就可以在應用程序不同部分調用SecurityUtils.getSubject()
來獲得當前用戶的信息。
補充說明:
上述代碼中用到了Subject和UsernamePasswordToken
。這里增加一點
Shiro
的概念。
- Subject 是安全領域術語,除了代表人,它還可以是應用。在單應用中,可將其視為 User 的同義詞。
- Principal 是 Subject 的標識,一般情況下是唯一標識,比如用戶名。
- 用戶令牌。在 Shiro 術語中,令牌 指的是一個鍵,可用它登錄到一個系統。最基本和常用的令牌是
UsernamePasswordToken
,用以指定用戶的用戶名和密碼。UsernamePasswordToken
類實現了AuthenticationToken
接口,它提供了一種獲得憑證和用戶的主體(帳戶身份)的方式。UsernamePasswordToken
適用于大多數應用程序,并且您還可以在需要的時候擴展AuthenticationToken
接口來將您自己獲得憑證的方式包括進來。例如,可以擴展這個接口來提供您應用程序用來驗證用戶身份的一個關鍵文件的內容。
更多的認證用法,請參考官方文檔。
更多的授權用法,請參考官方文檔。
JSP/GSP標簽庫
Shiro提供了JSP/GSP的標簽庫,這使得我們很容易能夠在JSP,JSTL或GSP頁面的控制基于Subject的狀態的輸出。
標簽庫的描述符(Tag Library Descriptor (TLD))在shiro-web.jar 的META-INF/shiro.tld下定義。要引用這些標簽,只需在JSP頁面的頭部添加下面的語句:
<%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>如定義一個pag_header.jsp如下:
<%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %> <div id="page_header"> <div id="page_heading">Hello World</div> <div id="page_headerlinks"> <ul> <li> <shiro:guest> <a href="/common/login.jsp">Login Now</a> </shiro:guest> <shiro:user> Welcome, <shiro:principal /> </shiro:user> </li> <script> var username = '<shiro:principal/>'; </script> <li> <shiro:guest> <a href="/common/register.jsp">Register Now</a> </shiro:guest> <shiro:user> <a href="Logout.action">Log out</a> </shiro:user> </li> </ul> </div> <div class="clearthis"> </div> </div>
說明:guest標簽只用于顯示當前Subject被認為是“guest”的Subject內容。通常用于顯示沒有登錄的內容。user標簽只用于 顯示當前Subject被認為是“user”的Subject內容。通常用于顯示已經登錄的內容。一般情況下,兩者是互斥的,只顯其一。
更多標簽用法,參見官方文檔。
參考文獻
Apache Shiro官方網站, Apache;
Apache Shiro 簡介,Nathan A. Good;
轉載請注明出處!!!