1. 程式人生 > >Apache Shiro 學習

Apache Shiro 學習

Apache Shiro 是一個框架,可用於身份驗證和授權。本文提供了幾個示例用來展示如何在 Java? 應用程式中使用 Shiro 並給出瞭如何在一個 Grails web 應用程式中使用它的概述。為了從本文中最大限度地受益,您應該習慣於建立 Java 應用程式並安裝瞭如下的幾個元件:

Java 1.6 JDK
Grails(用來執行這些 web 應用程式示例)

瞭解 Shiro

Shiro 是一個用 Java 語言實現的框架,通過一個簡單易用的 API 提供身份驗證和授權。使用 Shiro,您就能夠為您的應用程式提供安全性而又無需從頭編寫所有程式碼。

Shiro 是預構建的二進位制發行版。您可以下載 Shiro JAR 檔案或將各項放入到 Apache Maven 或 Apache Ivy 來自動安裝這些檔案。本例使用 Ivy 下載 Shiro JAR 檔案以及其他所需要的庫,指令碼很簡單,如 清單 1 所示。


清單 1. Apache Ivy 檔案和 Apache Ant 指令碼
    
<?xml version="1.0" encoding="UTF-8"?>
<ivy-module version="2.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:noNamespaceSchemaLocation="http://ant.apache.org/ivy/schemas/ivy.xsd">
    <info organisation="com.nathanagood.examples" module="shirotest" />
    <configurations>
        <conf name="dist" description="Dependency configuration for distribution." />
    </configurations>
    <dependencies>
        <dependency org="commons-logging" name="commons-logging"
            rev="1.1.1" conf="dist->default" />
        <dependency org="org.slf4j" name="slf4j-log4j12" rev="1.5.8"
            conf="dist->default" />
        <dependency org="org.apache.shiro" name="shiro-core" rev="1.0.0-incubating"
            conf="dist->default" />
        <dependency org="org.apache.shiro" name="shiro-web" rev="1.0.0-incubating"
            conf="dist->default" />
    </dependencies>
</ivy-module>

<project name="shiroTestApp" default="usage" basedir="."
    xmlns:ivy="antlib:org.apache.ivy.ant">
    <property name="project.lib" value="lib" />
    <path id="ivy.task.path">
        <fileset dir="${basedir}/ivy-lib">
            <include name="**/*.jar" />
        </fileset>
    </path>

    <target name="resolve">
        <taskdef resource="org/apache/ivy/ant/antlib.xml"
            uri="antlib:org.apache.ivy.ant" classpathref="ivy.task.path" />
        <ivy:resolve />
        <ivy:retrieve pattern="${project.lib}/[conf]/[artifact].[ext]" sync="true" />
    </target>

    <target name="usage">
        <echo message="Use --projecthelp to learn more about this project" />
    </target>
</project>
 

有關使用 Ivy 的更多資訊,參見 參考資料。如果不使用 Maven 或 Ivy,可用從本文 參考資料 部分提供的下載站點下載這些 Shiro JAR 檔案。

下載了這些庫後,只需將它們新增到 CLASSPATH。編寫 清單 2 內所示的簡單程式碼,它獲得對當前使用者的一個引用並報告說使用者未經身份驗證。(使用 Subject 類來代表此使用者。)


清單 2. ShiroTest Java 類
    
package com.nathanagood.examples.shirotest;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.Factory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class ShiroTest {

    private static Logger logger = LoggerFactory.getLogger(ShiroTest.class);

    public static void main(String[] args) {
        // Using the IniSecurityManagerFactory, which will use the an INI file
        // as the security file.
        Factory<org.apache.shiro.mgt.SecurityManager> factory =
            new IniSecurityManagerFactory("auth.ini");

        // Setting up the SecurityManager...
        org.apache.shiro.mgt.SecurityManager securityManager
            = factory.getInstance();
        SecurityUtils.setSecurityManager(securityManager);

        Subject user = SecurityUtils.getSubject();

        logger.info("User is authenticated:  " + user.isAuthenticated());
    }
}
 


添加了此程式碼後,建立一個名為 auth.ini 的檔案。此時,這個檔案是空白的;它的作用只是為了能夠在這裡執行這個示例來檢查程式碼是否正常工作。

建立檔案後,執行這個示例。應該會看到包含了一個 INFO 登入訊息的輸出,報告說使用者沒有登入。

SecurityUtils 物件是一個 singleton,這意味著不同的物件可以使用它來獲得對當前使用者的訪問。一旦成功地設定了這個 SecurityManager,就可以在應用程式不同部分呼叫 SecurityUtils.getSubject() 來獲得當前使用者的資訊。
簡單的身份驗證

至此,這個簡單的示例涵蓋的內容包括:啟動 Shiro SecurityManager、獲得當前使用者並記錄下使用者未經身份驗證。接下來的這個例子將會使用 UsernamePasswordToken 和儲存在 INI 檔案內的一個使用者記錄來展示如何通過使用者名稱和密碼進行身份驗證。

清單 3 內所示的 auth.ini 檔案現在包含了一個使用者記錄;這個記錄包含了使用者名稱和密碼。您可以在這個記錄內定義角色以及為應用程式提供授權。


清單 3. auth.ini 檔案
    
[users]
bjangles = dance
 


現在,建立 前一節 中介紹的這個 UsernamePasswordToken 物件,如 清單 4 所示。


清單 4. 使用 UsernamePasswordToken 類
    
// snipped... same as before.
public class ShiroTest {

    private static Logger logger = LoggerFactory.getLogger(ShiroTest.class);

    public static void main(String[] args) {
        // Using the IniSecurityManagerFactory, which will use the an INI file
        // as the security file.
        Factory<org.apache.shiro.mgt.SecurityManager> factory =
            new IniSecurityManagerFactory("auth.ini");

        // Setting up the SecurityManager...
        org.apache.shiro.mgt.SecurityManager securityManager = factory.getInstance();
        SecurityUtils.setSecurityManager(securityManager);

        Subject user = SecurityUtils.getSubject();

        logger.info("User is authenticated:  " + user.isAuthenticated());
       
        UsernamePasswordToken token = new UsernamePasswordToken("bjangles", "dance");
       
        user.login(token);
       
        logger.info("User is authenticated:  " + user.isAuthenticated());
    }
}
 


UsernamePasswordToken 物件由使用者名稱和密碼的組合例項化。隨後,令牌被傳遞至 Subject 類的 login() 方法。

再次執行這個示例。注意到登入訊息現在報告說此使用者已經身份驗證。

要確保程式碼正常工作且獲得的不是一個誤報,在程式碼內更改密碼或更改這個 INI 檔案並再次執行此示例。login() 方法現在會丟擲一個 IncorrectCredentialsException。在生產程式碼內這個異常應被明確捕獲以便應用程式在使用者提供了不正確的程式碼時能夠進行恰當的響應。

如果使用者不正確,login() 方法就會丟擲一個 UnknownAccountException。我們既要考慮如何處理這個異常,但又不應向用戶提供太多資訊。一種常見的做法是不要向用戶提示使用者名稱有效、只有密碼不正確。這是因為如果有人試圖通過猜測獲得訪問,那麼您絕對不會想要暗示此人他所猜測的使用者名稱是正確的。

用 LDAP 進行身份驗證

LDAP 是用來在 TCP/IP 上查詢目錄的一種協議。這些目錄可儲存有關使用者的任意數量的資訊,包括使用者 ID、聯絡資訊以及組成員等。LDAP 目錄對於公司的通訊簿很有用並被廣泛使用。

AD DS 是一種用於使用者和組管理的常見目錄,它支援 LDAP。 Shiro 不包含通用的 LDAP 安全域,但它卻包含了一個 ActiveDirectoryRealm 物件,允許針對 LDAP 進行使用者的身份驗證。本例使用了在 INI 檔案內配置的 ActiveDirectoryRealm 物件來驗證使用者的身份。雖然 AD DS 與 LDAP 不同,但本文中使用的 Shiro 的這個版本並沒有自帶通用的 LDAP 物件。

讓一個 LDAP 伺服器來測試本例要比編寫並執行示例本身需要更多工作。如果您不能訪問一個 AD DS 伺服器,那麼可以考慮下載並安裝 Apache Directory 來提供一個 LDAP 伺服器的樣例實現。Apache Directory 是用 Java 語言編寫的。同樣地,Apache Active Directory Studio 是一個 Eclipse 外掛,可用來瀏覽 LDAP 資料。它還具有一些樣例資料,為您提供了一種快捷方式來針對已知值編寫程式碼,而又無需疑惑您遇到的問題是程式碼問題還是資料問題。

清單 5 顯示了用來對儲存在 Apache Directory 內的一個使用者進行身份驗證所需的程式碼。


清單 5. 使用 LDAP 進行身份驗證
    
// snipped...
public class ShiroLDAPTest {

    private static Logger logger = LoggerFactory.getLogger(ShiroLDAPTest.class);

    /**
     * @param args
     */
    public static void main(String[] args) {

        // Using the IniSecurityManagerFactory, which will use the an INI file
        // as the security file.
        Factory<org.apache.shiro.mgt.SecurityManager> factory =
            new IniSecurityManagerFactory("actived.ini");

        // Setting up the SecurityManager...
        org.apache.shiro.mgt.SecurityManager securityManager = factory.getInstance();
        SecurityUtils.setSecurityManager(securityManager);

        Subject user = SecurityUtils.getSubject();

        logger.info("User is authenticated:  " + user.isAuthenticated());

        UsernamePasswordToken token =
        new UsernamePasswordToken(
            "cn=Cornelius Buckley,ou=people,o=sevenSeas", "argh");

        user.login(token);

        logger.info("User is authenticated:  " + user.isAuthenticated());
    }
}
 


除了 INI 檔名和使用者名稱及密碼之外,程式碼與之前用 INI 檔案內的記錄進行身份驗證的程式碼相同。這種類似性的出現是因為您可以使用 INI 檔案來配置 Shiro。用來設定 Shiro 針對 Apache Directory 進行身份驗證的這些 INI 記錄如 清單 6 所示。


清單 6. actived.ini 檔案
    
[main]
activeDirectoryRealm = org.apache.shiro.realm.activedirectory.ActiveDirectoryRealm
activeDirectoryRealm.systemUsername = uid=admin,ou=system
activeDirectoryRealm.systemPassword = secret
activeDirectoryRealm.searchBase = o=sevenSeas,ou=people
activeDirectoryRealm.url = ldap://localhost:10389
 


注意: 我使用 Apache Directory Studio 來將使用者的密碼更改為一個能放入測試程式碼以確保它工作的值。


--------------------------------------------------------------------------------
回頁首
執行一個 Grails web 應用程式

您可以使用兩種基本技巧將 Shiro 應用到 web 應用程式。首先,您可以使用這個 API 來將這裡所展示的的程式碼併入一個基礎 servlet。其次,您可以使用 Shiro 自帶的 HTTP 過濾器。本例展示了第二種技巧,因為使用過濾器充分利用了內建 web 應用伺服器技術以及來自 Shiro 專案的預先編寫好的程式碼。

本例顯示瞭如何使用 Grails 內的這些過濾器。Grails 是一個專案,旨在讓您通過使用一種慣例優先原則(convention-over-configuration)的方式儘快地編寫 Groovy web 應用程式。有關 Grails 的更多資訊,請參見 參考資料。

對於 Shiro 過濾器,您通常會手動地向 web.xml 檔案新增必需的過濾器項。但是,Grails 會在您每次啟動應用程式時生成 web.xml 檔案,因此不必手動修改 web.xml。

幸運的是,Grails 提供了外掛,可整合到 web.xml 生成過程並會讓您也可以參與在 web.xml 檔案內編寫這些項。如今,Grails 的可用外掛很多,包括面向 Shiro 的這個外掛。建議嘗試使用這個 Shiro Grails 外掛,它提供了幾個新的指令碼,執行這些指令碼可以建立不同的域和控制器。

或者,如果您更願意自己新增這些項並進行配置,您也可以編寫您自己的外掛。對於 Grails,編寫一個新的外掛很容易。要建立能將必需的 Shiro 過濾器項新增到 web.xml 檔案的 Grails 外掛,可以使用如下命令:

> grails create-plugin ShiroWebXml
 


建立了這個外掛專案後,編輯 ShiroWebXmlPlugin.groovy 檔案,並新增如 清單 7 所示的程式碼。


清單 7. 示例外掛
    
class ShiroWebXmlPlugin {
  
   // snipped plugin details...

   def doWithWebDescriptor = { xml ->

       def filterElement = xml.'filter'
       def lastFilter = filterElement[filterElement.size() - 1]

       lastFilter + {
           'filter' {
               'filter-name'("ShiroFilter")
               'filter-class'("org.apache.shiro.web.servlet.IniShiroFilter")
               'init-param' {
                   'param-name'("config")
                   'param-value'("\n#config")
               }
           }
       }

       def filterMappingElement = xml.'filter-mapping'
       def lastFilterMappingElement =
           filterMappingElement[filterMappingElement.size() - 1]

       lastFilterMappingElement + {
           'filter-mapping' {
               'filter-name'("ShiroFilter")
               'url-pattern'("/*")
               }
           }
       }
}
 


當 Grails 執行這個 web.xml 檔案時,此程式碼就會執行。

在啟動這個外掛應用程式測試它之前,先將用 Ivy 下載的 Shiro JAR 檔案複製到此外掛的 lib 資料夾。有了 JAR 檔案後,用以下命令測試此外掛是否能正常工作:

grails run-app
 


如果此外掛應用程式成功啟動,您可以將它打包以便用在一個示例專案中。要打包這個外掛,使用如下命令:

grails package-plugin
 


可以用如下命令安裝這個新的 ShiroWebXmlPlugin:

cd myapp
grails install-plugin /path/to/shiro-web-xml-1.0.zip
 


--------------------------------------------------------------------------------
回頁首
故障排除

如果出現了 UnavailableSecurityManagerException,那麼可能是 SecurityManager 未被正確設定。在呼叫 getSubject() 方法之前,請確保它設在 SecurityUtils 物件上。

連線到 LDAP 伺服器可能會有些困難。如果得到一個 javax.naming.CommunicationException,那麼請檢查此 LDAP 伺服器的主機名和埠。即便您沒有使用 Apache Directory,Apache Directory Studio(可單獨安裝)也可以幫助您排除連線故障以及名稱問題。

如果您沒有使用 Ant Ivy 指令碼來初始化您的環境,那麼有可能會得到丟失類的錯誤。即便沒有 Apache Commons Logging library (commons-logging),這個 INI 檔案示例仍可執行,但是執行這個 LDAP 示例會導致一個異常。這時,可以使用 Apace Commons BeanUtils 來解析這個 INI 檔案並設定物件上的值。