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!問候提示。
Shiro Web應用程序

提示:同時按住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 Web應用程序

二、連接到用戶存儲

複製上面 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#Web-%7B%7B%5Curls%5C%7D%7D

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

/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結束時創建的帳戶的用戶名和密碼,然後點擊「登錄」。 如果登錄成功,您將被定向到主頁! 如果登錄失敗,您將再次顯示登錄頁面。