Shiro Web應用程式


[TOC]

本文章是使用Apache Shiro保護Web應用程式的介紹性分步教學。Shiro的介紹性知識,並且假設您至少熟悉以下介紹性文章:

  • Shiro快速入門教學

這個分步教學應該需要大概45分鐘到1小時完成。 當你按照本文中給出的步驟一步步完成後,您應該會有一個很好的了解:Shiro是如何在一個Web應用程式中工作的。

概述

雖然Apache Shiro的核心設計目標允許它用於保護任何基於JVM的應用程式(如命令列應用程式,伺服器守護程式,Web應用程式等),但本指南將集中介紹最常見的用例:保護正在一個Servlet容器(如Tomcat或Jetty)中執行的Web應用程式。

先決條件

以下工具預計將安裝在本地開發機器上,以便遵循本教學。

  • Git(1.7+)
  • Java SDK 7
  • Maven 3

選擇您最喜歡的IDE,如:IntelliJ IDEA或Eclipse,甚至一個簡單的文字編輯器,檢視檔案和進行更改。

教學格式

這是一個分步教學。 本教學及其所有步驟作為Git儲存庫存在。 當您克隆git儲存庫時,主分支是您的起點。本教學中的每一步都是一個單獨的分支。可以簡單地通過檢查反映你當前正在審查的教學步驟的git分支。

應用程式

我們將要構建的Web應用程式是一個超級web應用程式,可以用作您自己的應用程式的起點。 在這個應用程式中將演示使用者登入,登出,使用者特定的歡迎訊息,對Web應用程式的某些部分的存取控制,以及插入安全資料儲存的整合。

我們將首先設定專案,包括構建工具和宣告依賴項,以及組態servlet的web.xml檔案以啟動Web應用程式和Shiro環境。
完成設定後,我們將分層單獨的功能,包括與安全資料儲存整合,然後啟用使用者登入,登出和存取控制。

專案設定

這裡不必手動設定目錄結構和初始的基本檔案集,我們已經在一個git儲存庫中為您做了這些工作了。

1. 下載教學專案

在GitHub上,存取教學專案,然後單擊右上角的綠色clone or download按鈕或者直接下載,並將下載後的檔案解壓到一個目錄中,在本教學中放在:F:\worksp\shiro\apache-shiro-tutorial-webapp-master 目錄中。

2. 專案結構

當前下載的檔案目錄(F:\worksp\shiro\shiro-webapp)具有以下結構:

    shiro-webapp/
      |-- src/
      |  |-- main/
      |    |-- resources/
      |      |-- logback.xml
      |    |-- webapp/
      |      |-- WEB-INF/
      |        |-- web.xml
      |      |-- home.jsp
      |      |-- include.jsp
      |      |-- index.jsp
      |-- .gitignore
      |-- .travis.yml
      |-- LICENSE
      |-- README.md
      |-- pom.xml

這裡簡單解釋上面每個檔案的含義:

  • pom.xml:Maven專案/構建檔案。它組態了Jetty,因此可以通過執行mvn jetty:run來測試Web應用程式。
  • README.md:一個簡單的專案讀我檔案。
  • LICENSE:專案的Apache 2.0許可證。
  • .travis.yml: Travis CI組態檔案,可在專案上執行持續整合,用來確保它始終構建。
  • .gitignore: 一個git忽略檔案,包含不應該被檢入版本控制的字尾和目錄。
  • src/main/resources/logback.xml: 一個簡單的Logback組態檔案。 對於本教學,我們選擇了SLF4J作為紀錄檔記錄API,Logback作為紀錄檔記錄實現。也可以選擇使用 Log4JJUL
  • src/main/webapp/WEB-INF/web.xml:初始的web.xml檔案,接下來將組態啟用Shiro。
  • src/main/webapp/include.jsp:包含常見匯入和宣告的頁面,包含在其他JSP頁面中。 這允許在一個地方管理匯入和宣告。
  • src/main/webapp/home.jsp: 這個webapp專案的簡單預設主頁。 包括include.jsp
  • src/main/webapp/index.jsp: 預設站點索引頁 - 只是將請求轉發到home.jsp首頁。

3.執行webapp

現在已經完成了專案的基本組態,可以通過在命令列上執行以下命令來執行Web應用程式:

$ mvn jetty:run

接下來,開啟web瀏覽器存取:http://localhost:8080 , 應該會看到主頁上有一個Hello,World!問候提示。

提示:同時按住ctrl+C關閉退出上面Web應用程式。

一、啟用 Shiro

初始儲存庫主分支只是一個簡單的通用Web應用程式,可以用作任何應用程式的模板。這裡我們新增最低限度,以在Web應用程式中啟用Shiro。

複製上面 shiro-webapp 專案為 shiro-webapp1

並新增了一個新的src/main/webapp/WEB-INF/shiro.ini檔案, 同時修改 src/main/webapp/WEB-INF/web.xml 檔案。

1. 新增 shiro.ini 檔案

Shiro可以在Web應用程式中以許多不同的方式進行組態,具體取決於使用的Web和/或MVC框架。 例如,可以通過Spring,Guice,Tapestry等等來組態Shiro。

為了簡化現在,我們使用Shiro的預設(非常簡單)基於INI的組態來啟動一個Shiro環境。
這個新的src/main/webapp/WEB-INF/shiro.ini檔案的內容(為了簡潔,刪除了檔頭注釋)如下所示:

[main]

# Let's use some in-memory caching to reduce the number of runtime lookups against a remote user store.
# A real application might want to use a more robust caching solution (e.g. ehcache or a
# distributed cache).  When using such caches, be aware of your cache TTL settings: too high
# a TTL and the cache won't reflect any potential changes in Stormpath fast enough.  Too low
# and the cache could evict too often, reducing performance.
cacheManager = org.apache.shiro.cache.MemoryConstrainedCacheManager
securityManager.cacheManager = $cacheManager

這個.ini檔案只包含一個帶有一些最小組態的[main]塊:

  • 它定義了一個新的cacheManager範例。 快取是Shiro架構的重要組成部分 - 它減少了到各種資料儲存的常規往返通訊。 這個例子使用一個MemoryConstrainedCacheManager,它只對單個JVM應用程式有用。 如果應用程式部署在多個主機(例如,叢集的Web伺服器場)上,您需要使用叢集化的CacheManager實現。
  • 它在Shiro securityManager上組態新的cacheManager範例。 Shiro SecurityManager範例始終存在,因此不需要顯式定義。

2. 在web.xml中啟用Shiro

雖然我們有一個shiro.ini組態,要實際載入它並啟動一個新的Shiro環境,並使該環境可用於Web應用程式。還要向現有的src/main/webapp/WEB-INF/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>
</filter>

<filter-mapping>
    <filter-name>ShiroFilter</filter-name>
    <url-pattern>/*</url-pattern>
    <dispatcher>REQUEST</dispatcher>
    <dispatcher>FORWARD</dispatcher>
    <dispatcher>INCLUDE</dispatcher>
    <dispatcher>ERROR</dispatcher>
</filter-mapping>
  • <listener>宣告定義了一個ServletContextListener,在Web應用程式啟動時啟動Shiro環境(包括Shiro SecurityManager)。 預設情況下,此偵聽器自動查詢用於Shiro組態的WEB-INF/shiro.ini檔案。
  • <filter>宣告定義主ShiroFilter。 此過濾器將過濾所有到Web應用程式的請求,以便Shiro可以在允許請求到達應用程式之前執行必要的身份和存取控制操作。
  • <filter-mapping>宣告確保所有請求型別都由ShiroFilter提交。 通常,過濾器對映宣告不指定<dispatcher>元素,但Shiro需要它們全部定義,因此它可以過濾可能為Web應用程式執行的所有不同的請求型別。

3. 執行webapp

完成 shiro-webapp1 專案組態後,執行Web應用程式:

$ mvn jetty:run

這一次,您將看到類似於以下內容的紀錄檔輸出,這表明Shiro確實在webapp中執行了:

二、連線到使用者儲存

複製上面 shiro-webapp1 專案為 shiro-webapp2

並新增了一個新的src/main/webapp/WEB-INF/shiro.ini檔案, 同時修改 src/main/webapp/WEB-INF/web.xml 檔案。

在上一步中已經在webapp中整合併執行了Shiro。 但是我們沒有實際告訴Shiro做任何事情。
在執行登入,登出,執行基於角色或基於許可權的存取控制或任何其他安全相關之前,我們需要有使用者。
我們需要組態Shiro以存取某種型別的使用者儲存,所以它可以查詢使用者執行登入嘗試,或檢查角色的安全決策等。任何應用程式可能需要存取的使用者儲存有許多型別: 也許你的使用者儲存在MySQL資料庫,也許在MongoDB,也許你的公司的使用者帳戶儲存在LDAP或Active Directory中的,也許你儲存在一個簡單的檔案或一些其他專有的資料儲存。

但這都不要緊,Shiro通過它稱為一個領域(Realm)。Shiro文件對Realm的解釋如下:

Realms act as the ‘bridge’ or ‘connector’ between Shiro and your application’s security data. When it comes time to actually interact with security-related data like user accounts to perform authentication (login) and authorization (access control), Shiro looks up many of these things from one or more Realms configured for an application.
In this sense a Realm is essentially a security-specific DAO: it encapsulates connection details for data sources and makes the associated data available to Shiro as needed. When configuring Shiro, you must specify at least one Realm to use for authentication and/or authorization. The SecurityManager may be configured with multiple Realms, but at least one is required.
Shiro provides out-of-the-box Realms to connect to a number of security data sources (aka directories) such as LDAP, relational databases (JDBC), text configuration sources like INI and properties files, and more. You can plug-in your own Realm implementations to represent custom data sources if the default Realms do not meet your needs.

因此,需要組態一個領域(Realm),以便我們可以存取使用者。

3. 組態Stormpath

本教學盡可能簡單,所以沒有引入複雜性或範圍,分散我們的學習Shiro的目的,將使用一個最簡單的Realm:一個Stormpath Realm

Stormpath是一個雲託管的使用者管理服務,完全免費用於開發目的。所以啟用Stormpath後,您將以下東西就會準備好了:

  • 當學習這個教學:用於管理應用程式,目錄,帳戶和組的使用者介面。但Shiro不提供這些,所以使用 Stormpath 是非常方便,也節省您的時間。
  • 用於使用者密碼的安全儲存機制。應用程式從不需要擔心密碼安全性,密碼比較或儲存密碼。 雖然Shiro可以做這些事情,還要你去組態它們,並知道加密概念。而Stormpath自動化密碼安全,所以你(和Shiro)不需要擔心它。

  • 安全工作流程,如帳戶電子郵件驗證和通過電子郵件重置密碼。 Shiro不支援這個,因為它通常是應用程式特定。

  • 託管很方便 - 我們不必設定任何東西或維護任何東西。

對於本教學的目的,Stormpath比設定單獨的RDBMS伺服器和擔心SQL或密碼加密問題簡單得多。所以我們現在就使用它吧。
當然,Stormpath只是Shiro可以通訊的許多後端資料儲存之一。 稍後我們將討論更複雜的資料儲存和特定於應用程式的組態。

註冊Stormpath帳號

  1. 填寫並提交Stormpath登錄檔單 - http://api.stormpath.com/register 。並行送確認電子郵件。
  2. 開啟電子郵件,點選確認電子郵件中的連結,完事。

獲取Stormpath API金鑰
Stormpath RealmStormpath通訊需要Stormpath API金鑰。按以下步驟獲取Stormpath API金鑰:

  1. 使用您在Stormpath註冊的電子郵件地址和密碼登入Stormpath管理控制台
  2. 在中間右側的結果頁面上,存取頁面的DEVELOPER TOOLS部分中的API Keys: Manage API Keys
  3. 在「帳戶詳細資訊」頁面的「Security Credentials」部分中,在「Api Keys」下單擊「Create API Key」。這將生成您的API金鑰並將其作為apiKey.properties檔案下載到您的計算機。 如果在文字編輯器中開啟檔案,您將看到類似於以下內容的內容:
    apiKey.id = 144JVZINOF5EBNCMG9EXAMPLE
    apiKey.secret = lWxOiKqKPNwJmSldbiSkEbkNjgh2uRSNAb+AEXAMPLE
    
  4. 將此檔案儲存在安全位置,例如,在使用者主目錄下的.stormpath目錄中。例如:
    $HOME/.stormpath/apiKey.properties
    
  5. 還要更改檔案許可權,以確保只有您可以讀取此檔案。 例如,在 *nix 作業系統:
    $ chmod go-rwx $HOME/.stormpath/apiKey.properties
    $ chmod u-w $HOME/.stormpath/apiKey.properties
    
    在Windows上,您可以類似地設定檔案許可權,參考:http://msdn.microsoft.com/en-us/library/bb727008.aspx

檢索預設的Stormpath應用程式

當您註冊Stormpath時,會自動為您建立一個空應用程式。 它的名稱是:My Application

我們必須使用Stormpath註冊我們的Web應用程式,以允許應用程式使用Stormpath進行使用者管理和身份驗證。 為了使用Stormpath 的 My Application 應用程式註冊我們的Web應用程式,我們需要知道一些資訊。我們可以使用Stormpath API檢索這些資訊。

首先,需要您的租戶在Stormpath中的位置。使用以下方法得到:

curl -i --user $YOUR_API_KEY_ID:$YOUR_API_KEY_SECRET \
'http://api.stormpath.com/v1/tenants/current'

其中:
$YOUR_API_KEY_IDapiKey.properties檔案中的apiKey.id
$YOUR_API_KEY_SECRETapiKey.properties檔案中的apiKey.secret

執行curl後會得到這樣的響應:

HTTP/1.1 302 Found
Date: Fri, 28 Aug 2015 18:34:51 GMT
Location: http://api.stormpath.com/v1/tenants/sOmELoNgRaNDoMIdHeRe
Server: Apache
Set-Cookie: rememberMe=deleteMe; Path=/; Max-Age=0; Expires=Thu, 27-Aug-2015 18:34:52 GMT
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
Content-Length: 0
Connection: keep-alive

注意:Windows系統預設沒有 CURL 命令,需要額外安裝。請參考:http://www.baidu.com/s?wd=window+curl&

注意Location頭資訊。這是您的Stormpath租戶的位置。 現在再次使用API檢索 Stormpath 的 My Application應用程式的位置:

curl -u $API_KEY_ID:$API_KEY_SECRET \
     -H "Accept: application/json" \
     '$TENANT_HREF/applications?name=My%20Application'

其中:

$YOUR_API_KEY_IDapiKey.properties檔案中的apiKey.id
$YOUR_API_KEY_SECRETapiKey.properties檔案中的apiKey.secret
$TENANT_HREF是上一步驟的Location頭資訊的值

這個響應有很多資訊。下面只是響應中的一個例子的一部分:

{
    ...
    "href": "http://api.stormpath.com/v1/applications/aLoNGrAnDoMAppIdHeRe",
    "name": "My Application",
    "description": "This application was automatically created for you in Stormpath for use with our Quickstart guides(http://docs.stormpath.com). It does apply to your subscription's number of reserved applications and can be renamed or reused for your own purposes.",
    "status": "ENABLED",
    "tenant": {
        "href": "http://api.stormpath.com/v1/tenants/sOmELoNgRaNDoMIdHeRe"
    },
    ...
}

從上面記下你的頂層href - 將使用這個hrefshiro.ini組態下。

建立應用程式測試使用者帳戶

現在我們有一個應用程式,接下來將要為這個應用程式建立一個sample/test使用者:

curl --request POST --user $YOUR_API_KEY_ID:$YOUR_API_KEY_SECRET \
    -H "Accept: application/json" \
    -H "Content-Type: application/json" \
    -d '{
           "givenName": "Jean-Luc",
           "surname": "Picard",
           "username": "jlpicard",
           "email": "[email protected]",
           "password":"Changeme1"
        }' \
 "$YOUR_APPLICATION_HREF/accounts"

在上面程式碼中:

$YOUR_API_KEY_IDapiKey.properties檔案中的apiKey.id
$YOUR_API_KEY_SECRETapiKey.properties中的apiKey.secret
$YOUR_APPLICATION_HREF是您記下的應用程式href
同樣,不要忘記更改上面的網址中的$YOUR_APPLICATION_HREF以匹配應用程式的ID

2. 組態 shiro.ini中的Realm

選擇至少一個使用者儲存來連線到Shiro,需要組態一個Realm來代表該資料儲存,然後告訴Shiro SecurityManager。

src/main/webapp/WEB-INF/shiro.ini 檔案的[main]部分新增以下內容:

# Configure a Realm to connect to a user datastore.  In this simple tutorial, we'll just point to Stormpath since it
# takes 5 minutes to set up:
stormpathClient = com.stormpath.shiro.client.ClientFactory
stormpathClient.cacheManager = $cacheManager

# (Optional) If you put your apiKey.properties in the non-default location, you set the location here
#stormpathClient.apiKeyFileLocation = $HOME/.stormpath/apiKey.properties

stormpathRealm = com.stormpath.shiro.realm.ApplicationRealm
stormpathRealm.client = $stormpathClient

# Find this URL in your Stormpath console for an application you create:
# Applications -> (choose application name) --> Details --> REST URL
# (Optional) If you only have one Application
#stormpathRealm.applicationRestUrl = http://api.stormpath.com/v1/applications/$STORMPATH_APPLICATION_ID

stormpathRealm.groupRoleResolver.modeNames = name
securityManager.realm = $stormpathRealm

注意可選行:

如果您已經使用Stormpath一段時間,並且有多個Stormpath應用程式,則必須設定stormpathRealm.applicationRestUrl屬性。

3. 執行webapp

按照步驟1步驟2中的指定進行更改後,繼續並執行Web應用程式:

mvn jetty:run

這一次,您將看到類似於以下的紀錄檔輸出,表明Shiro和新的 Realm 在您的webapp中正確組態:

16:08:25.466 [main] INFO  o.a.shiro.web.env.EnvironmentLoader - Starting Shiro environment initialization.
16:08:26.201 [main] INFO  o.a.s.c.IniSecurityManagerFactory - Realms have been explicitly set on the SecurityManager instance - auto-setting of realms will not occur.
16:08:26.201 [main] INFO  o.a.shiro.web.env.EnvironmentLoader - Shiro environment initialized in 731 ms.

三、啟用登入和登出

現在我們有使用者,可以在UI中輕鬆新增,刪除和禁用它們。 現在可以在我們的應用程式中啟用諸如登入/登出和存取控制等功能。

複製上面 shiro-webapp2 專案為 shiro-webapp3

並新增以下2個附加檔案:

  • 新增了一個新的src/main/webapp/login.jsp檔案和一個簡單的登入表單以使用它來登入。
  • 更新shiro.ini檔案,以支援特定於Web(URL)的功能。

1. 啟用Shiro表單登入和登出支援

src/main/webapp/WEB-INF/shiro.ini 檔案包含以下2個附加項:

[main]

shiro.loginUrl = /login.jsp

# Stuff we've configured here previously is omitted for brevity

[urls]
/login.jsp = authc
/logout = logout

shiro.* 行

[main]部分的頂部,新增有一個新行:

shiro.loginUrl = /login.jsp

這是一個特殊的組態指令,告訴Shiro「對於任何具有loginUrl屬性的Shiro預設過濾器,我希望將屬性值設定為/login.jsp「。

這允許Shiro的預設authc過濾器(預設情況下,FormAuthenticationFilter)了解登入頁面。 這是FormAuthenticationFilter正常工作所必需的。

[urls]部分

[urls]部分是一個新的特定於web的INI部分。

本部分允許您使用非常簡潔的名稱/值對語法來告訴shiro如何過濾任何給定URL路徑的請求。 [urls]中的所有路徑都是相對於Web應用程式的HttpServletRequest.getContextPath()) 值。

這些名稱/值對提供了一種非常強大的方法來過濾請求,允許各種安全規則。 更深的URL和過濾器鏈的覆蓋範圍超出了本文件的範圍,但如果你有興趣,請閱讀: http://shiro.apache.org/web.html

現在,我們將介紹說明以下新增的兩行:

/login.jsp = authc
/logout = logout
  • 第一行表示「每當Shiro看到對/login.jsp的 URL 請求時,請求期間啟用Shiro authc過濾器」。
  • 第二行表示「每當Shiro看到對/logout的 URL 請求,請求期間啟用Shiro logout 過濾器。

這兩個過濾器有點特別:實際上不需要任何東西「在它們的後面」。他們實際上只是完全處理請求。沒有任何東西可以處理這些URL - 因為沒有編寫任何控制器! Shiro將根據需要處理請求。

2. 新增登入頁面

由於步驟3啟用登入和登出支援,現在我們需要確保有一個/login.jsp頁面來顯示登入表單。

在這個新的src/main/webapp/login.jsp頁面中。 這是一個簡單的引導主題的HTML登入頁面,它有四個重要的事情:

  1. 表單的操作值是空字串。當表單沒有操作值時,瀏覽器會將表單請求提交到同一個網址。 Shiro可以自動處理任何登入提交。shiro.ini中的/login.jsp = authc行是告訴authc過濾器處理提交的內容。
  2. 有一個使用者名(username)表單欄位。 Shiro authc過濾器將在登入提交期間自動查詢使用者名(username)請求引數,並將其用作登入期間的值(許多Realms允許這是電子郵件或使用者名)。
  3. 有一個密碼(password)表單欄位。 Shiro authc過濾器將在登入提交期間自動查詢密碼請求引數。
  4. 有一個rememberMe核取方塊,其「選中」狀態可以是「true」值(truet1enabledyyesopen)。

login.jsp中的表單只使用預設的:使用者名,密碼和rememberMe表單欄位名。 如果想要更改這些名稱,可以組態這些名稱 - 有關更改組態資訊,請參閱FormAuthenticationFilter JavaDoc

3. 執行webapp

按照步驟1步驟2中的指定進行更改後,繼續並執行Web應用程式:

mvn jetty:run

4. 嘗試登入

使用Web瀏覽器,導航存取: localhost:8080/login.jsp , 將看到新的登入表單。

輸入您在步驟2結束時建立的帳戶的使用者名和密碼,然後點選「登入」。 如果登入成功,您將被定向到主頁! 如果登入失敗,您將再次顯示登入頁面。