天天看點

Apache Shiro第1部分–基礎

Apache Shiro (最初稱為JSecurity)是Java安全架構。 它被接受并于2010年成為Apache頂級項目。它的目标是功能強大且易于使用。

該項目正在積極開發中,使用者和開發人員的郵件清單均處于活動狀态。 最重要的區域記錄在其網頁上。 但是,它在文檔上有很多空白。 僅從文檔中就不可能學會使用大多數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"  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 permission
    if (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