Securing Web Applications with Apache Shiro[译]

2017-10-26
Apache

这篇文章是介绍使用Shiro一步一步保护你的web应用的教程。它包含了Shiro的知识,以及和最近两篇文章很相似:

  • Application Security with Apache Shiro
  • Apache Shiro 10 Minute Tutorial
    这个入门教程会花费大约45分钟到一个小时。当你读完后,你就会知道Shiro在web应用中怎么工作了的。

    目录

  • 概括
  • 设置
  • 第一步:开启Shiro
  • 第二步:连接到用户存储
  • 第三步:开启登录和登出
  • 第四步:改变用户指定UI
  • 第五步:仅仅允许认证用户访问
  • 第六步:基于角色访问控制
  • 第七步:基于权限访问控制

概括

虽然Shiro设计的核心目标可以用于保护任何基于JVM的应用,例如命令行应用,后台服务,网页应用等。这篇文章专注最常见的应用:保护跑在Servlet容器上的web应用,例如Tomca或者Jetty。

先决条件

下面的工具是要安装到你本地开发机器上的,以便遵循本教程。

  • Git (tested w/ 1.7)
  • Java SDK 7
  • Maven 3
  • 你自己喜欢的编辑器,IDEA或者Eclipse都行,甚至文本编辑器也行。

教程格式

这是一步接着一步的教程。该教程和所有步骤都在GIT仓库中存着。当你clone git仓库,master分支就是你的起点。在这个教程中的每个步骤都是单独的分支。你可以通过检出你正在看的教程步骤的git分支来读这个教程(就是边看代码边读这个教程的意思)。

应用

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

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

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

设置

我们已经在git仓库中设置完成了,因此你不必手动去设置目录结构初始化文件等。

1. Fork the tutorial project

在github中访问这个,点击fork按钮。

2. Clone your tutorial repository

现在你把那个仓库fork到你自己的账号中去了,clone到你自己的机器:

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

3. Review project structure

完成clone后,你当前的master分支将有下面的结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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 不解释
  • README.md 描述文件
  • LICENSE 证书
  • .travis.yml CI的配置文件,假如你想持续集成
  • .gitignore 忽略文件,包含后缀。你不想加入版本控制的都能写这里
  • src/main/resources/logback.xml 简单的Logback配置文件,在这个教程中,我们选择SELF4J作为日志API,Logback作为实现。这个比Log4J和JUL简单。
  • src/main/webapp/WEB-INF/web.xml web配置文件
  • src/main/webapp/include.jsp 公用的JSP文件,用于导入和声明。方便在一个地方管理。
  • src/main/webapp/home.jsp 默认的homepage
  • src/main/webapp/index.jsp 默认主页,仅仅是将请求指向home.jsp

4. Run the webapp

现在你将工程克隆到本地了,你可以通过使用一下命令来跑这个web应用:

1
$ mvn jetty:run

然后打开你的浏览器,输入 localhost:8080 你可以看到主页显示的Hello,World!按ctl-c停止web。

Step 1: Enable Shiro

我们初始化仓库master分支仅仅是一个简单通用的可用于任何应用的模板的web应用。Let’s add the bare minimum to enable Shiro in the web app next(这句话不知道怎么翻译)。

执行下面的git检出命令加载step1的分支:

1
$ git checkout step1

检出后你会发现两个变化:

  1. 多了src/main/webapp/WEB-INF/shiro.ini这个文件
  2. src/main/webapp/WEB-INF/web.xml被改了

添加shiro.ini文件

在web应用中油很多种方式配置Shiro,这取决于你使用的是什么web框架。例如,你可以通过Spring、Guice、Tapestry等配置Shiro。

现在为了方便,我们将使用Shiro默认的配置,基于ini的配置

如果你检出了step1的分支,你可以看到这个新的配置文件src/main/webapp/WEB-INF/shiro.ini

1
2
3
4
5
6
7
8
9
[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应用很棒。如果你的应用部署在多个主机,你最好使用集群的CacheManager 实现。
  • 它在Shiro securityManager上配置新的cacheManager实例。Shiro SecurityManager实例始终存在,因此不需要明确定义。

在web.xml中开启Shiro

虽然我们有shiro.ini配置文件,但是我们确实需要把它加载进去然后开启Shiro环境让web应用能够使用它。

我们在src/main/webapp/WEB-INF/web.xml配置文件中添加这些东西:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<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环境。默认这个listener会去自动找WEB-INF/shiro.ini配置文件。
  • <filter>定义了一个主要的ShiroFilter。这个过滤器会去过滤所有的请求,这样Shiro能够在允许请求到达应用程序之前执行必要的身份和访问控制操作。

跑起来

拉去了分支后,继续跑起来:

1
$ mvn jetty:run

这个时候,你会看到下面熟悉的输出,这表示Shiro确实在你的web应用中跑起来了。

1
2
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.

Step 2: Connect to a User Store

执行下面的命令检出step2的代码:

1
$ git checkout step2

现在我们将Shiro集成并运行在webapp中.但是我们还没有告诉Shiro要做什么事情。

在我们登录,退出或执行基于角色或权限的访问控制或任何其他安全相关之前,我们需要用户!

我们需要配置Shiro来访问某种类型的用户存储,这样它可以查找用户做登录操作或者检查角色等等。有许多类型的用户存储,任何应用程序可能需要访问:也许你将用户存在MySql数据库中,或者MongoDB,或者LDAP,或者Active Directory,或者简单的文件中。

Shiro将这叫做Realm.来自Shiro的文档:

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这样我们就能够得到用户了。

Set up Stormpath

本着越简单越好的原则,没有介绍Shiro的复杂性,我们使用一个最简单的realm的实现:Stormpath realm。

Stormpath 是一个云平台上的用户管理服务。对开发者免费。这意味着使用了Stormpath以后,帮你做了做以下的事情:

  • 一个用于管理应用,目录、账户、和组的接口。Shiro根本不提供这些东西,因此当你浏览这篇教程的时候能够节约时间,这是很方便的。
  • 一个为用户存储密码的安全机制。你的应用不必担心密码安全,密码比较或存储密码。虽然Shiro能做这些事情,但是你有必要去配置一下,然后注意一下加密的概念。 Stormpath自动的对密码进行保护,因此你不必担心这些东西。
  • 安全的工作流,类似于邮箱验证密码重置。Shiro对此没有任何支持,因为这是应用程序来把控的。
  • 主机托管总是在基础设施上的(云平台),你没有必要来维护这些东西。

为了本教程的目的,Stormpath比设置单独的RDBMS服务器要简单得多,并且担心SQL或密码加密问题。

当然,Stormpath只是Shiro可以进行沟通的许多后端数据存储之一。 稍后将介绍更复杂的数据存储和应用程序特定的配置。

Sign up for Stormpath

blabla…..

Get a Stormpath API Key

Stormpath API Key对于Stormpath来说是必须的。可以这样获取:

  1. 登录到Stormpath中(这个网站关了,合并到okta)。
  2. 页面的右边,访问API KEY:Manage API KEY
  3. 在账号详情页面,点击Create API Key。此时会生成你的API key,然后下载到你的电脑。如果你打开看看:

    1
    2
    apiKey.id = 144JVZINOF5EBNCMG9EXAMPLE
    apiKey.secret = lWxOiKqKPNwJmSldbiSkEbkNjgh2uRSNAb+AEXAMPLE
  4. 保存到一个安全的地方,例如:

    1
    $HOME/.stormpath/apiKey.properties
  5. 当然也可以改一下这个文件的权限。例如在*nix下:

    1
    2
    $ chmod go-rwx $HOME/.stormpath/apiKey.properties
    $ chmod u-w $HOME/.stormpath/apiKey.properties
Retrieve the default Stormpath Application

当你登录到Stormpath,自动的创建了一个空的应用,名字叫做:My Application

我们必须使用Strompath注册我们的web应用,这样就能使用Strompath来管理我们的用户和对用户授权。为了使用我的应用程序Stormpath应用程序注册我们的Web应用程序,我们需要了解一些信息。幸运的是,我们可以使用Stormpath API来检索这些信息。

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

你能得到这些返回信息:

1
2
3
4
5
6
7
8
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 tenant.

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

这个回应有很多的信息。 这是一个从响应中摘录的例子.

1
2
3
4
5
6
7
8
9
10
11
{
...
"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

Create an application test user account

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

1
2
3
4
5
6
7
8
9
10
11
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"

Configure the Realm in shiro.ini

按照Shiro的要求,你选择至少一个用户存储的连接,我们将需要配置一个表示该数据存储的域,然后告诉Shiro SecurityManager。

如果你检出step2的分支,你会看到src/main/webapp/WEB-INF/shiro.ini中的[main]部分:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 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这个属性必须被设置。

Run the webapp

在完成上面的改动后,使用下面的命令来跑一下:

1
$ mvn jetty:run

这时候就会看到熟悉的输出:

1
2
3
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.

Step 3: Enable Login and Logout

现在我们有用户,而且我们能很容易的添加,移除和禁用他们。现在我们可以在我们的应用程序中开始启用登录/注销和访问控制等功能。

检出step3的分支:

1
$ git checkout step3

添加了2个东西:

  • src/main/webapp/login.jsp中加了一个简单的登录表单,我们使用它登录。
  • shiro.ini文件已更新,以支持Web(URL)特定功能。

Enable Shiro form login and logout support

step3中src/main/webapp/WEB-INF/shiro.ini添加了下面2个东西:

1
2
3
4
5
6
7
8
9
[main]
shiro.loginUrl = /login.jsp
# Stuff we've configured here previously is omitted for brevity
[urls]
/login.jsp = authc
/logout = logout

在[main]这个部分多了一行:

1
shiro.loginUrl = /login.jsp

这是一个告诉Shiro的特殊配置指令:”对于任何具有loginUrl属性的Shiro的默认过滤器,我希望该属性值设置为/login.jsp。”

这个就是让Shiro的默认认证过滤器(默认的是FormAuthenticationFilter)知道登录页面是哪个。这个让FormAuthenticationFilter正确的工作是很有必要的。

[urls]部分允许你使用非常简洁的键值对语法来告诉Shiro怎么样去过滤给定的url请求。urls中的所有路径都是相对于Web应用程序的HttpServletRequest.getContextPath()的值.

这些键值对提供一个极其强大的方式来过滤请求,允许各种各样的安全规则。URL和过滤器链的更深入的覆盖范围超出了本文档的范围,但如果你有兴趣,请详细阅读。

1
2
/login.jsp = authc
/logout = logout
  • 第一行指出不论Shiro什么时候遇到请求/login.jsp的url,在请求中开启authc过滤器。
  • 第二行表示不论Shiro遇到/login.jsp的url,在请求中开启logout 过滤器。

这两个过滤器都有一点特殊的地方,他们都不需要在背后进行额外的处理。它们实际上只是完全处理请求,而不是过滤。这意味着你对这些URL的请求没有任何要求,不需要写任何controller。Shiro将根据需要处理这些请求。

Add a login page

在上个步骤中我们开启了对登录和登出的支持。现在我们需要保证有一个/login.jsp页面来显示登录表单。

step3的分支中包含src/main/webapp/login.jsp页面。这是一个简单的bootstrap主题的登录页面,但是依旧有四点需要注意:

  • 这个表单的action值是空的字符串。当表单中没有action值,浏览器将提交请求到同一个URL。这很好,因为我们会告诉Shiro那个URL很短,所以Shiro可以自动处理任何登录提交。/login.jsp = authc这行是告诉authc过滤器处理这个提交。
  • 有一个username字段,Shiro authc过滤器将在登录提交期间自动查找用户名请求参数,并将其用作登录期间的值(许多域允许此为电子邮件或用户名)。
  • 有一个password字段,Shiro authc过滤器将在登录提交期间自动查找密码请求参数。
  • 有一个rememberMe复选框,其“checked”状态可以是“true”值(true,t,1,enabled,y,yes或on)。

我们的login.jsp表单只使用默认的用户名,密码和rememberMe表单字段名称。 如果您想更改这些名称,这些名称是可配置的。参见FormAuthenticationFilter.

Run the webapp

再跑一次:

1
$ mvn jetty:run

Try to Login

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

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

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

Step 4: User-specific UI changes

通常需要根据用户是谁来更改web用户界面。我们可以很容易地做到这一点,因为Shiro支持JSP标签库根据当前登录的Subject(用户)进行操作。

检出step4的分支:

1
$ git checkout step4

home.jsp页面中新增了:

  • 当浏览该页面的当前用户未登录时,他们将看到一个“欢迎访客”消息,并查看登录页面的链接。
  • 当浏览该页面的当前用户登录后,他们将看到自己的名字“欢迎xxx”和一个链接以注销。

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

Add the Shiro Tag Library Declaration

home.jsp顶部包含了这两行:

1
2
<%@ 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 )标签库。

Add Shiro Guest and User tags

home.jsp文件进一步修改了body里的内容,以包含标签。

1
2
3
4
5
6
7
8
9
10
11
12
13
<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>这个标签仅仅显示它内部的内容,假如当前的subject是一个访客的话。Shiro将guest定义为未登录到应用程序的任何Subject,或者在之前登录没有被记住的用户。
  • <shiro:user>Shiro将用户定义为当前登录到(认证)应用程序或从先前登录记录的主题的任何主题。

如果subject是访客,上述代码段将呈现以下代码:

1
Hi Guest! (Log in)

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

1
Hi jsmith! (Log out)

你可以看到,您可以关闭整个页面部分,功能和UI组件。
除了这两个标签,Shiro还提供其他标签供你来定制你自己的UI。

Run the webapp

跑吧!

Step 5: Allow Access to Only Authenticated Users

虽然你可以根据subject的状态来改变内容,但是通常情况下,你将要限制webapp的整个部分,这取决于有人在当前与Web应用程序的互动过程中是否已证明其身份(已验证)。

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

检出step5:

1
$ git checkout step5

Step 5有3个更新:

  • 添加了一个新的部分(url path),我们要限制只有经过身份验证的用户。
  • 我们更改了shiro.ini,以告诉Shiro只允许经过身份验证的用户访问该Web应用程序的该部分。
  • 我们修改了主页,根据当前的主题是否被认证来更改其输出。

Add a new restricted section

src/main/webapp/account目录被添加进来了。此目录(及其下方的所有路径)会模拟网站的“私有”或“仅验证”部分,你可能希望将其限制为只能登录用户。src/main/webapp/account/index.jsp文件只是模拟“主页”页面的占位符。

Configure shiro.ini

shiro.ini[urls]这个章节的结尾,添加了下面的一行:

1
/account/** = authc

这个Shiro过滤器链定义意味着“对/ account(或其任何子路径)的任何请求必须被认证”。
但是,如果有人尝试访问该路径或其任何子路径,会发生什么?
你还记得在步骤3中,当我们将以下行添加到main部分吗?

1
shiro.loginUrl = /login.jsp

这一行自动使用我们的webapp登录URL配置authc过滤器。
基于这一行配置,authc过滤器现在足够聪明,可以知道当访问/帐户当前主题未被验证时,它将自动将主题重定向到/login.jsp页面。成功登录后,它将自动将用户重定向到他们试图访问的页面(/帐户)。 很方便!

Update our home page

步骤5的最终更改是更新/home.jsp页面,让用户知道他们可以访问网站的新部分。这些行被添加到欢迎消息下:

1
2
3
4
<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>

如果当前的Subject在当前会话期间已经登录(认证),则标签内的内容被显示。这是Subject知道他们可以去访问网站的新部分。
如果当前Subject在当前会话期间尚未验证,则标签内的内容被显示。
但是你注意到了吗,未经身份验证的内容仍然具有/ account部分的URL?没关系 - 我们的authc过滤器将如上所述处理login-and-then-redirect流程。
用新的更改启动webapp并尝试一下!

Run the webapp

跑一下吧。

Step 6: Role-Based Access Control

除了基于身份验证控制访问之外,还经常需要根据分配给当前主体的角色来限制对应用程序某些部分的访问。
检出代码:

1
$ git checkout step6

Add Roles

为了执行基于角色的访问控制,我们需要有角色。
在本教程中最快的方法是在Stormpath中添加一些组。要做到这一点,登录到UI并导航如下:
Directories > My Application Directory > Groups
添加下面三个组:

  • Captains
  • Officers
  • Enlisted

创建组后,将Jean-Luc Picard帐户添加到Captains和Officers组。您可能需要创建一些临时帐户,并将它们添加到您喜欢的任何组;确保某些帐户不重叠组,因此你可以基于分配给用户帐户的单独组查看变更。

Role Based Access Control (RBAC) Tags

我们更新/home.jsp页面,让用户知道他们有什么角色,哪些角色没有。这些消息将添加到主页的新的<h2>角色</ h2>部分:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<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>

如果当前主体被分配了指定的角色,标签将显示其中的内容。标签只会在当前主体未分配指定角色时显示其中的内容。

RBAC filter chains

向读者留下的一个练习(不是一个定义的步骤)是根据分配给当前用户的角色,创建网站的一个新的部分,并限制URL访问该网站的该部分。
提示:使用 filter chain definition为webapp的新部分创建过滤器链定义。

Run the webapp

run一下咯

Step 7: Permission-Based Access Control

基于角色的访问控制对于许多情况是行得通的,但是它存在一个主要问题:你不能在运行时添加或删除角色。角色检查使用角色名称进行硬编码,所以如果你改变了角色名称或角色配置,或添加或删除角色,你必须回去更改代码!
因此,Shiro具有强大的功能:内置的权限支持。
在Shiro,权限是一个原始的功能说明,例如“打开门”,创建博客条目“,”删除jsmith用户“等。权限反映了您的应用程序的原始功能,所以在更改应用程序的功能时,您只需要更改权限检查,而不是要更改角色或用户模型。
为了演示这一点,我们将创建一些权限并将其分配给用户,然后根据用户的授权(权限)自定义我们的Web UI。

Add Permissions

Shiro的Realm是只读组件:每个数据存储模型的角色,组,权限,帐户和他们的关系不同,所以Shiro没有一个’write’API来修改这些资源。要修改模型对象的底层,你只需通过任何您想要的API直接修改它们。你的Shiro的Realm然后知道如何阅读这些信息,并以Shiro理解的格式表示它。
因此,由于我们在此示例应用程序中使用Stormpath,因此我们将以特定于Stormpath API的方式为帐户和组分配权限。
让我们执行一个cURL请求,为以前创建的Jean-Luc Picard帐户添加一些权限。 使用该帐户的href URL,我们将通过自定义数据将一些apacheShiroPermissions发布到该帐户:

1
2
3
4
5
6
7
8
9
10
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的插槽相匹配。
这将直接向Stormpath帐户添加两个权限:

  • ship:NCC-1701-D:command
  • user:jlpicard:edit

这些使用Shiro的WildcardPermission语法。
第一个基本上是指用“NCC-1701-D”标识符来“命令”’船’的能力。这是实例级权限的一个示例:控制对资源船的特定实例NCC-1701-D的访问。第二个也是一个实例级权限,指出使用标识符jlpicard编辑用户的能力。
如何在Stormpath中存储权限,以及如何在Stormpath中自定义存储和访问选项超出本文档的范围,但这是在Shiro Stormpath插件文档中解释的。

Permission Tags

就像我们有用于角色检查的JSP标签一样,还有同样用于权限检查的标签。我们更新/home.jsp页面,让用户知道是否允许你根据分配给他们的权限执行某些操作。这些消息将添加到主页的新的<h2>权限</ h2>部分:

1
2
3
4
5
6
<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>

当你第一次访问主页时,在登录之前,你将看到以下输出:

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

但是,在你使用Jean-Luc Picard帐户登录后,你将看到这一点:

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

你可以看到Shiro解决了经过身份验证的用户具有权限,并以适当的方式呈现输出。
您还可以使用标签进行肯定权限检查。
最后,我们将注意到极其强大的权限检查功能。 你能看到第二个权限检查如何使用运行时生成的权限值吗?

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

${account.username}在运行的时候将user:aUsername:edit的值组装起来,最后这个最终的值被用来权限检查。
这种运行时的权限检查机制是用来构建高度定制化和安全的应用的常规技术。

Run the webapp

run一run咯

Summary

我们希望这篇指南能对你使用Shiro构建webapp有帮助。在下个版本我们将推出这些内容:

  • 不同用户数据的存储插件,像RDBMS或者NoSQL之类的。

原文Securing Web Applications with Apache Shiro


留言: