該項目正在積極開發中,用戶和開發人員的郵件列表均處于活動狀態。 最重要的區域記錄在其網頁上。 但是,它在文檔上有很多空白。 僅從文檔中就不可能學會使用大多數Shiro功能。 幸運的是,該代碼的注釋很好,在我嘗試過的地方也很容易閱讀。
Shiro的主要功能是:
- 驗證,
- 授權,
- 密碼學
- 會話管理。
在本文中,我們嘗試演示Shiro的各種功能。 我們從簡單的不安全Web應用程序開始,然后向其中添加安全功能。 所有代碼均在Github上的SimpleShiroSecuredApplication項目中可用。
不安全的應用程序
不安全的應用程序代碼位于unsecured_application分支中。 應用程序代表一個虛構公司的內部系統。 該公司有四個部門:
- 管理員,
- 修理工
- 科學家們,
- 銷售。
每個部門都有自己的頁面。 每個頁面都包含用戶用來完成其工作的按鈕。 當用戶按下按鈕時,工作就完成了。 例如,任何維修人員都可以轉到維修人員頁面,然后按“維修冰箱”按鈕。 該按鈕將修復冰箱并顯示成功消息。
每個用戶都有自己的帳戶頁面。 帳戶頁面包含用戶的私人數據。 由于不安全的應用程序尚無用戶,因此帳戶頁面不執行任何操作。 此外,還有一個頁面包含所有應用程序功能。 任何人都可以做的所有事情都可以在此頁面上完成。
任何人都可以做任何工作并查看所有頁面 。示例應用程序在測試類RunWaitTest中運行。 以這種方式使用單元測試不是最佳實踐,但現在并不重要。 如果您運行該類,則該應用程序將位于http:// localhost:9180 / simpleshirosecuredapplication / url中。
添加身份驗證
首先,我們必須驗證用戶的身份。 最簡單,最標準的身份驗證是通過用戶名和密碼來完成的。 用戶填寫其用戶名和密碼,然后系統驗證提供的值是否與某個用戶帳戶匹配。
對于最簡單的應用程序,將用戶名和密碼存儲在純文本文件中就足夠了。 在更現實的情況下,用戶名和密碼存儲在持久性存儲中,或者通過其他系統(例如ldap或活動目錄)進行驗證。 Shiro支持所有提到的身份驗證方法。 如果開箱即用的身份驗證功能不足,則可以使用自己的驗證實現來擴展框架。
在本章中,我們將基于用戶名和密碼的身份驗證添加到應用程序中。 用戶名和密碼存儲在靜態純文本Shiro ini文件中。
新要求:可以登錄和注銷用戶。 該應用程序僅可用于登錄用戶。 成功登錄會將用戶重定向到他自己的帳戶頁面。 所有登錄用戶仍然可以訪問所有應用程序功能和頁面。
所需步驟:
- 添加Apache Shiro,
- 創建登錄頁面,
- 配置用戶和密碼,
- 創建注銷頁面。
添加Apache Shiro
Shiro通過Servlet過濾器集成到Web應用程序中。 過濾器在servlet之前攔截請求和響應,并執行所有必要的任務(例如,標識當前登錄的用戶,將登錄的用戶附加到當前線程等)。 默認的Shiro篩選器提供基本的安全功能,例如:
- 強制用戶登錄,
- 執行ssl,
- 檢查頁面訪問權限。
如果您想了解有關默認Shiro過濾器的更多信息,那么最好的起點是DefaultFilter枚舉。 它列出了所有默認可用的Shiro過濾器。 如果這些不足以滿足您的需求,則可以創建自定義的。
我們將使用高度可配置的IniShiroFilter 。 它從ini文件讀取Shiro配置并初始化安全框架。 它不執行任何安全檢查。 權限檢查,用戶登錄,協議檢查等都委托給默認或自定義過濾器。 IniShiroFilter僅初始化它們。
文檔和javadoc中都介紹了Ini配置。 Ini文件配置包含四個部分:
- [main]部分包含Shiro初始化。 過濾器和自定義對象在此處配置。
- [用戶]部分定義用戶,密碼和角色。
- [角色]部分將角色與權限相關聯。
- [urls]部分指定對應用程序頁面(url)的訪問權限。 通過將默認或自定義過濾器綁定到url來完成此操作。
將Apache Shiro依賴項添加到pom.xml:
<properties><shiro.version>1.1.0</shiro.version>
</properties>
<dependencies><dependency><groupid>org.apache.shiro</groupid><artifactid>shiro-core</artifactid><version>${shiro.version}</version></dependency><dependency><groupid>org.apache.shiro</groupid><artifactid>shiro-web</artifactid><version>${shiro.version}</version></dependency>
</dependencies>
創建Shiro.ini文件并將其放在類路徑中。 將web.xml配置為在每個請求之前調用IniShiroFilter:
<filter><filter-name>ShiroFilter</filter-name><filter-class>org.apache.shiro.web.servlet.IniShiroFilter</filter-class><init-param><param-name>configPath</param-name><param-value>classpath:Shiro.ini</param-value></init-param>
</filter><filter-mapping><filter-name>ShiroFilter</filter-name><url-pattern>/*</url-pattern>
</filter-mapping>
創建登錄頁面
登錄頁面是帶有提交按鈕,用戶名和密碼字段的簡單html頁面。 登錄功能默認為Shiro authc過濾器處理。 Authc過濾器僅允許登錄用戶訪問url。 如果用戶未登錄,過濾器會將其重定向到登錄頁面。
登錄頁面上的表單名稱必須為“ loginform”,其提交方法必須為“ post”。 創建login.jsp頁面:
<form name="loginform" action="" method="post">
<table align="left" border="0" cellspacing="0" cellpadding="3"><tr><td>Username:</td><td><input type="text" name="user" maxlength="30"></td></tr><tr><td>Password:</td><td><input type="password" name="pass" maxlength="30"></td></tr><tr><td colspan="2" align="left"><input type="checkbox" name="remember"><font size="2">Remember Me</font></td></tr><tr><td colspan="2" align="right"><input type="submit" name="submit" value="Login"></td></tr>
</table>
</form>
為所有應用程序頁面啟用authc過濾器:
[main]
# specify login page
authc.loginUrl = /simpleshirosecuredapplication/account/login.jsp# name of request parameter with username; if not present filter assumes 'username'
authc.usernameParam = user
# name of request parameter with password; if not present filter assumes 'password'
authc.passwordParam = pass
# does the user wish to be remembered?; if not present filter assumes 'rememberMe'
authc.rememberMeParam = remember# redirect after successful login
authc.successUrl = /simpleshirosecuredapplication/account/personalaccountpage.jsp[urls]
# enable authc filter for all application pages
/simpleshirosecuredapplication/**=authc
更新: Shiro自動執行上下文相關的路徑匹配。 由于SimpleShiroSecuredApplication沒有設置上下文路徑,因此Shiro.ini中的完整路徑是必需的。 但是,如果應用程序上下文路徑為/ simpleshirosecuredapplication,則路徑可能是相對的:例如,簡單的/ ** = authc或/account/personalaccountpage.jsp。
由于通過網絡發送未加密的用戶名和密碼是不安全的,因此我們應強制使用ssl登錄。 SSL過濾器正是這樣做的。 它具有一個可選參數:ssl端口號。 如果省略port參數,它將使用默認的ssl端口443。
在Shiro中配置ssl之前,我們必須在Web服務器上啟用它。 具體操作取決于Web服務器。 我們展示了如何在Jetty中啟用它。 首先,使用自簽名證書創建密鑰庫:
keytool -genkey -keyalg RSA -alias jetty -keystore keystore -storepass secret -validity 360 -keysize 2048
回答所有問題,最后按Enter鍵,以便密鑰庫密碼和密鑰密碼相同。
其次,將密鑰庫添加到項目中,并將Jetty配置為使用ssl。 Java代碼在AbstractContainerTest類中可用。
現在,可以在Shiro.ini中配置ssl過濾器:
[urls]
# force ssl for login page
/simpleshirosecuredapplication/account/login.jsp=ssl[8443],authc
# enable authc filter for the all application pages; as Shiro reads urls from up to down, must be last
/simpleshirosecuredapplication/**=authc
配置用戶和密碼
現在,SimpleShiroSecuredApplication僅適用于登錄用戶。 現在,我們需要添加一些用戶,以便人們可以登錄。配置在Shiro.ini文件的[用戶]部分中完成。 部分條目的格式為:
username = password, roleName1, roleName2, ..., roleNameN
以下部分創建七個用戶,所有用戶都具有相同的密碼“ heslo”:
[users]
administrator=heslo,Administrator
friendlyrepairmen=heslo,repairmen
unfriendlyrepairmen=heslo,repairmen
mathematician=heslo,scientist
physicien=heslo,scientist
productsales=heslo,sales
servicessales=heslo,sales
現在可以登錄到應用程序。 但是,如果用戶犯了錯誤,則不會顯示任何合理的錯誤消息。 此外,密碼存儲在純文本文件中。
錯誤處理
如果用戶在登錄時出錯,則Shiro會將其重定向回登錄頁面。 該頁面看起來與以前完全相同,這可能會使用戶感到困惑。
新要求:每次嘗試登錄失敗后,顯示錯誤消息。
每當發生身份驗證錯誤時,都會引發異常。 默認情況下,表單身份驗證過濾器會捕獲異常并將其類名稱存儲在request參數中。 由于我們希望自定義發送到頁面的數據,因此我們必須擴展FormAuthenticationFilter并重寫setFailureAttribute方法:
@Override
protected void setFailureAttribute(ServletRequest request, AuthenticationException ae) {String message = ae.getMessage();request.setAttribute(getFailureKeyAttribute(), message);
}
用VerboseFormAuthenticationFilter替換表單授權過濾器,并將其配置為使用'simpleShiroApplicationLoginFailure'請求屬性來保存錯誤信息:
[main]
# replace form authentication filter with verbose filter
authc = org.meri.simpleshirosecuredapplication.servlet.VerboseFormAuthenticationFilter
# request parameter with login error information; if not present filter assumes 'shiroLoginFailure'
authc.failureKeyAttribute=simpleShiroApplicationLoginFailure
在login.jsp頁面中顯示錯誤:
<% String errorDescription = (String) request.getAttribute("simpleShiroApplicationLoginFailure");if (errorDescription!=null) {
%>
Login attempt was unsuccessful: <%=errorDescription%>
<% }
%>
當心:真實的應用程序不應顯示太多的登錄錯誤信息。 消息“嘗試登錄失敗。” 沒有更多信息通常就足夠了。
散列密碼
當前應用程序版本的所有密碼均以純文本格式存儲。 最好只存儲和比較密碼哈希。
負責身份驗證的對象稱為領域 。 默認情況下,Shiro使用帶可插入密碼匹配器的IniRealm來比較密碼。 我們將用ini的SHA-256哈希替換ini中的密碼,并將IniRealm配置為使用SHA-256哈希匹配器。
生成密碼的SHA-256哈希:
import org.apache.shiro.crypto.hash.Sha256Hash;public static void main(String[] args) {Sha256Hash sha256Hash = new Sha256Hash("heslo");System.out.println(sha256Hash.toHex());
}
將Shiro配置為比較密碼哈希而不是密碼本身:
[main]
# define matcher matching hashes instead of passwords
sha256Matcher = org.apache.shiro.authc.credential.HashedCredentialsMatcher
sha256Matcher.hashAlgorithmName=SHA-256# enable matcher in iniRealm (object responsible for authentication)
iniRealm.credentialsMatcher = $sha256Matcher
用密碼哈希替換用戶密碼:
[users]
administrator=56b1db8133d9eb398aabd376f07bf8ab5fc584ea0b8bd6a1770200cb613ca005, Administrator
friendlyrepairmen=56b1db8133d9eb398aabd376f07bf8ab5fc584ea0b8bd6a1770200cb613ca005, repairmen
unfriendlyrepairmen=56b1db8133d9eb398aabd376f07bf8ab5fc584ea0b8bd6a1770200cb613ca005, repairmen
mathematician=56b1db8133d9eb398aabd376f07bf8ab5fc584ea0b8bd6a1770200cb613ca005, scientist
physicien=56b1db8133d9eb398aabd376f07bf8ab5fc584ea0b8bd6a1770200cb613ca005, scientist
productsales=56b1db8133d9eb398aabd376f07bf8ab5fc584ea0b8bd6a1770200cb613ca005, sales
servicessales=56b1db8133d9eb398aabd376f07bf8ab5fc584ea0b8bd6a1770200cb613ca005, sales
注意:無法在ini配置中指定salt。
創建注銷頁面
具有登錄功能的任何應用程序也應具有注銷功能。 使用Shiro注銷當前用戶很容易,請使用以下命令:
//acquire currently logged user and log him out
SecurityUtils.getSubject().logout();
注銷頁面如下所示:
<%@ page import="org.apache.shiro.SecurityUtils" %>
<% SecurityUtils.getSubject().logout();%>
You have succesfully logged out.
添加授權
我們通過向應用程序添加授權來結束第一部分。 我們從限制用戶訪問頁面開始。 任何用戶都不能看到其他部門的頁面。 由于用戶仍然能夠使用“所有應用程序功能”頁面或在瀏覽器中編輯URL來執行任何操作,因此這僅為項目提供了部分安全性。 我們將其稱為頁面級授權。
然后,我們限制了用戶自己執行操作的能力。 即使用戶打開“所有應用程序功能”頁面或在瀏覽器中編輯URL,也將只允許他執行其部門特定的功能。 我們將其稱為功能級別授權。
新要求:用戶無法查看不屬于他的部門的頁面。 用戶只能執行其部門職能。 以前的規則唯一的例外是管理員,管理員可以執行管理和修復功能。
頁面授權
頁面級授權是通過角色過濾器完成的。 過濾器的參數部分可以包含任意數量的角色。 登錄的用戶只有擁有所有提供的角色,才能訪問頁面。
像往常一樣,在Shiro.ini文件中配置角色過濾器:
[urls]
# force ssl for login page
/simpleshirosecuredapplication/account/login.jsp=ssl[8443],authc# only users with some roles are allowed to use role-specific pages
/simpleshirosecuredapplication/repairmen/**=authc, roles[repairman]
/simpleshirosecuredapplication/sales/**=authc, roles[sales]
/simpleshirosecuredapplication/scientists/**=authc, roles[scientist]
/simpleshirosecuredapplication/adminarea/**=authc, roles[Administrator]# enable authc filter for the all application pages; as Shiro reads urls from up to down, must be last
/simpleshirosecuredapplication/**=authc
測試安全性是否有效:以任何銷售用戶身份登錄,單擊“主頁”,然后單擊“維修人員頁面”鏈接。 您會看到一個難看的錯誤。
我們完成頁面授權并將錯誤替換為重定向到錯誤頁面。 默認的Shiro過濾器具有屬性validateUrl。 如果發生未經授權的訪問,過濾器會將用戶重定向到指定的url。
[main]
# redirect to an error page if user does not have access rights
roles.unauthorizedUrl = /simpleshirosecuredapplication/account/accessdenied.jsp
accessdenied.jsp:
<body>
Sorry, you do not have access rights to that area.
</body>
功能授權
現在所有部門頁面均已安全。 但是,任何用戶仍可以在“所有應用程序功能”頁面上執行任何功能。 此外,任何登錄的用戶都可以編輯url,從而可以執行任何操作。 例如,如果您以銷售人員身份登錄并將https:// localhost:8443 / simpleshirosecuredapplication / masterservlet?action = MANAGE_REPAIRMEN放入url中,則該應用程序也將執行管理修復功能(然后將引發空指針異常,但存在安全漏洞)已經完成了)。
我們為每個功能分配唯一的權限 。 它們分為幾組:
- 所有權限都在“功能”組中,
- 所有管理權限都在“管理”組中,
- 所有修復權限都在“修復”組中,
- 所有銷售權限都在“銷售”組中,
- 所有科學許可都在“科學”組中。
Shiro支持表示為字符串的多級權限。 級別用符號“:”分隔。 例如,“功能:管理:修理工”具有三個級別:“功能”,“管理”和“修理工”。 多級權限允許輕松進行權限分組。 例如,科學組屬于功能組,并且包含三個權限:
- 職能:科學:研究,
- 功能:科學:寫作文章,
- 職能:科學:準備談話。
操作將在完成記錄的用戶權限之前對其進行驗證:
public String doIt() {String neededPermission = getNeededPermission();// acquire logged user and check permissionif (SecurityUtils.getSubject().isPermitted(neededPermission))return "Function " + getName() + " run succesfully.";throw new UnauthorizedException("Logged user does not have " + neededPermission + " permission");
}
注意:實現相同目標的另一種方法是通過注釋。
PerformFunctionAndGoBackServlet servlet捕獲授權異常并將其轉換為錯誤消息:
private String performAction(String actionName) {try {Actions action = findAction(actionName);String result = action == null ? null : action.doIt();log.debug("Performed function with result: " + result);return result;} catch (ShiroException ex) {log.debug("Function failed with " + ex.getMessage() + " message.");return "Error: " + ex.getMessage();}
}
最后,我們需要在Shiro.ini文件中配置角色的權限。 Shiro支持通配符以獲得多級權限。 因此,我們不必分別指定每個部門的許可:
[roles]
# members of departments should be able to perform all departmental functions
sales=functions:sale:*
scientist=functions:science:*
repairman=functions:repair:*# administrators are able to do all management functions and repair functions
Administrator=functions:manage:*,functions:repair:*
您現在可以在“所有應用程序功能”頁面上嘗試功能。 如果登錄的用戶沒有所需的權限,則會在頁面頂部顯示錯誤消息。 此外,如果您以銷售人員身份登錄并嘗試入侵https:// localhost:8443 / simpleshirosecuredapplication / masterservlet?action = MANAGE_REPAIRMEN,則會在控制臺中看到錯誤消息(而不是成功消息)。
結束
最終的應用程序可以在Github上的“ static_authentication_and_authorization”分支中找到。
在第二部分中,我們將創建自定義領域,并將用戶,密碼,角色和權限從ini文件移動到數據庫。 第三部分專門介紹Apache Shiro加密軟件包。
參考: Apache Shiro第1部分– JCG合作伙伴 Maria Jurcovicova的基礎知識,來自This is Stuff博客。
翻譯自: https://www.javacodegeeks.com/2012/05/apache-shiro-part-1-basics.html