Application Security With Apache Shiro[译]

2017-09-21
Apache

当你为你的应用添加安全措施的时候有没有觉得很受挫?你有没有觉得Java在安全这一块的解决方案用起来很难,让你更加觉得困惑?这篇文章介绍Apache Shiro,一个简单又强大为你应用提供安全的Java框架。本文对Apache Shiro的愿景、架构设计及怎样去使用Shiro来保护你的应用进行解释。

Apache Shiro是什么?

Apache Shiro(发音为“歇肉”,在日本的发音是城堡的意思)是一个强大又好用的Java安全框架,它提供认证、授权、加密和会话管理,它可以被用在保护任何应用–从命令行应用、移动应用到大型的web企业级应用。

Shiro为以下几个方面提供应用安全的API。

  • 认证-证明用户身份,通常叫做登录
  • 授权-访问控制
  • 加密-保护或隐藏数据不被窥视
  • 会话管理-每个用户的状态是对时间敏感的

Shiro还提供辅助的特性,比如说web安全,单元测试,和多线程的支持。这些都是为了加强上面提到的几个特性。

Apache Shiro为什么被发明?

对于一个框架而言,它存在的意义就是它满足你的需求而找不到其他的替代方案。为了理解这些东西,我们有必要翻一番Shiro的历史以及当时的替代品。

在2008年加入ASF之前,Shiro都5岁了,而且和早在2003年开始的JSecurity一样有名了。在2003年,没有太多为Java开发者提供可选的安全方案。我们坚持使用Java认证和Java授权服务,称作JAAS。然而JASS有很多缺点当然认证能力是可以接受的,授权方面让人用起来觉得很崩溃。JAAS和JVM级别的安全密切相关,比方说决定一个class该不该被加载到JVM。作为一个应用开发者,我更关心的是应用程序用户能做什么,而不是我的代码在JVM里能做什么。

由于我当时着手的工作,我需要一个干净的和容器无关的会话机制。当时在游戏(我猜作者的工作就是写游戏)中唯一的选择就是HttpSession,这个玩意需要一个web容器或者EJB容器。我需要的是能够从容器中解耦,而且在任何环境下都好用的。

最后有一个密码的问题。有很多情况下我们需要加密数据,但是Java加密架构除了密码专家外普通人很难理解。它的API到处都是异常检查,用起来感觉很笨重。我迫切希望一个干净的开箱即用的可以满足我的需求的解决方案。

看了一下在2003年早期的安全概况,你能很快的意识到没有一款单一的框架能够满足这些需求。正因为如此,JSecurity还有后来的Shiro诞生了。

如今你为何用Apache Shiro?

自2003年来,框架的格局发生了很大的变化,因此现在有一个让人信服的理由来使用Shiro。理由如下:

  • 好用–好用是一个项目最关键的目标。应用安全可能是非常让人沮丧和困惑,因此被称作“必要的罪恶”。假如你让它变得新手程序员都容易使用,那就不那么痛苦了。
  • 全面–没有比Shiro更广泛的安全框架了,因此它可以一站式满足你对安全的需要。
  • 灵活–Shiro能在任何环境下工作。虽然它在web、EJB、和ioC容器中工作,但是它不依赖他们。Shiro没有太多的规定,也没有太多的依赖。
  • 支持web–Shiro对web应用有全面的支持,你可以创建基于web协议和url的灵活的安全策略,同时提供一套控制页面输出的JSP库。
  • 可插拔–Shiro干净的API(干净可以翻译为简洁)和设计思想可以让你很容易地集成到其他框架和应用。你可以看到Shiro和Spring、Grails等框架无缝集成。
  • 支持–Shiro是ASF的一部分。这个项目的开发和用户群会友好的提供帮助。如果需要,像Katasoft商业公司也会提供专业的支持和服务。

谁在用Shiro?

Shiro和它的先驱JSecurity在很多行业很多公司的项目中用了很多年。在成为ASF顶级项目之后,它的网站流量和使用率大增。也有许多开源的组织使用Shiro,比方说Spring, Grails, Wicket, Tapestry, Tynamo, Mule, 和 Vaadin。

商业公司像Katasoft、Sonatype、MuleSoft等也使用Shiro来保护他们的商业软件和网站。

核心概念:主体,安全管理器,域

目前我们讲了Shiro的优点,我们直接来看看它的API,让你直接感受它。Shiro的架构有3个核心的概念–主体、安全管理器和域。

Subject(主体)

当你要对你的应用进行保护的时候,你最大的可能要问的问题就是“当前用户是谁?”或者“当前用户允许做这件事吗?”,我们问自己这些问题是很正常的,就像我们在写代码或者设计接口一样。应用程序通常是基于用户故事来构建,而且你想基于每个用户来构建你的功能。因此,你考虑在你应用安全最常规的思路就是基于当前用户。Shiro API中的主体概念基本上体现了这种思路。

主体是安全术语中的一个名词,代表的是当前执行的用户。不能仅仅只称作“用户”因为“用户”这个词通常和人类关联起来。在安全领域,主体这个词可以代表一个人,也可以代表第三方进程、一个后台账号或者类似的东西。简单来讲,就是和当前软件交互的东西。然而对于大多数目的和用途,你可以把它当做Shiro用户的概念。你能很容易地在你的代码中获取Shiro的主体,代码如下:

1
2
3
4
import org.apache.shiro.subject.Subject;
import org.apache.shiro.SecurityUtils;
...
Subject currentUser = SecurityUtils.getSubject();

一旦你获取了主体,你就能使用Shiro为当前用户拿到90%的访问权限,比如登录,退出登录,获取session,执行权限检查等。这里关键点是Shiro的API大体上是很直观的,从中可以体现开发者在针对每个用户的安全控制中的思路(这句话实在是不知道怎么翻译)。在代码中任何地方获取主体很容易,在需要进行安全操作的地方进行控制也很容易。

SecurityManager(安全管理器)

在主体背后后与之配对的是安全管理器。主体提供当前用户的安全操作,然而安全管理器为所有用户管理着安全操作。这是Shiro架构的核心,有类似于很多“伞”的作用。这些伞内部引用了很多嵌套的安全组件而形成一个伞图(翻译很晦涩,意思就是很多组件组合而成)。然而,一旦安全管理器及内部的“伞图”配置好了,应用开发者几乎将所有时间都花在了主体API上,通常这个安全管理器是独立的。

在每一个应用中总有一个安全管理器实例。本质上是一个单例应用(虽然不必是静态单例)。像Shiro中其他东西一样,默认的安全管理器的实现是POJO,可以通过任何与POJO兼容的配置机制–普通的Java代码、Spring XML、YAML、.properties和ini文件等等。通常来讲,能够被实例化的类和能够调用JavaBean兼容方法的都可以使用。

为此,Shiro借助基于文本的INI配置提供了一个缺省的“公共”解决方案。INI读起来很容易,用起来很简单,而且依赖的东西也很少。你可以看到,通过简单了解对象图导航,INI可以很高效的配置简单的对象图,像SecurityManager。注意Shiro也支持Spring XML配置合其他替代品。我们这里只谈INI。

1
2
3
4
5
6
7
8
9
10
[main]
cm = org.apache.shiro.authc.credential.HashedCredentialsMatcher
cm.hashAlgorithm = SHA-512
cm.hashIterations = 1024
#Base64 encoding (less text):
cm.storedCredentialsHexEncoded = false
iniRealm.credentialsMatcher = $cm
[users]
jdoe = TWFuIGlzIGRpc3Rpbmd1aXNoZWQsIG5vdCBvbmx5IGJpcyByZWFzb2
asmith = IHNpbmd1bGFyIHBhc3Npb24gZnJvbSBvdGhlciBhbXNoZWQsIG5vdCB

在上面的INI配置例子中,我们看到配置SecurityManager 实例。在INI配置中有2个部分:[main]和[users].

[main]部分用于配置SecurityManager对象或者被SecurityManager 使用的任何对象。在这个例子中,我们看到了两个对象被配置:

  1. cm对象,Shiro中的HashedCredentialsMatcher 类的一个实例。你能看到,很多cm的属性通过嵌套的点的语法被配置。清单3所示的IniSecurityManagerFactory使用的约定来表示对象图形导航和属性设置。
  2. iniRealm对象,它是SecurityManager用于表示以INI格式定义的用户帐户的组件。

在[users]这部分中你可以指定一个静态的用户列表,对于简单的应用是很方便来测试的。

介绍这些的目的不是去理解每个部分的复杂性,而是INI配置方式是一种简单的Shiro配置。更多关于INI配置的细节,详情可以参考Shiro文档

1
2
3
4
5
6
7
8
9
10
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.util.Factory;
...
//1. Load the INI configuration
Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
//2. Create the SecurityManager SecurityManager securityManager = factory.getInstance();
//3. Make it accessible
SecurityUtils.setSecurityManager(securityManager);

在上面的例子中,我们有3个步骤:

  1. 加载配置SecurityManager及其组件的INI配置文件。
  2. 基于这个配置创建SecurityManager实例(使用Shiro的工厂概念)。
  3. 把SecurityManager变成单例。在这个例子中,我们将它设置成VM静态单例,但是这不是必须的。你的应用配置机制可以决定你要不要使用静态内存(这段翻译真的一点都不容易理解)。

Realms(域)

Shiro中第三个核心概念就是域。域在你的应用数据和Shiro中承担一个桥梁或者说是连接器的角色。也就是说,当和安全相关的数据交互,比方说认证(登录)和授权(访问控制)时,Shiro从为应用配置的一个或者多个域配置中去找这些东西。

在某种意义上,一个域本质上就是一个安全的DAO:它为数据源封装了连接细节以及在需要的时候让Shiro能够访问关联的数据。当在配置Shiro的时候,你必须指定至少一个域用来做认证或者授权。可以配置多个,但至少要有一个。

Shiro提供开箱即用的域来连接各种安全的数据源,比方说LDAP、数据库相关的(JDBC)、文本文件配置类似INI文件和properties配置文件等。如果默认的域不能满足你的需求,你也可以将你自己实现的域插入到其中。下面通过INI配置Shiro的例子就是用LDAP来作为应用的一个域实现。

1
2
3
4
5
[main]
ldapRealm = org.apache.shiro.realm.ldap.JndiLdapRealm
ldapRealm.userDnTemplate = uid={0},ou=users,dc=mycompany,dc=com
ldapRealm.contextFactory.url = ldap://ldapHost:389
ldapRealm.contextFactory.authenticationMechanism = DIGEST-MD5

现在我们知道了怎么配置一个基础的Shiro环境,我们接下来一起讨论作为一个开发者你改怎么使用这个框架。

Authentication

认证是验证用户的唯一性的过程。就是说,当用户使用应用进行认证的时候,他们在证明他们确实是他们所说的那个人。通常有时候我们把它称作登录。典型的三个步骤:

  1. 收集用户身份信息,也叫作principals,并支持身份证明,称为credentials。
  2. 提交principals和credentials到系统。
  3. 如果提交的credentials和系统期望该用户身份匹配,这个用户就可以认为认证通过。如果不匹配,那就是认证不通过。

每个人都熟悉的最常见的例子就是用户名和密码的组合。当用户登录到系统,他们通常提供他的用户名(principal)和密码(credential)。如果存储在系统中的 密码和用户提供的一致,他就被认证成功。

Shiro以简单直观的方式支持类似的流程。正如我们说的,Shiro有一套以主体为中心的API–几乎所有在运行时你和Shiro关心的都是通过与当前执行的主体交互实现的。因此,要登录一个主体,你只需要调用它的登录方法,传一个带有principals 和credentials的AuthenticationToken实例。下面是一个例子:

1
2
3
4
5
6
//1. Acquire submitted principals and credentials:
AuthenticationToken token = new UsernamePasswordToken(username,password);
//2. Get the current Subject:
Subject currentUser = SecurityUtils.getSubject();
//3. Login:
currentUser.login(token);

如你所见,Shiro的APi很简单的表示了这个流程。你可以将这种对所有主体操作的简单性作为一种特色。当登录方法被调用,SecurityManager将接收AuthenticationToken然后将其分发到一个或者多个配置好的域中用来做认证。每个域都有能力根据需要对提交的AuthenticationToken进行处理。但是假如登录失败了会发生什么呢?假如用户的密码输错了呢?你可以通过运行时的AuthenticationException 来处理这些失败,代码如下:

1
2
3
4
5
6
7
8
9
//3. Login:
try {
currentUser.login(token);
} catch (IncorrectCredentialsException ice) { …
} catch (LockedAccountException lae) { …
}
catch (AuthenticationException ae) {…
}

你可以选择捕获AuthenticationException的一个子类来进行具体的处理,或者统一处理任何AuthenticationException(比如说,通常显示“用户名或者密码错误”消息)。这是根据你应用的需求来选择的。

一个主体登录成功后,他们被认为认证通过,通常你允许他们使用你的应用。但是,由于用户仅证明了自己的身份不代表他们在应用中能做任何他们想做的事情。这就提出了下一个问题:“我如何控制用户被允许做什么?”决定用户允许做什么叫做授权。接下来我们讨论Shiro怎么启用授权。

Authorization

授权是基本的访问控制–控制你的用户在你的应用中可以访问什么,比方说资源、web页面等等。大多数用户通过使用角色和权限等概念来执行访问控制。就是说,一个用户通常允许做什么事或者不允许做什么事是基于他们分配的角色或者权限的。然后,你的应用程序可以根据对这些角色和权限的检查来控制显示哪些功能。正如你期望的,主体API允许你非常容易的执行权限和角色检查。代码片段如下:

1
2
3
4
5
if ( subject.hasRole(“administrator”) ) {
//show the ‘Create User’ button
} else {
//grey-out the button?
}

如你所见,你的应用可以基于访问控制开启或者关闭功能。

权限检查是另一种执行授权的方式。在上面的例子中检查权限有一个很大的缺陷:你不能再运行的时候添加或者删除角色。你的代码是和角色名字硬编码进去的,因此假如你改变了角色名或者配置,你的代码将会爆炸!假如你需要可以在运行时改变角色的含义,或者根据需要添加删除角色,那么你就必须依靠别的东西了。

为此,Shiro支持权限的概念。权限是原始功能的说明,举个栗子,‘开门’、‘创建博客实体’、‘删除xx用户’等等。有权限代表你有应用的原始功能,当你更改应用程序的功能时,你只需要更改权限检查。反过来,在运行时必要的时候你可以分配权限到角色或者用户。下面的代码使用权限检查:

1
2
3
4
5
if ( subject.isPermitted(“user:create”) ) {
//show the ‘Create User’ button
} else {
//grey-out the button?
}

这样,任何分配了“user:create”权限的角色或用户可以单击“创建用户”按钮,而且这些角色和分配甚至可以在运行时更改,给你提供了一种非常灵活的安全模型。

“user:create”字符串是遵守某些解析约定的权限字符串的示例。Shiro通过其WildcardPermission支持这个开箱即用的约定。虽然超出本导言文章的范围,你将看到WildcardPermission在创建安全策略时可以非常灵活,甚至支持诸如实例级访问控制。

1
2
3
4
5
if ( subject.isPermitted(“user:delete:jsmith”) ) {
//delete the ‘jsmith’ user
} else {
//don’t delete ‘jsmith’
}

这个例子展示了你能控制,甚至可以细化到更细粒度的级别,如果有需要可以访问单个资源。如果你想的话你也可以发明自己的权限语法。参考Shiro Permission获取更多详情。最后,就像身份验证一样,上面的调用最终也进入了SecurityManager,SecurityManager将会查询一个或多个域,以作出访问控制决定。这允许领域根据需要对认证和授权操作进行响应。

到此就是一个队Shiro授权功能的一个简要概述。很多安全框架在认证和授权面前停了下来,而Shiro不止于此。接下来我们要讨论Shiro的高级会话管理功能。

Session Management

Shiro提供安全框架里独一无二的:可用于任何应用和任何结构层一致的会话API。也就是说,Shiro为任何应用程序启用了会话编程范例–从小的后台应用到大型集群web应用。这意味着希望使用会话的应用程序开发人员不再强制使用Servlet或EJB容器。或者,如果使用这些容器,开发人员现在可以选择在任何层中使用统一和一致的会话API,而不是使用servlet或EJB特定的机制。

但是或许一个最重要的好处是Shiro会话是独立于容器的。这具有微妙但非常强大的含义。例如我们考虑会话集群。有多少以指定容器的方式来集群会话以进行容错和故障转移?Tomcat和Jetty做的不一样,和Websphere也不一样。但是使用Shiro session,你可以获得一个容器无关的集群解决方案。Shiro的架构允许可插拔的会话数据存储,例如企业级缓存,关系型数据库。NoSQL等。这意味着你可以一次配置会话群集,并且无论你的部署环境如何,它都将以相同的方式工作–Tomcat、Jetty、JEE Server或者独立的应用。实在是没用必要根据你怎么部署你的应用来重新配置你的应用。

另一个Shiro session的好处是session数据如果需要可以跨客户端共享。举个栗子,如果需要,Swing桌面客户端可以参与相同的Web应用程序会话–如果终端用户同时使用两者,则很有用。因此你怎样在任何环境下访问主体的session?下面的代码主体的2个方法会展示出来:

1
2
Session session = subject.getSession();
Session session = subject.getSession(boolean create);

如你所见,这些方法在概念上与HttpServletRequest API相同。第一个方法将会返回主体存在的session,如果没有,那就创建一个再返回。第二个方法接收一个bool参数,这个参数决定是否在session不存在的时候创建新的session。一旦你请求主体的session,你可以使用它和几乎使用HttpSession一样。Shiro团队考虑到HTTPSession对Java开发人员很友好,因此我们有这种感觉。最大的区别就是你可以在任何应用中使用Shiro Session,不仅限于web应用。下面的代码展示了其相同之处:

1
2
3
4
5
Session session = subject.getSession();
session.getAttribute(“key”, someValue);
Date start = session.getStartTimestamp();
Date timestamp = session.getLastAccessTime();
session.setTimeout(millis);

Cryptography

密码学是隐藏或混淆数据的过程,所以窥探眼睛无法理解它。Shiro在密码学中的目标是简化和使用JDK的加密支持。要注意的是,一般来说密码学对于主体来说不是特定的,所以它是Shiro API的一个领域,和主体无关。你可以使用Shiro的加密用在任何地方,即使主体没有使用。Shiro真正关注的两个领域一个是哈希加密和密码加密。

Hashing

如果你用过JDK的MessageDigest类,你很快会发现用起来有一点笨重。它有一套基于工厂的笨重的静态方法API,而不是面向对象的,而且你被迫捕获那些永远不会被捕获的异常。假如你需要16进制或者base64编码消息摘要的输出,你得自己写–没有标准的JDK实现。Shiro以干净直观的散列API解决了这些问题。

打个比方,我们考虑一下比较常见的MD5散列文件并确定该哈希值的十六进制值的情况。“校验和”,这是在提供文件下载时经常使用的 - 用户可以在下载的文件上执行自己的MD5哈希,并声明其校验和与下载站点的校验和匹配。假如匹配,用户可以充分地假设该文件在传输过程中没有被篡改。

在不使用Shiro情况下这个过程:

  1. 将文件转化为字节数组。JDK不能帮你完成,因此你得自己创建一个打开FileInputStream的方法,使用字节缓冲,然后抛出可能的异常等等。
  2. 用MessageDigest类来对字节数组求哈希,处理异常。
  3. 对哈希后的字节数组编码成16进制。JDK中也没有现成的帮你完成,因此你得创建另一个帮助类,并且可能在你的实现中使用按位操作和位移。
1
2
3
4
5
6
7
try {
MessageDigest md = MessageDigest.getInstance("MD5");
md.digest(bytes);
byte[] hashed = md.digest();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}

对于这么简单和相对普遍的事情来说,这是一个很繁琐的工作。
看看Shiro怎么做相同的事情的:

1
String hex = new Md5Hash(myFile).toHex();

当你使用Shiro来简化所有这些工作时,这是非常简单和容易理解的。 SHA-512散列和Base64编码的密码同样简单。

1
2
String encodedPassword =
new Sha512Hash(password, salt, count).toBase64();

可以看到在哈希和编码上Shiro帮你简化了多少,在这个过程中节省了你不少时间。

Ciphers

密码是可以使用密钥可逆地转换数据的加密算法。我们通常用作保护数据安全,特别在传输或者存储数据的时候,数据特别容易被窥探。

如果你曾经使用过JDK加密API,特别是 javax.crypto.Cipher类,你知道这可以是一个难以置信的复杂野兽驯服(翻译为这是一个很困难的过程)。对于入门者而言,每个密码配置始终由javax.crypto.Cipher的一个实例表示。需要公钥/私钥加密吗?

然而怎么创建你需要的Cipher 实例?你创建一个复杂的,非直观的令牌分隔的密码选项字符串,称为“转换字符串”,并将此字符串传递给Cipher.getInstance静态工厂方法。使用这种密码选项String方法,没有类型安全性来确保你使用有效的选项。这也隐含意味着没有JavaDoc帮助你了解相关选项。而且您还需要处理检查的异常情况,以防你的String配置不正确,即使你知道配置正确。正如你所看到的,使用JDK密码是一项相当麻烦的任务。这些技术曾经是Java API在很久以前的标准,但时代已经改变,我们想要一个更简单的方法。

Shiro尝试通过引入其CipherService API来简化加密密码的整个概念。CipherService是大多数开发人员在保护数据时所需要的:一种简单,无状态,线程安全的API,可以在一个方法调用中对数据进行全面加密或解密。所有你需要做的是提供你的密钥,可以根据需要进行加密或解密。例如,可以使用256位AES加密:

AesCipherService cipherService = new AesCipherService();
cipherService.setKeySize(256);
//create a test key:
byte[] testKey = cipherService.generateNewKey();
//encrypt a file’s bytes:
byte[] encrypted =
cipherService.encrypt(fileBytes, testKey);

与JDK的Cipher API相比,Shiro示例更简单:

  • 你可以直接实例化CipherService - 没有奇怪或混乱的工厂方法。
  • 密码配置选项表示为与JavaBeans兼容的getter和setter - 没有奇怪和难以理解的“转换字符串”。
  • 加密和解密在单一方法调用中执行。
  • 没有强制检查异常。 如果你想要的话,可以捕获Shiro的CryptoException。

Shiro的CipherService API还有其他优点,例如支持基于字节数组的加密/解密(称为“块”操作)以及基于流的加密/解密(例如,加密音频或视频)的能力。

Java加密技术不必如此难受。
Shiro对密码的支持旨在简化你保护数据安全的付出。

Web Support

最后,但不是不重要的,我们简要介绍Shiro对web的支持。Shiro拥有强大的web支持模块,来保护web应用安全。为一个web应用设置Shiro是很简单的。仅需要在web.xml中定义一个Shiro Servlet Filter。

1
2
3
4
5
6
7
8
9
10
11
<filter-name>ShiroFilter</filter-name>
<filter-class>
org.apache.shiro.web.servlet.IniShiroFilter
</filter-class>
<!-- no init-param means load the INI config
from classpath:shiro.ini -->
</filter>
<filter-mapping>
<filter-name>ShiroFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

这个过滤器能够读之前的shiro.ini配置文件,因此无论在什么样的部署环境下,配置都是一样的。一旦配置好了,Shiro filter就会过滤一切请求,并确保在请求期间特定的请求主体可被访问。由于过滤所有请求,你可以执行指定安全的逻辑代码,以确保仅允许符合特定条件的请求。

URL-Specific Filter Chains

Shiro通过它特有的URL链来支持安全的过滤规则。它允许你为任何匹配的URL模式指定特殊过滤器链。这意味着在使用Shiro过滤机制的时候你有很大的灵活性–比在web.xml中定义的过滤器好很多。

1
2
3
4
5
6
[urls]
/assets/** = anon
/user/signup = anon
/user/** = user
/rpc/rest/** = perms[rpc:invoke], authc
/** = authc

如你所见,有一个用于web应用的[urls]部分。每一行等号的左边表示一个上下文相关的web应用路径。等号右边定义了一个过滤器链–有序的、逗号分隔的为指定路径执行servlet过滤器列表。每一个过滤器是一个servlet filter,但是你在上述看到的过滤器是特殊的,是与安全相关的由Shiro提供开箱即用的过滤器。你可以组合和匹配这些安全过滤器,以创建自定义的安全体验。你还可以指定任何其他现有的可能拥有的Servlet过滤器。

使用Shiro,更容易看出为给定的匹配路径执行的过滤器链。只要你想,你可以只在web.xml中定义Shiro filter然后在ini配置文件中定义其他过滤器和过滤器链,这样比web.xml更简洁,易于理解的过滤器链定义机制。即使你没有使用任何Shiro的安全特性,这个小小的方便让Shiro值得使用。

JSP Tag Library

Shiro还提供一套JSP标签来允许你基于当前主体的状态控制页面输出。一个常见有用的例子就是当一个用户登录后显示‘Hello ’文本。但是假如他们是匿名的,你就得显示点别的,像“Hello! Register Today!”。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<%@ taglib prefix="shiro"
uri="http://shiro.apache.org/tags" %>
...
<p>Hello
<shiro:user>
<!-- shiro:principal prints out the Subject’s main
principal - in this case, a username: -->
<shiro:principal/>!
</shiro:user>
<shiro:guest>
<!-- not logged in - considered a guest. Show
the register link: -->
! <a href=”register.jsp”>Register today!</a>
</shiro:guest>
</p>

还有其他标签基于用户有哪些角色或者没有哪些角色,分配了哪些权限,有没有被认证,“记住我”了的,或者一个匿名用户来允许你输出。
Shiro支持很多web特有的feature,像记住我,REST和BASIC认证,当然,如果你希望使用Shiro的本地企业会话,也可以使用透明的HttpSession支持。参见Apache Shiro web documentation

Web Session Management

最后,有趣的是探讨Shiro在web环境中对session的支持。

Default Http Sessions

对web应用而言,Shiro默认的session架构是我们现有常使用的Servlet Container。当你调用subject.getSession()subject.getSession(boolean)方法的时候,Shiro会返回一个由Servlet 容器支持的session实例。这种方法的优点是调用subject.getSession()的业务层代码与Shiro Session实例进行交互–它并不知道是和一个基于web的HTTPSession对象一起工作的。分层架构保持代码整洁是很好的做法。

Shiro’s Native Sessions in the Web Tier

假如你在web应用中开启了Shiro的本地session管理,你需要Shiro的企业session特性(类似独立于容器的集群),当然你想HttpServletRequest.getSession()HttpSession API和本地session一起使用而不是servlet容器的session。如果你要重构任何使用HttpServletRequest和HttpSession API的代码,而不是使用Shiro的Session API,那将是非常令人沮丧的。Shiro永远不期望你这样做。相反,Shiro完全实现了Servlet规范的Session部分,以支持Web应用程序中的本机会话。这意味着每当你调用相应的HttpServletRequest或HttpSession方法调用时,Shiro会将这些调用委托给其内部本地Session API。结果就是你不需要去改动你的代码,即使你使用Shiro本地会话管理–实际上是一个非常方便和必要的特性。

Additional Features

在Shiro框架中其他用于让Java应用安全的特性,例如(中文翻译太晦涩,还是直接贴原文的):

  • Threading and Concurrency support for maintaining Subjects across threads (Executor and ExecutorService support)
  • Callable and Runnable support for executing logic as a specific Subject
  • “Run As” support for assuming the identity of another Subject (e.g. useful in administrative applications)
  • Test harness support, making it very easy to have full testing of Shiro secured-code in unit and integration tests

Framework Limitations

就像我们想要的那样,Apache Shiro不是一个“银弹”–它不会轻易解决所有安全问题。Shiro没有解决这些事情:

  • 虚拟机级别的考虑:当前Shiro没有考虑虚拟机级别的安全,比如基于访问控制策略的阻止特定的class加载到类加载器中。然而Shiro和现有的JVM安全操作整合起来是不可思议的–只是没有人去为这个项目做这件事罢了。
  • 多阶段认证:Shiro当前不支持‘多阶段’认证,用户可以通过一种机制登录,只能被要求使用不同的机制再次登录。这已经在基于Shiro的应用程序中完成,但应用程序通过应用程序收集所有必需的信息,然后与Shiro进行交互。很可能在未来的版本中会有这种支持。
  • 写域操作:当前所有的域实现支持读操作来获取认证和授权数据用来执行登录和访问控制。写操作,像创建用户,组和角色或者将用户和角色组及权限关联起来都是不支持的。这是因为支持这些操作的数据模型在应用程序中有很大差异,所以在所有Shiro用户上执行“写入”API将是困难的。

Upcoming Features

Shiro的社区每天都很活跃,Shiro的特性也在变多。在接下来的版本中你将看到:

  • 简洁的web filter机制,允许更多的可插拔的过滤器支持,而不是通过子类。
  • 更多可插拔的默认有利于组合继承的域实现。你将能够插拔查找身份验证和授权数据的组件,而不是要求你对Shiro Realm实现进行子类实现。
  • 强大的OpenID和OAuth(或者都有)客户端支持
  • 验证码支持
  • 100%无状态应用程序(例如许多REST环境)更容易配置。
  • 通过请求/响应协议进行多级认证。
  • 通过AuthorizationRequest进行粗粒度授权。
  • ANTLR语法用于安全断言查询(例如(’role(admin)&&(guest ||!group(developer))’)

Summary

Apache Shiro是一个功能齐全,功能强大且通用的Java安全框架,可用于保护你的应用程序。通过简化应用安全性的四个方面,即认证,授权,会话管理和密码,应用安全性在实际应用中更容易理解和实现。Shiro的简单架构和JavaBeans兼容性允许在几乎任何环境中进行配置和使用。额外的网络支持和辅助功能,如对多线程和测试的支持,整合框架,为应用程序安全性提供可能是你的“一站式”服务。Apache Shiro的开发团队继续前进,改进代码库并支持社区。随着开源代码和商业化的采用,Shiro只会越来越强。

About the Author

Les Hazlewood是Shiro PMC主席,Katasoft的联合创始人兼首席技术官,专注于应用程序安全产品和对Apache Shiro的支持。Les拥有10年的Java开发和企业架构师经验,曾在Bloomberg, Delta Airlines, 和 JBoss担任过高级职务。Les一直在积极参与开源开发9年以上,为Spring Framework,Hibernate,JBoss,OpenSpaces以及Apache Shiro的前身JSecurity等项目提交或贡献过代码。Les目前住在加州圣马特奥,并且在不编程的时候练习剑道和研究日语。

查看英文原文: Application Security With Apache Shiro


留言: