使用Apache Shiro保护Web应用程序
参考:https://shiro.apache.org/webapp-tutorial.html
本文档是使用Apache Shiro保护web应用程序的介绍性逐步教程。本文假设您对Shiro有一定的入门知识,并且至少熟悉以下两个入门文档:
这个循序渐进的教程需要大约45分钟到1个小时才能完成。完成后,您将对Shiro在Web应用程序中的工作方式有一个很好的了解。
文章目录
概述
虽然Apache Shiro的核心设计目标允许它用于保护任何基于JVM的应用程序,例如命令行应用程序、服务器守护进程,Web应用程序等,但本指南将重点介绍最常见的用例:保护运行在Servlet容器(如Tomcat或Jetty)中的Web应用程序。
先决条件
为了学习本教程,您需要在本地开发机器上安装以下工具。
- Git (tested w/ 1.7)
- Java SDK 7
- Maven 3
- 您最喜欢的IDE
教程格式
本分步教程及其所有步骤以Git仓库形式存在。克隆Git仓库时,主分支是您的起点。本教程中的每个步骤都是一个单独的分支。您可简单地通过检出反映您正在查看的教程步骤的Git分支来跟着操作。
应用程序
我们将构建的Web应用程序是一个超级Web应用程序,可以用作自己的应用程序的起点。它将演示用户登录、登出、用户特定的欢迎消息、对Web应用程序某些部分的访问控制,以及与可插拔安全数据存储的集成。
我们将从设置项目开始,包括构建工具和声明依赖项,以及配置servlet web.xml文件以启动web应用程序和Shiro环境。
完成设置后,我们将对各个功能进行分层,包括与安全数据存储的集成,然后启用用户登录、注销和访问控制。
项目设置
不必手动设置目录结构和初始基本文件集,我们已经在Git仓库中为您完成了这些工作。
1.Fork教程项目
在GitHub上,访问 教程项目 并单击右上方的Fork按钮,将项目复制一份到自己的GitHub账号下。
2.克隆你的教程仓库
既已将仓库分叉到自己的GitHub帐户,请在您的本地机器上克隆它:
git clone git@github.com:$YOUR_GITHUB_USERNAME/apache-shiro-tutorial-webapp.git
(其中$YOUR_GITHUB_USERNAME当然是您自己的GitHub用户名)
复制完成后可以进入克隆目录并查看项目结构:
cd apache-shiro-tutorial-webapp
3.审查项目结构
克隆仓库后,当前主分支将具有以下结构:
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配置文件。在本教程中,我们选择SLF4 J作为日志API,并选择Logback作为日志实现。这很容易是Log4 J或JUL。
- src/main/webapp/WEB-INF/web.xml:初始web.xml文件,我们将很快配置它以启用Shiro。
- src/main/webapp/include.jsp:包含公共导入和声明的页面,由其他JSP页面引入。这允许我们在一个地方管理导入和声明。
- src/main/webapp/home.jsp:Web应用程序的简单默认主页。
- src/main/webapp/index.jsp:默认站点索引页面—这只是将请求转发到我们的home.jsp主页。
4.运行Web应用
克隆项目后,可执行以下命令来运行Web应用程序:
mvn jetty:run
接下来,打开浏览器到 localhost:8080,您将看到带有Hello, World!的问候。按Ctrl+C可关闭Web应用程序。
步骤1:启用Shiro
我们最初的仓库主分支只是一个简单的通用Web应用程序,可以用作任何应用程序的模板。接下来,让我们添加最低限度以在Web应用程序中启用Shiro。执行以下Git签出命令以加载step1分支:
git checkout step1
检出此分支,您会发现两个更改:
- 一个新的src/main/webapp/WEB-INF/shiro.ini文件被添加
- src/main/webapp/WEB-INF/web.xml被修改
1a: 添加一个shiro.ini文件
Shiro可以在Web应用程序中以许多不同的方式进行配置,具体取决于您使用的Web和MVC框架。例如,您可以通过Spring、Guice、Tapestry等配置Shiro。
简单起见,我们将使用Shiro默认的(非常简单的)基于ini的配置来启动Shiro环境。
如果您签出了step1分支,您将看到这个新的src/main/webapp/WEB-INF/shiro.ini文件的内容(为简洁起见,删除了标题注释):
[main]
# 让我们使用一些内存缓存来减少对远程用户存储的运行时查找次数。
# 真实的应用程序可能想要使用更健壮的缓存解决方案(例如ehcache或分布式缓存)。
# 使用缓存时,要注意缓存TTL设置:TTL过高,缓存将不能足够快地反映Stormpath中的任何潜在变化;
# 如果太低,缓存可能会频繁地清除,从而降低性能。
cacheManager = org.apache.shiro.cache.MemoryConstrainedCacheManager
securityManager.cacheManager = $cacheManager
这个.ini仅包含一个具有一些最小配置的 [main] 部分:
- 它定义了一个新的缓存管理器实例。缓存是 Shiro 架构的重要组成部分—它减少了与各种数据存储的持续往返通信。本例使用MemoryConstrainedCacheManager,它只适用于单个JVM应用程序。如果你的应用程序部署在多个主机上(例如,一个集群的web服务器场),你应该使用集群缓存管理器实现。
- 它在Shiro安全管理器(securityManager)上配置新的缓存管理器(cacheManager)实例。Shiro SecurityManager实例始终存在,因此不需要显式地定义它。
1b: 在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>
- 声明定义了一个ServletContextListener,它在web应用程序启动时启动Shiro环境(包括Shiro SecurityManager)。默认情况下,此侦听器会自动知道查找WEB-INF/shiro.ini文件以进行Shiro配置。
- 声明定义了主要的ShiroFilter。这个过滤器将过滤所有进入web应用程序的请求,因此Shiro可以在允许请求到达应用程序之前执行必要的身份识别和访问控制操作。
- 声明确保所有请求类型都由ShiroFilter提交。通常filter-mapping声明不指定元素,但是Shiro需要定义所有这些元素,这样它才能过滤可能为web应用程序执行的所有不同的请求类型。
1c: 运行Web应用
签出step1分支后,继续运行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.
按Ctrl+C以关闭Web应用程序。
步骤2:连接到用户存储
执行以下git checkout命令来加载step2分支:
git checkout step2
现在,我们已经在Web应用程序中集成并运行了Shiro。但实际上我们还没有告诉Shiro做任何事情。
在登录、登出、执行基于角色或基于权限的访问控制或任何其他与安全性相关的操作之前,我们需要用户。
我们需要配置Shiro以访问某种类型的用户存储,这样它就可以查找用户以执行登录尝试,或检查角色以进行安全决策等。许多类型的用户存储可能被应用程序访问:也许您将用户存储在MySQL数据库中,也许在MongoDB中,也许您的公司将用户帐户存储在LDAP或Active Directory中,也许您将它们存储在一个简单的文件或其他一些专有数据存储中。
Shiro通过所谓的Realm来做到这一点。Shiro文档对其描述如下:
Realms充当Shiro和应用程序安全数据之间的桥梁或连接器。当需要实际与安全相关数据(如用户帐户)交互以执行身份验证(登录)和授权(访问控制)时,Shiro从为应用程序配置的一个或多个Realm中查找这些内容。
从这个意义上讲,Realm本质上是特定于安全的DAO:它封装了数据源的连接细节,并根据需要将相关数据提供给Shiro。配置Shiro时,您必须指定至少一个用于身份验证和授权的Realm。SecurityManager可以配置多个Realm,但至少需要一个。
Shiro提供了开箱即用的Realm来连接到许多安全数据源(又称目录),例如 LDAP、关系数据库(JDBC)、文本配置源(如 INI 和属性文件)等。如果默认的Realm不能满足您的需求,您可以插入自己的Realm实现来表示自定义数据源。
因此,我们需要配置一个Realm,以便能够访问用户。
2a: 设置Stormpath
本着使本教程尽可能简单的精神,不引入复杂性或使我们偏离学习Shiro目的的范围,我们将使用最简单的Realm之一:Stormpath。
Stormpath是一项云托管用户管理服务,完全免费用于开发目的。这意味着启用Stormpath后,您将准备好以下内容:
- 用于管理应用程序、目录、帐户和组的用户界面。Shiro根本不提供这个功能,因此在您学习本教程时,这将非常方便,并且可以节省时间。
- 用户密码的安全存储机制。您的应用程序永远不需要担心密码安全、密码比较或存储密码。虽然Shiro可以做这些事情,但您必须配置它们并了解加密概念。Stormpath自动执行密码安全,所以你(和Shiro)不必担心,也不需要为“正确”而烦恼。
- 安全工作流程,例如通过电子邮件进行帐户邮件验证和密码重置。Shiro对此不支持,因为它通常是特定于应用程序的。
- 托管/管理的“始终在线”基础架构—您无需设置或维护任何内容。
就本教程而言,Stormpath要比设置单独的RDBMS服务器和担心SQL或密码加密问题简单得多。所以我们现在就用它。
当然,Stormpath只是Shiro可以与之通信的众多后端数据存储之一。稍后我们将介绍更复杂的数据存储及其特定于应用程序的配置。
注册Stormpath
- 填写并提交Stormpath注册表。这将发送一封确认邮件。
- 点击确认邮件中的链接。
获取一个Stormpath API密钥
Stormpath Realm需要Stormpath API密钥才能与Stormpath通信。要获取Stormpath API密钥,请执行:
- 使用注册Stormpath时使用的电子邮件地址和密码登录Stormpath管理控制台。
- 在结果页面的中间偏右,访问API密钥:在页面的“开发人员工具”部分中管理API密钥。
- 在“帐户详细信息”页面的“安全凭据”部分中,单击“API密钥”下的“创建API密钥”。
这将生成您的API密钥并将其作为apiKey.properties文件下载到您的计算机。如果在文本编辑器中打开文件,您将看到类似下面的内容:
-
将此文件保存在一个安全的位置,如隐藏的.stormpath目录中的主目录,例如
$HOME/.stormpath/apiKey.properties
-
此外,更改文件权限以确保只有您可以读取此文件。例如,在 *nix 操作系统上:
chmod go-rwx $HOME/.stormpath/apiKey.properties chmod u-w $HOME/.stormpath/apiKey.properties
在Windows上,您可以类似地设置文件权限。
检索默认Stormpath应用程序
当您注册Stormpath时,会自动为您创建一个空应用程序。它被称为:My Application。
我们必须向Stormpath注册我们的Web应用程序,以允许应用程序使用Stormpath进行用户管理和身份验证。为了向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'
其中:
- $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
注意Location标头。这是您的Stormpath租户的位置。现在,我们可以再次使用API检索My Application Stormpath应用程序的位置:
curl -u $API_KEY_ID:$API_KEY_SECRET \
-H "Accept: application/json" \
'$TENANT_HREF/applications?name=My%20Application'
其中:
- $YOUR_API_KEY_ID是apiKey.properties中的apiKey.id的值
- $YOUR_API_KEY_SECRET是apiKey.properties中的apiKey.secret的值
- $TENANT_HREF是上一步中Location标头的值
由此产生的响应中包含很多信息。下面是响应的示例摘录:
{
...
"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。
创建一个应用程序测试用户帐户
现在我们有一个应用程序,我们需要为该应用创建一个示例/测试用户:
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"
其中:$YOUR_APPLICATION_HREF是您之前记下的应用程序href。
同样,不要忘记更改上面URL中的$YOUR_APPLICATION_ID以匹配你的应用ID!
2b: 在shiro.ini中配置Realm
一旦为Shiro的需求选择了至少一个要连接的用户存储,我们就需要配置一个表示该数据存储的Realm,然后将其告知Shiro SecurityManager。
如果您已签出step2分支,你将注意到src/main/webapp/WEB-INF/shiro.ini文件的[main]部分现在添加了以下内容:
[main]
cacheManager = org.apache.shiro.cache.MemoryConstrainedCacheManager
securityManager.cacheManager = $cacheManager
stormpathClient = com.stormpath.shiro.client.ClientFactory
stormpathClient.cacheManager = $cacheManager
stormpathRealm = com.stormpath.shiro.realm.ApplicationRealm
stormpathRealm.client = $stormpathClient
# (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属性必须设置。
2d: 运行webapp
在步骤2b和2c中指定的更改执行后,继续并运行web应用程序:mvn jetty:run 。
这一次,您将看到如下所示的日志输出,表明Shiro和新Realm在您的Web应用程序中已正确配置。
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.
按Ctrl+C以关闭Web应用程序。
步骤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
# 为简洁起见,这里省略了之前在此处配置的内容
[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和过滤器链的更深入介绍超出了本文档的范围,但如果您有兴趣,请阅读有关它的更多信息。
现在,我们将介绍添加的两行:
/login.jsp = authc
/logout = logout
- 第一行表示“每当Shiro看到对/login.jsp的请求时,在请求期间启用Shiro authc过滤器”。
- 第二行表示“每当Shiro看到对/logout的请求时,在请求期间启用Shiro登出过滤器”。
这两个过滤器都有点特殊:它们实际上不需要任何“背后”的东西。它们实际上只是处理整个请求,而不是过滤。这意味着您无需为对这些URL的请求执行任何操作—无需编写controllers!Shiro将根据需要处理这些请求。
3b: 添加登录页面
因为步骤3a启用了登录和登出支持,现在我们需要确保实际上有一个/login.jsp页面来显示登录表单。
step3分支包含一个新的src/main/webapp/login.jsp页面。这是一个足够简单的bootstrap主题HTML登录页面,但其中有四个重要的内容:
- form表单的action值是空字符串。当表单没有action值时,浏览器会将表单请求提交到相同的URL。这没问题,因为我们将很快告诉Shiro URL是什么,这样Shiro就可以自动处理任何登录提交。shiro.ini中的/login.jsp = authc行告诉authc过滤器处理提交。
- 有一个username表单字段。Shiro authc过滤器将在登录提交时自动查找username请求参数并在登录期间使用它作为值(许多Realm允许这是一个电子邮件或用户名)。
- 有一个password表单字段。Shiro authc过滤器将在登录提交时自动查找password请求参数。
- 有一个“记住我”复选框,其选中状态可以是truthy值(true, t, 1, enabled, y, yes, or on)。
我们的login.jsp表单只用到了默认的username, password和rememberMe表单字段名。如果您希望更改这些名称,则它们是可配置的—有关信息请参阅FormAuthenticationFilter的JavaDoc。
3c: 运行Web应用
做出步骤3b和3c中指定的更改后,继续运行 Web 应用:
mvn jetty:run
3d: 尝试登录
使用浏览器导航至localhost:8080/login.jsp,你将看到新的闪亮的登陆表单。
输入您在步骤2结束时创建的帐户的用户名和密码,然后点击“登录”。如果登录成功,您将被定向到主页!如果登录失败,您将再次看到登录页面。
提示:如果您希望成功登录后将用户重定向到主页以外的其他页面(上下文路径 /),您可以在INI文件的[main]部分中设置authc.successUrl = /whatever。
按Ctrl+C以关闭Web应用程序。
步骤4:特定于用户的UI更改
通常需要根据用户身份更改Web用户界面。我们可以很容易地做到这一点,因为Shiro支持JSP标签库来根据当前登录的主体(用户)执行操作。
执行以下git checkout命令来加载step4分支:
git checkout step4
此步骤给home.jsp页面附加了一些东西:
- 当查看页面的当前用户未登录时,他们将看到“Welcome Guest”消息和指向登录页面的链接。
- 当查看页面的当前用户已登录时,他们将看到自己的姓名、“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访客和用户标签
home.jsp文件在页面主体中(就在<h1>欢迎信息之后)被进一步修改,以包含<shiro:guest>和<shiro:user>标签。
<p>Hi <shiro:guest>Guest</shiro:guest><shiro:user>
<%
//这永远不应该在普通页面中完成,而应该存在于某种类型的适当MVC控制器中,但对于本教程,
//我们将从Shiro的PrincipalCollection中提取Stormpath帐户数据,以便在<c:out/>标签中引用。
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访客定义为未登录应用程序或未从先前登录中记住(使用Shiro的remember me功能)的任何Subject。
- <shiro:user>:如果当前Shiro Subject是应用程序user,则此标签只显示其内部内容。Shiro将user用户定义为当前登录到应用程序或从先前登录中记住(使用Shiro的remember me功能)的任何Subject。
如果Subject是guest,则上面的代码片段将呈现以下内容:
Hi Guest! (Log in)
其中“Log in”是指向/login.jsp的超链接。
如果Subject是user,它将呈现如下内容:
Hi jsmith! (Log out)
'jsmith’是登录帐户的用户名。“Log out”是指向由Shiro logout过滤器处理的/logout URL的超链接。
如您所见,您可以关闭或打开整个页面部分、功能和UI组件。除了<shiro:guest>和<shiro:user>,Shiro支持许多其他有用的JSP标签,您可以使用这些标签来基于当前Subject的各种已知信息定制UI。
4c: 运行Web应用
签出step4分支后,继续运行Web应用程序:
mvn jetty:run
尝试以访客身份访问localhost:8080,然后登录。成功登录后,您将看到页面内容发生变化,以反映您现在是一个已知用户。
按Ctrl+C以关闭Web应用程序。
步骤5:只允许通过认证的用户访问
虽然您可以根据Subject状态更改页面内容,但通常情况下,您会希望根据某人在当前与Web应用程序交互期间是否已证明其身份来限制Web应用程序的整个部分。
如果Web应用程序的用户专用部分显示敏感信息,如账单明细或控制其他用户的能力,这一点尤其重要。
执行以下git checkout命令来加载step5分支:
git checkout step5
步骤5引入了以下3个变更:
- 我们添加了Web应用的新部分(网址路径),我们希望将其限制为仅经过身份验证的用户。
- 我们更改了shiro.ini,告诉Shiro只允许经过身份验证的用户访问Web应用程序的该部分。
- 我们修改了主页,以根据当前Subject是否经过身份验证来更改其输出。
5a: 添加一个新的限制部分
添加了一个新的src/main/webapp/account目录。此目录(及其下的所有路径)模拟网站的“私有”或“仅经过身份验证”部分,您可能希望将其限制为仅登录用户。src/main/webapp/account/index.jsp文件只是模拟“home account”页面的占位符。
5b: 配置shiro.ini
shiro.ini通过在[urls]部分的末尾添加以下一行来修改:
/account/** = authc
这个Shiro过滤器链定义意味着“对/account(或其子路径)的任何请求都必须进行身份验证”。
但是如果有人试图访问这个路径或者它的子路径会发生什么?
你还记得在步骤3中,我们向[main]部分添加了下面的行吗:
shiro.loginUrl = /login.jsp
此行使用应用login URL自动配置了authc过滤器。
基于这一行配置,authc过滤器现在足够智能,知道如果在访问/account时当前Subject未通过身份验证,它会自动将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在当前会话期间已登录时显示内容。这就是Subject知道他们能否访问网站新部分的方式。
<shiro:notAuthenticated>标签仅在当前Subject在当前会话期间尚未进行身份验证时才会显示内容。
但是您是否注意到未经过身份验证的内容仍然具有指向/account部分的URL?没关系—我们的authc过滤器将如上所述处理登录然后重定向流。
用新的变化启动web应用程序并尝试一下。
5d: 运行Web应用
签出step5分支后,继续运行web应用程序:
mvn jetty:run
尝试访问localhost:8080。点击新的/account链接并观察它重定向您以强制您登录。登录后,返回主页,并在认证后再次查看内容更改。您可以随时访问account页面和主页,直到注销为止。
按Ctrl+C以关闭Web应用程序。
步骤6:基于角色的访问控制
除了基于身份验证控制访问,通常需要根据分配给当前Subject的角色来限制对应用程序某些部分的访问。
执行以下git checkout命令来加载step6分支:
git checkout step6
6a: 添加角色
为了执行基于角色的访问控制,我们需要角色存在。
在本教程中,最快的方法是在Stormpath中填充一些Group(在Stormpath中,一个Stormpath Group可以起到与角色相同的作用)。
为此,请登录到UI并按如下方式导航:
Directories > My Application Directory > Groups
添加以下三个组:
- Captains
- Officers
- Enlisted
(与我们的Star-Trek主题保持一致)
创建组后,将Jean-Luc Picard帐户添加到Captains和Officers组。您可能希望创建一些特别帐户,并将其添加到您喜欢的任何组中。请确保某些帐户不与组重叠,以便可以根据分配给用户帐户的单独组查看更改。
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<br/></shiro:hasRole>
<shiro:hasRole name="Officers">Bad Guys<br/></shiro:hasRole>
<shiro:hasRole name="Enlisted">Enlisted<br/></shiro:hasRole>
</p>
<h3>Roles you DON'T have:</h3>
<p>
<shiro:lacksRole name="Captains">Captains<br/></shiro:lacksRole>
<shiro:lacksRole name="Officers">Officers<br/></shiro:lacksRole>
<shiro:lacksRole name="Enlisted">Enlisted<br/></shiro:lacksRole>
</p>
<shiro:hasRole>标签仅在当前Subject被分配了指定角色时才会显示内容。
<shiro:lacksRole>标签仅在当前Subject未被分配指定角色时才显示内容。
6c: RBAC过滤器链
留给读者的练习(不是定义的步骤)是创建网站的新部分,并根据分配给当前用户的角色限制对网站该部分的URL访问。
提示:使用角色过滤器为Web应用的新部分创建一个过滤器链定义。
6d: 运行Web应用
签出step6分支后,继续运行Web应用程序:
mvn jetty:run
尝试访问localhost:8080并使用分配了不同角色的不同用户帐户登录,并观察主页的角色部分内容的变化!
按Ctrl+C以关闭Web应用程序。
步骤7:基于权限的访问控制
基于角色的访问控制适用于许多用例,但它存在一个主要问题:不能在运行时添加或删除角色。角色检查使用角色名称进行硬编码,因此,如果您更改了角色名称或角色配置,或添加或删除角色,你必须回去修改你的代码!
因此,Shiro有一个强大的侯爵特性:内置权限支持。在Shiro中,权限是功能的原始声明,例如“打开一扇门”、“创建一个博客条目”、“删除jsmith用户”等。权限反映应用程序的原始功能,因此只需在更改应用程序的功能时更改权限检查,而不需要在更改角色或用户模型时更改。
为了演示这一点,我们将创建一些权限并将其分配给一个用户,然后根据用户的授权(权限)自定义Web UI。
7a: 添加权限
Shiro Realm是只读组件:每个数据存储都以不同的方式对角色、组、权限、帐户及其关系进行建模,所以Shiro没有一个write API来修改这些资源。要修改底层模型对象,只需通过您想要的任何API直接修改它们。然后,您的Shiro Realm知道如何读取这些信息,并以Shiro理解的格式表示这些信息。
由于我们在这个示例应用程序中使用了Stormpath,我们将以特定于Stormpath API的方式为帐户和组分配权限。
让我们执行一个cURL请求,向之前创建的Jean-Luc Picard帐户添加一些权限。使用该帐户的href URL,我们将通过自定义数据发布一些Shiro权限到帐户。
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的通配符权限语法。
第一个基本上意味着控制(command)标识为NCC-1701-D的船(ship)的能力。这是实例级权限的示例:控制对资源船的特定实例NCC-1701-D的访问。第二个也是一个实例级权限,它声明了编辑标识为jlpicard的用户的能力。如何在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值,然后最终的字符串值被用于权限检查。
这是非常强大的:您可以根据当前用户是谁以及当前正在与什么进行交互来执行权限检查。这些基于运行时的实例级权限检查是开发高度可定制且安全的应用程序的基本技术。
7c: 运行Web应用
签出step7分支后,继续运行Web应用程序:
mvn jetty:run
尝试访问 localhost:8080 并使用您的Jean-Luc Picard帐户(和其他帐户)登录和退出UI,并根据所分配的权限(或未分配的权限)查看页面输出的变化。
按Ctrl+C以关闭Web应用程序。
总结
我们希望这篇关于启用Shiro的Web应用程序的入门教程对您有用。在本教程的后续版本中,我们将介绍:
- 插入不同的用户数据存储,如RDBMS或NoSQL数据存储。
修复和拉取请求
请将任何勘误修复作为GitHub Pull请求发送到 https://github.com/lhazlewood/apache-shiro-tutorial-webapp 仓库。我们会非常感激!!!