apache-shiro / 1.5.3 / reference / webapp-tutorial.html

使用 Apache Shiro 保护 Web 应用程序的安全

NOTE: This Page is Out of Date

Github上查看正式的 Apache Shiro 示例应用程序的完整列表。通过Pull Request更新此页面来帮助完成项目

本文档是使用 Apache Shiro 保护 Web 应用程序的循序渐进教程。它假定您具有 Shiro 的入门知识,并且至少熟悉以下两个入门文档:

本分步教程大约需要 45 分钟到 1 个小时才能完成。完成后,您将对 Shiro 在 Web 应用程序中的工作方式有一个很好的了解。

目录

Overview

尽管 Apache Shiro 的核心设计目标允许其用于保护任何基于 JVM 的应用程序,例如命令行应用程序,服务器守护程序,Web 应用程序等,但本指南将重点介绍最常见的用例:保护 Web 应用程序运行在Servlet容器(例如 Tomcat 或 Jetty)中。

Prerequisites

预期将在您的本地开发计算机上安装以下工具,以便与本教程一起进行。

  • Git(经 1.7 测试)

  • Java SDK 7

  • Maven 3

  • 您最喜欢的 IDE,例如 IntelliJ IDEA 或 Eclipse,甚至是一个简单的文本编辑器,用于查看文件并进行更改。

Tutorial Format

这是一个分步教程。本教程及其所有步骤都作为 Git 存储库存在。克隆 git 存储库时,master分支是您的起点。本教程中的每个步骤都是一个单独的分支。您可以简单地通过检出反映您正在查看的教程步骤的 git 分支进行操作。

The Application

我们将构建的 Web 应用程序是一个超级 Webapp,可用作您自己的应用程序的起点。它将演示用户登录,注销,特定于用户的欢迎消息,对 Web 应用程序某些部分的访问控制以及与可插拔安全性数据存储的集成。

我们将从设置项目开始,包括构建工具和声明依赖项,以及配置 servlet web.xml文件以启动 Web 应用程序和 Shiro 环境。

完成设置后,我们将分层处理各个功能,包括与安全数据存储集成,然后启用用户登录,注销和访问控制。

Project Setup

无需手动设置目录结构和初始基本文件集,我们已经在 git 存储库中为您完成了此操作。

1.分叉教程项目

在 GitHub 上,访问tutorial project并单击右上角的Fork按钮。

2.克隆您的教程 Repository

现在,您已将存储库分叉到您自己的 GitHub 帐户,将其克隆到本地计算机上:

$ git clone git@github.com:$YOUR_GITHUB_USERNAME/apache-shiro-tutorial-webapp.git

(其中$YOUR_GITHUB_USERNAME当然是您自己的 GitHub 用户名)

现在,您可以cd进入克隆的目录并查看项目结构:

$ cd apache-shiro-tutorial-webapp

3.审查项目结构

克隆存储库后,您当前的master分支将具有以下结构:

apache-shiro-tutorial-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 作为日志记录实现。可能很容易就是 Log4J 或 JUL。

  • src/main/webapp/WEB-INF/web.xml:我们即将配置的初始web.xml文件,以启用 Shiro。

  • src/main/webapp/include.jsp:包含其他 JSP 页面中包含的通用导入和声明的页面。这使我们可以在一个地方 Management 导入和声明。

  • src/main/webapp/home.jsp:我们的 Web 应用程序的简单默认主页。包括include.jsp(其他人也一样,我们将很快看到)。

  • src/main/webapp/index.jsp:默认的网站索引页面-仅将请求转发到我们的home.jsp主页。

4.运行网络应用

既然您已经克隆了项目,则可以通过在命令行上执行以下操作来运行 Web 应用程序:

$ mvn jetty:run

接下来,打开 Web 浏览器到localhost:8080,您将看到带有 Hello,World! 问候的主页。

按下ctl-C以关闭该网络应用。

步骤 1:启用 Shiro

我们最初的存储库master分支只是一个简单的通用 Web 应用程序,可以用作任何应用程序的模板。接下来添加最低限度,以便在 Web 应用程序中启用 Shiro。

执行以下 git checkout 命令来加载step1分支:

$ git checkout step1

签出此分支,您将发现两个更改:

  • 添加了一个新的src/main/webapp/WEB-INF/shiro.ini文件,并且

  • src/main/webapp/WEB-INF/web.xml已被修改。

1 a:添加 shiro.ini 文件

可以使用多种不同的方式在 Web 应用程序中配置 Shiro,具体取决于您使用的 Web 和/或 MVC 框架。例如,您可以通过 Spring,Guice,Tapestry 等配置 Shiro。

为使事情现在变得简单,我们将使用 Shiro 的默认(且非常简单)INI-based configuration启动 Shiro 环境。

如果您检出step1分支,您将看到此新src/main/webapp/WEB-INF/shiro.ini文件的内容(为简洁起见,删除了标题 Comments):

[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实例始终存在,因此无需明确定义。

1 b:在 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,该ServletContextListener在 Web 应用程序启动时启动 Shiro 环境(包括 Shiro SecurityManager)。默认情况下,此侦听器会自动知道要查找我们的WEB-INF/shiro.ini文件以进行 Shiro 配置。

  • <filter>声明定义了主ShiroFilter。该过滤器有望将所有请求过滤到 Web 应用程序中,因此 Shiro 可以在允许请求到达应用程序之前执行必要的身份和访问控制操作。

  • <filter-mapping>声明可确保ShiroFilter提交所有请求类型。 filter-mapping声明通常不指定<dispatcher>元素,但 Shiro 需要全部定义它们,以便它可以过滤可能对 Web 应用程序执行的所有不同请求类型。

1 c:运行网络应用

检出step1分支后,continue 运行 Web 应用程序:

$ mvn jetty:run

这次,您将看到类似于以下内容的日志输出,表明 Shiro 确实在您的 Web 应用程序中运行:

16:04:19.807 [main] INFO  o.a.shiro.web.env.EnvironmentLoader - Starting Shiro environment initialization.
16:04:19.904 [main] INFO  o.a.shiro.web.env.EnvironmentLoader - Shiro environment initialized in 95 ms.

按下ctl-C以关闭该网络应用。

步骤 2:连接到用户 Store

执行以下 git checkout 命令来加载step2分支:

$ git checkout step2

现在,我们已将 Shiro 集成并在 webapp 中运行。但是实际上我们还没有告诉 Shiro 任何事情!

我们需要用户才能登录,注销,执行基于角色或基于权限的访问控制或任何其他与安全性相关的操作!

我们将需要配置 Shiro 来访问某种类型的* User Store *,以便它可以查找用户以执行登录尝试,或检查角色的安全性决定等。任何应用程序可能需要使用多种类型的用户存储。访问:也许您将用户存储在 MySQL 数据库中,也许在 MongoDB 中,也许您的公司将用户帐户存储在 LDAP 或 Active Directory 中,也许将它们存储在简单文件中,或其他专有数据存储中。

Shiro 通过称为Realm的方式进行此操作。根据 Shiro 的文档:

Note

领域充当 Shiro 与应用程序的安全数据之间的“bridge 梁”或“连接器”。当 true 需要与安全性相关的数据(例如用户帐户)进行交互以执行身份验证(登录)和授权(访问控制)时,Shiro 会从一个或多个为应用程序配置的领域中查找许多此类内容。

从这个意义上说,领域本质上是特定于安全性的DAO:它封装了数据源的连接详细信息,并根据需要使关联数据可用于 Shiro。在配置 Shiro 时,您必须至少指定一个领域用于身份验证和/或授权。 SecurityManager 可以配置有多个领域,但至少需要一个。

Shiro 提供了开箱即用的领域,可以连接到许多安全数据源(又名目录),例如 LDAP,关系数据库(JDBC),文本配置源(例如 INI 和属性文件)等。如果默认的 Realms 不能满足您的需求,那么您可以插入自己的 Realm 实现以表示自定义数据源。

因此,我们需要配置一个领域,以便我们可以访问用户。

2 a:设置风暴路径

本着使本教程尽可能简单的精神,而又不引入会使我们脱离学习 Shiro 的目的的复杂性或范围,我们将使用我们可以做到的最简单的领域之一:Stormpath 领域。

Stormpath是云托管的用户 Management 服务,出于开发目的完全免费。这意味着在启用 Stormpath 之后,您将可以进行以下操作:

  • 用于 Management 应用程序,目录,帐户和组的用户界面。 Shiro 完全不提供此功能,因此在您阅读本教程时,这将很方便,并节省时间。

  • 用户密码的安全存储机制。您的应用程序无需担心密码安全性,密码比较或存储密码。尽管 Shiro 可以做到这些,但您必须对其进行配置并了解加密概念。 Stormpath 自动执行密码安全性,因此您(和 Shiro)无需担心它,也不必为“正确设置”而烦恼。

  • 安全工作流程,例如帐户电子邮件验证和通过电子邮件重置密码。 Shiro 不支持此功能,因为它通常是特定于应用程序的。

  • 托管/托管的“始终在线”基础设施-您无需进行任何设置或维护。

就本教程而言,Stormpath 比设置单独的 RDBMS 服务器和担心 SQL 或密码加密问题要简单得多。因此,我们现在将使用它。

当然,Stormpath 只是 Shiro 可以与之通信的许多后端数据存储之一。稍后,我们将介绍更复杂的数据存储及其特定于应用程序的配置。

注册 Stormpath

  • 填写并提交Stormpath 注册表。这将发送确认电子邮件。

  • 单击确认电子邮件中的链接。

获取 Stormpath API 密钥

要使 Stormpath 领域与 Stormpath 通信,需要一个 Stormpath API 密钥。要获取 Stormpath API 密钥:

  • 使用您用来向 Stormpath 注册的电子邮件地址和密码登录StormpathManagement 控制台

  • 在结果页面的右上角,访问页面“开发人员工具”部分中的“ API 密钥:ManagementAPI 密钥”。

  • 在“帐户详细信息”页面上的“安全凭据”部分中,单击“ Api 密钥”下的“创建 API 密钥”。

这将生成您的 API 密钥,并将其作为apiKey.properties文件下载到您的计算机中。如果在文本编辑器中打开文件,将看到类似以下内容的内容:

apiKey.id = 144JVZINOF5EBNCMG9EXAMPLE
apiKey.secret = lWxOiKqKPNwJmSldbiSkEbkNjgh2uRSNAb+AEXAMPLE
  • 将此文件保存在安全的位置,例如主目录在隐藏的.stormpath目录中。例如:
$HOME/.stormpath/apiKey.properties
  • 同时更改文件权限,以确保只有您可以读取此文件。例如,在* nixos 上:
$ chmod go-rwx $HOME/.stormpath/apiKey.properties
$ chmod u-w $HOME/.stormpath/apiKey.properties

在 Windows 上,您可以设置文件权限类似

检索默认的 Stormpath 应用程序

当您注册 Stormpath 时,将自动为您创建一个空应用程序。名为:My Application

我们必须在 Stormpath 中注册我们的 Web 应用程序,以允许该应用程序使用 Stormpath 进行用户 Management 和身份验证。为了向My Application Stormpath 应用程序注册我们的 Web 应用程序,我们需要了解一些信息。幸运的是,我们可以使用 Stormpath API 检索此信息。

首先,我们需要 Stormpath 中您租户的位置。这是如何得到的:

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

where:

  • $ YOUR_API_KEY_ID 是 apiKey.properties 中的 apiKey.id 值,

  • $ YOUR_API_KEY_SECRET 是 apiKey.properties 中的 apiKey.secret 值

您将收到如下响应:

HTTP/1.1 302 Found
Date: Fri, 28 Aug 2015 18:34:51 GMT
Location: https://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

请注意LocationHeaders。这是您的 Stormpath 租户的位置。现在,我们可以再次使用 API 来检索My Application Stormpath 应用程序的位置:

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

where:

  • $ YOUR_API_KEY_ID 是 apiKey.properties 中的 apiKey.id 值,

  • $ YOUR_API_KEY_SECRET 是 apiKey.properties 中的 apiKey.secret 值

  • $ TENANT_HREF 是上一步中LocationHeaders 的值

对此的响应包含很多信息。这是响应的摘录示例:

{
    ...
    "href": "https://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(https://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": "https://api.stormpath.com/v1/tenants/sOmELoNgRaNDoMIdHeRe"
    },
    ...
}

从上方记录您的顶级href-我们将在接下来的shiro.ini配置中使用此 href。

创建应用程序测试用户帐户

现在我们有了一个应用程序,我们将要为该应用程序创建一个 samples/测试用户:

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": "capt@enterprise.com",
           "password":"Changeme1"
        }' \
 "$YOUR_APPLICATION_HREF/accounts"

where:

  • $ YOUR_API_KEY_ID 是 apiKey.properties 中的 apiKey.id 值,

  • $ YOUR_API_KEY_SECRET 是 apiKey.properties 中的 apiKey.secret 值

  • $ YOUR_APPLICATION_HREF 是您在上面记下的应用程序href

同样,不要忘记在上面的 URL 中更改$YOUR_APPLICATION_ID以匹配您的应用程序 ID!

2 b:在 shiro.ini 中配置领域

一旦选择了至少一个要连接到 Shiro 的用户存储,我们就需要配置一个代表该数据存储的Realm,然后将其告知 Shiro SecurityManager

如果您已检出step2分支,您会注意到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 = https://api.stormpath.com/v1/applications/$STORMPATH_APPLICATION_ID

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

请注意可选行:

  • 如果您已经使用 Stormpath 一段时间,并且有多个 Stormpath 应用程序,则必须设置stormpathRealm.applicationRestUrl属性。

2 d:运行网络应用

在按照步骤 2b 和 2c 进行更改后,continue 并运行 Web 应用程序:

$ mvn jetty:run

这次,您将看到类似于以下内容的日志输出,表明在 Webapp 中正确配置了 Shiro 和新的 Realm:

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.

按下ctl-C以关闭该网络应用。

步骤 3:启用登录和注销

现在我们有了用户,我们可以在 UI 中轻松添加,删除和禁用它们。现在,我们可以开始在应用程序中启用登录/注销和访问控制等功能。

执行以下 git checkout 命令来加载step3分支:

$ git checkout step3

此结帐将加载以下 2 个附加项:

  • 新的src/main/webapp/login.jsp文件已添加,带有简单的登录表单。我们将使用它来登录。

  • shiro.ini文件已更新,以支持特定于 Web(URL)的功能。

步骤 3a:启用 Shiro 表单登录和注销支持

step3分支的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.* lines

[main]部分的顶部有一个新行:

shiro.loginUrl = /login.jsp

这是一个告诉 Shiro 的特殊配置指令:“对于具有loginUrl属性的 Shiro 的default filters,我希望将该属性值设置为/login.jsp。”

这允许 Shiro 的默认authc过滤器(默认情况下为FormAuthenticationFilter)知道登录页面。为了使FormAuthenticationFilter正常工作,这是必需的。

[urls]部分

[urls]部分是新的特定于网络的 INI 部分

本节允许您使用非常简洁的名称/值对语法来告诉 shiro 如何过滤任何给定 URL 路径的请求。 [urls]中的所有路径都相对于 Web 应用程序的+199+值。

这些名称/值对提供了一种非常强大的方法来过滤请求,从而支持各种安全规则。 url 和过滤器链的更深入介绍不在本文档的讨论范围之内,但是如果您有兴趣,请执行了解更多

现在,我们将介绍添加的两行:

/login.jsp = authc
/logout = logout
  • 第一行表示“只要 Shiro 看到对/login.jsp URL 的请求,请在请求期间启用 Shiro authc过滤器”。

  • 第二行表示“只要 Shiro 看到对/logout网址的请求,就在请求期间启用 Shiro logout过滤器”。

这两个过滤器都有些特殊:它们实际上不需要任何东西在它们后面。他们实际上不会完全过滤,而只是完全处理请求。这意味着您无需为这些 URL 的请求做任何事情-无需编写控制器! Shiro 将根据需要处理这些请求。

步骤 3b:添加登录页面

由于第 3a 步启用了登录和注销支持,所以现在我们需要确保实际上有一个/login.jsp页面来显示登录表单。

step3分支包含一个新的src/main/webapp/login.jsp页面。这是一个非常简单的以引导程序为主题的 HTML 登录页面,但是其中包含四点重要的内容:

  • 表单的action值为空字符串。当表单没有操作值时,浏览器会将表单请求提交到相同的 URL。很好,因为我们将很快告诉 Shiro 该 URL 是什么,以便 Shiro 可以自动处理所有登录提交。 shiro.ini中的/login.jsp = authc行告诉authc过滤器处理提交。

  • 有一个username表单字段。 Shiro authc过滤器将在登录提交期间自动查找username请求参数,并将其用作登录期间的值(许多领域允许它为电子邮件或用户名)。

  • 有一个password表单字段。 Shiro authc过滤器将在登录提交期间自动查找password请求参数。

  • 有一个rememberMe复选框,其“已选中”状态可以是“真实”值(truet1enabledyyeson)。

我们的 login.jsp 表单仅使用默认的usernamepasswordrememberMe表单字段名称。如果您想更改它们,可以配置这些名称-有关信息,请参见FormAuthenticationFilter JavaDoc

步骤 3c:运行 webapp

在按照步骤 2b 和 2c 进行更改后,continue 并运行 Web 应用程序:

$ mvn jetty:run

步骤 3d:尝试登录

使用网络浏览器,导航至localhost:8080/login.jsp,您将看到我们新的闪亮登录表单。

Importing 您在第 2 步结束时创建的帐户的用户名和密码,然后点击“登录”。如果登录成功,您将被带到主页!如果登录失败,将再次显示登录页面。

提示:如果要成功登录将用户重定向到首页以外的其他页面(上下文路径/),则可以在 INI 的[main]部分中设置authc.successUrl = /whatever

按下ctl-C以关闭该网络应用。

步骤 4:变更使用者专用的使用者介面

通常需要根据用户身份来更改 Web 用户界面。我们可以轻松做到这一点,因为 Shiro 支持 JSP 标记库来根据当前登录的 Subject(用户)执行操作。

执行以下 git checkout 命令来加载step4分支:

$ git checkout step4

此步骤为我们的home.jsp页面添加了一些内容:

  • 当前查看页面的当前用户未登录时,他们将看到“欢迎访客”消息,并看到登录页面的链接。

  • 当当前正在查看页面的用户已登录时,他们将看到自己的名字“ Welcome * username *”和注销链接。

这种 UI 自定义类型对于导航栏非常常见,用户控件位于屏幕的右上角。

步骤 4a:添加 Shiro 标签库声明

home.jsp文件已修改为在顶部包括两行:

<%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

这两个 JSP 页面指令允许页面中的 Core(c:)和 Shiro(shiro:)标记库。

步骤 4b:添加 Shiro Guest 和 User 标签

home.jsp文件在页面正文中被进一步修改(在<h1>欢迎消息之后),以同时包含<shiro:guest><shiro:user>标签:

<p>Hi <shiro:guest>Guest</shiro:guest><shiro:user>
<%
    //This should never be done in a normal page and should exist in a proper MVC controller of some sort, but for this
    //tutorial, we'll just pull out Stormpath Account data from Shiro's PrincipalCollection to reference in the
    //<c:out/> tag next:

    request.setAttribute("account", org.apache.shiro.SecurityUtils.getSubject().getPrincipals().oneByType(java.util.Map.class));

%>
<c:out value="${account.givenName}"/></shiro:user>!
    ( <shiro:user><a href="<c:url value="/logout"/>">Log out</a></shiro:user>
    <shiro:guest><a href="<c:url value="/login.jsp"/>">Log in</a></shiro:guest> )
</p>

给定格式,阅读起来有点困难,但是这里使用了两个标签:

  • <shiro:guest>:仅当当前 Shiro Subject是应用程序“ guest”时,此标签才会显示其内部内容。 Shiro 将guest定义为尚未登录到该应用程序的任何Subject,或者以前登录时不记得的Subject(使用 Shiro 的“记住我”功能)。

  • <shiro:user>:仅当当前 Shiro Subject是应用程序“用户”时,此标记才会显示其内部内容。 Shiro 将user定义为当前已登录(已验证)应用程序的任何Subject或从先前登录中记住的Subject(使用 Shiro 的“记住我”功能)。

如果主题是访客,则以上代码段将呈现以下内容:

Hi Guest! (Log in)

“登录”是指向/login.jsp的超链接

如果主题是“用户”,它将呈现以下内容:

Hi jsmith! (Log out)

假设“ jsmith”是已登录帐户的用户名。“注销”是指向 Shiro logout过滤器处理的“/logout” URL 的超链接。

如您所见,您可以关闭或打开整个页面的部分,功能和 UI 组件。除了<shiro:guest><shiro:user>之外,Shiro 还支持许多其他有用的 JSP 标记,您可以使用许多其他有用的 JSP 标记根据有关当前Subject的各种已知信息来自定义 UI。

步骤 4c:运行 webapp

检出step4分支后,continue 运行 Web 应用程序:

$ mvn jetty:run

尝试以访客身份访问localhost:8080,然后登录。成功登录后,您将看到页面内容更改,以反映您现在是已知用户!

按下ctl-C以关闭该网络应用。

步骤 5:只允许经过身份验证的用户访问

尽管您可以根据主题状态更改页面内容,但通常您会希望根据某人在当前与 Web 应用程序进行交互时是否“证明”其身份(已验证)来限制 Web 应用程序的整个部分。

如果 Webapp 的仅用户部分显示敏感信息(如帐单明细或控制其他用户的能力),则这一点尤其重要。

执行以下 git checkout 命令来加载step5分支:

$ git checkout step5

步骤 5 引入以下 3 个更改:

  • 我们添加了 Webapp 的新部分(URL 路径),我们希望将其限制为仅通过身份验证的用户。

  • 我们更改了shiro.ini,以告知 Shiro 仅允许经过身份验证的用户使用该 Web 应用程序的该部分。

  • 我们修改了主页,以根据当前的Subject是否通过身份验证来更改其输出。

步骤 5a:添加新的受限部分

添加了一个新的src/main/webapp/account目录。此目录(及其下的所有路径)模拟您可能希望限制为仅登录用户的网站的“私有”或“仅经过身份验证”部分。 src/main/webapp/account/index.jsp文件只是模拟的“家庭帐户”页面的占位符。

步骤 5b:配置 shiro.ini

通过在[urls]部分的末尾添加以下行来修改shiro.ini

/account/** = authc

Shiro 过滤器链定义表示“对/account(或其任何子路径)的任何请求都必须经过身份验证”。

但是,如果有人尝试访问该路径或其任何子路径,会发生什么?

您还记得在第 3 步中,当我们将以下行添加到[main]部分时:

shiro.loginUrl = /login.jsp

该行使用我们的 Web 应用程序的登录 URL 自动配置了authc过滤器。

基于此配置行,authc筛选器现在已经足够聪明,可以知道如果访问/account时当前主题未通过身份验证,它将自动将Subject重定向到/login.jsp页面。成功登录后,它将自动将用户重定向到他们尝试访问的页面(/account)。方便!

步骤 5c:更新我们的首页

步骤 5 的最终更改是更新/home.jsp页面,以使用户知道他们可以访问网站的新部分。

这些行添加在欢迎消息下方:

<shiro:authenticated><p>Visit your <a href="<c:url value="/account"/>">account page</a>.</p></shiro:authenticated>
<shiro:notAuthenticated>
  <p>If you want to access the authenticated-only <a href="<c:url value="/account"/>">account page</a>, you will need to log-in first.</p>
</shiro:notAuthenticated>

<shiro:authenticated>标记仅在当前主题在其当前会话期间已经登录(认证)的情况下显示内容。 Subject就是这样知道他们可以访问网站的新部分。

<shiro:notAuthenticated>标签仅在当前主题在其当前会话期间尚未通过身份验证时显示内容。

但是您是否注意到notAuthenticated内容仍然具有指向/account部分的 URL?没关系-我们的authc过滤器将如上所述处理登录然后重定向的流程。

用新的更改启动 Webapp 并尝试一下!

步骤 5d:运行 Webapp

检出step5分支后,continue 运行 Web 应用程序:

$ mvn jetty:run

尝试访问localhost:8080。到达该位置后,单击新的/account链接并观看它重定向,以强制您登录。登录后,返回首页,并在您通过身份验证后再次看到内容更改。您可以随意访问帐户页面和主页,直到注销为止。真好!

按下ctl-C以关闭该网络应用。

步骤 6:基于角色的访问控制

除了基于身份验证控制访问之外,通常还需要根据将哪些角色分配给当前Subject来限制对应用程序某些部分的访问。

执行以下 git checkout 命令来加载step6分支:

$ git checkout step6

步骤 6a:添加角色

为了执行基于角色的访问控制,我们需要角色存在。

在本教程中,最快的方法是在 Stormpath 中填充一些组(在 Stormpath 中,Stormpath 组可以起到与角色相同的作用)。

为此,请登录到 UI 并按以下方式导航:

目录>我的应用程序目录>组

添加以下三个组:

  • Captains

  • Officers

  • Enlisted

(与我们的“星际迷航”帐户主题保持一致:))

创建组后,请将Jean-Luc Picard帐户添加到CaptainsOfficers组。您可能想要创建一些临时帐户,并将它们添加到所需的任何组中。确保某些帐户不与组重叠,以便您可以看到基于分配给用户帐户的单独组的更改。

步骤 6b:基于角色的访问控制(RBAC)标签

我们更新了/home.jsp页面,以使用户知道他们拥有哪些角色以及他们没有哪些角色。这些消息将添加到主页的新<h2>Roles</h2>部分中:

<h2>Roles</h2>

<p>Here are the roles you have and don't have. Log out and log back in under different user
    accounts to see different roles.</p>
    
<h3>Roles you have:</h3>

<p>
    <shiro:hasRole name="Captains">Captains
</shiro:hasRole> <shiro:hasRole name="Officers">Bad Guys
</shiro:hasRole> <shiro:hasRole name="Enlisted">Enlisted
</shiro:hasRole> </p> <h3>Roles you DON'T have:</h3> <p> <shiro:lacksRole name="Captains">Captains
</shiro:lacksRole> <shiro:lacksRole name="Officers">Officers
</shiro:lacksRole> <shiro:lacksRole name="Enlisted">Enlisted
</shiro:lacksRole> </p>

<shiro:hasRole>标签将仅在当前主题已分配了指定角色的情况下显示内容。

<shiro:lacksRole>标签仅在未为当前主题**分配指定角色的情况下显示内容。

步骤 6c:RBAC 过滤器链

留给 Reader 的练习(未定义的步骤)是创建网站的新部分,并根据分配给当前用户的角色来限制对网站该部分的 URL 访问。

提示:使用roles filter为网络应用的新部分创建一个过滤链定义

步骤 6d:运行 webapp

检出step6分支后,continue 运行 Web 应用程序:

$ mvn jetty:run

尝试访问localhost:8080并使用分配了不同角色的不同用户帐户登录,并观看首页的“角色”部分内容的更改!

按下ctl-C以关闭该网络应用。

步骤 7:基于权限的访问控制

基于角色的访问控制对许多用例都适用,但是存在一个主要问题:您不能在运行时添加或删除角色。角色检查使用角色名称进行硬编码,因此,如果您更改了角色名称或角色配置,或者添加或删除了角色,则必须返回并更改代码!

因此,Shiro 具有强大的侯爵功能:对* permissions *的内置支持。在 Shiro 中,权限是对功能的原始说明,例如“开门”,“创建博客条目”,“删除jsmith用户”等。权限反映了应用程序的原始功能,因此您只需要更改权限检查当您更改应用程序的功能时-如果要更改角色或用户模型,则不需要。

为了说明这一点,我们将创建一些权限并将其分配给用户,然后根据用户的授权(权限)自定义我们的 Web UI。

步骤 7a:添加权限

Shiro Realm是只读组件:每个数据存储区对角色,组,权限,帐户及其关系的建模方式都不相同,因此 Shiro 没有“写入” API 来修改这些资源。要修改基础模型对象,只需通过所需的任何 API 直接对其进行修改。然后,您的 Shiro 领域就会知道如何读取此信息并以 Shiro 理解的格式表示它。

这样,由于我们在此示例应用程序中使用了 Stormpath,因此我们将以特定于 Stormpath API 的方式为帐户和组分配权限。

让我们执行一个 cURL 请求,向我们先前创建的 Jean-Luc Picard 帐户添加一些权限。使用该帐户的href URL,我们将通过custom data将一些apacheShiroPermissions发布到该帐户:

curl -X POST --user $YOUR_API_KEY_ID:$YOUR_API_KEY_SECRET \
    -H "Accept: application/json" \
    -H "Content-Type: application/json" \
    -d '{
            "apacheShiroPermissions": [
                "ship:NCC-1701-D:command",
                "user:jlpicard:edit"
            ]
        }' \
"https://api.stormpath.com/v1/accounts/$JLPICARD_ACCOUNT_ID/customData"

其中$JLPICARD_ACCOUNT_ID与您在本教程开始时创建的 Jean-Luc Picard 的 uid 匹配。

这直接向 Stormpath 帐户添加了两个权限:

  • ship:NCC-1701-D:command

  • user:jlpicard:edit

它们使用 Shiro 的WildcardPermission语法。

第一种基本上意味着能够“命令”带有标识符“ NCC-1701-D”的“船” 。这是 instance-level 权限的示例:控制对资源ship的特定 instance * NCC-1701-D的访问。第二个也是实例级权限,它声明能够使用标识符jlpicard edit user *。

如何在 Stormpath 中存储权限以及如何在 Stormpath 中自定义存储和访问选项不在本文档的讨论范围之内,但这在Shiro Stormpath 插件文档中进行了说明。

步骤 7b:权限标签

正如我们有用于角色检查的 JSP 标签一样,也存在用于权限检查的并行标签。我们更新了/home.jsp页,以根据分配给他们的权限让用户知道他们是否被允许做某事。这些消息将添加到主页的新<h2>Permissions</h2>部分中:

<h2>Permissions</h2>

<ul>
    <li>You may <shiro:lacksPermission name="ship:NCC-1701-D:command"><b>NOT</b> </shiro:lacksPermission> command the <code>NCC-1701-D</code> Starship!</li>
    <li>You may <shiro:lacksPermission name="user:${account.username}:edit"><b>NOT</b> </shiro:lacksPermission> edit the ${account.username} user!</li>
</ul>

首次访问主页时,在登录之前,您将看到以下输出:

You may NOT command the NCC-1701-D Starship!
You may NOT edit the user!

但是,使用您的 Jean-Luc Picard 帐户登录后,您将看到以下内容:

You may command the NCC-1701-D Starship!
You may edit the user!

您可以看到 Shiro 解析了经过身份验证的用户具有权限,并且以适当的方式呈现了输出。

您也可以使用<shiro:hasPermission>标记进行权限确认。

最后,我们将引起注意的是一项具有权限检查功能的极其强大的功能。您是否看到第二个权限检查如何使用“运行时”生成的权限值?

<shiro:lacksPermission name="user:${account.username}:edit"> ...

${account.username}值在运行时被解释并形成最终的user:aUsername:edit值,然后将最终的 String 值用于权限检查。

这是“极其”强大的功能:您可以根据当前用户是谁以及“当前正在与谁交互”来执行权限检查。这些基于运行时的实例级权限检查是开发高度可定制且安全的应用程序的基础技术。

步骤 7c:运行 webapp

检出step7分支后,continue 运行 Web 应用程序:

$ mvn jetty:run

尝试访问localhost:8080并使用您的 Jean-Luc Picard 帐户(和其他帐户)登录和注销 UI,然后查看页面输出基于分配(或不分配)权限的更改!

按下ctl-C以关闭该网络应用。

Summary

我们希望您发现启用 Shiro 的 web 应用程序入门教程很有用。在本教程的后续版本中,我们将介绍:

  • 插入不同的用户数据存储区,例如 RDBMS 或 NoSQL 数据存储区。

修复和请求请求

请将勘误表的所有修补程序作为GitHub 拉取请求发送到https://github.com/lhazlewood/apache-shiro-tutorial-webapp存储库。我们非常感谢!!!