What is Apache Shiro?
Apache Shiro 是一个强大而灵活的开源安全框架,它干净利落地处理身份认证,授权,企业会话管理和加密。
Apache Shiro 的首要目标是易于使用和理解。安全有时候是很复杂的,甚至是痛苦的,但它没有必要这样。框架应该尽可能掩盖复杂的地方,露出一个干净而直观的 API,来简化开发人员在使他们的应用程序安全上的努力。以下是你可以用 Apache Shiro 所做的事情:
验证用户来核实他们的身份
对用户执行访问控制,如:
判断用户是否被分配了一个确定的安全角色
判断用户是否被允许做某事
在任何环境下使用 Session API,即使没有 Web 或 EJB 容器。
在身份验证,访问控制期间或在会话的生命周期,对事件作出反应。
聚集一个或多个用户安全数据的数据源,并作为一个单一的复合用户“视图”。
启用单点登录(SSO)功能。
为没有关联到登录的用户启用"Remember Me"服务
…
以及更多――全部集成到紧密结合的易于使用的 API 中。
Shiro 视图在所有应用程序环境下实现这些目标――从最简单的命令行应用程序到最大的企业应用,不强制依赖其他第三方框架,容器,或应用服务器。当然,该项目的目标是尽可能地融入到这些环境,但它能够在任何环境下立即可用。
Apache Shiro 是一个拥有许多功能的综合性的程序安全框架。下面的图表展示了 Shiro 的重点,并且这个参考手册也会与之类似的被组织起来:
Shiro 把 Shiro 开发团队称为“应用程序的四大基石” ――身份验证,授权,会话管理和加密作为其目标。
Authentication:有时也简称为“登录”,这是一个证明用户是他们所说的他们是谁的行为。
Authorization:访问控制的过程,也就是绝对“谁”去访问“什么”。
Session Management:管理用户特定的会话,即使在非 Web 或 EJB 应用程序。
Cryptography:通过使用加密算法保持数据安全同时易于使用。
也提供了额外的功能来支持和加强在不同环境下所关注的方面,尤其是以下这些:
Web Support: Shiro 的 web 支持的 API 能够轻松地帮助保护 Web 应用程序。
Caching:缓存是 Apache Shiro 中的第一层公民,来确保安全操作快速而又高效。
Concurrency: Apache Shiro 利用它的并发特性来支持多线程应用程序。
Testing:测试支持的存在来帮助你编写单元测试和集成测试,并确保你的能够如预期的一样安全。
"Run As":一个允许用户假设为另一个用户身份(如果允许)的功能,有时候在管理脚本很有用。
"Remember Me":在会话中记住用户的身份,所以他们只需要在强制时候登录。
Apache Shiro Tutorial
Your First Apache Shiro Application
如果你从未使用过 Apache Shiro,这个简短的教程将会向您展示如何建立一个由 Apache Shiro 担保的初始的及非常简单的应用程序。一路上我们将讨论 Shiro 的核心概念来帮助你熟悉 Shiro 的设计和 API。
当你遵循本教程时,如果你确实不想编辑文件,你可以得到一个几乎相同的实例应用程序并按照你的意愿引用它。
选择一个位置:
在 Apache Shiro 的版本控制库: https://svn.apache.org/repos/asf/shiro/trunk/samples/quickstart
在 Apache Shiro 的源代码的 samples/quickstart 目录。该源代码在 Download 页面提供下载。
在这个简单的示例中,我们将创建一个非常简单的命令行应用程序,它将会运行并迅速退出,这样你能够获得对 Shiro的 API 的感受。
Apache Shiro 从开始的那天起就被设计成能够支持任何应用程序――从最小的命令行应用程序到最大的群集 Web 应用程序。即使我们为该教程创建的是一个简单的应用,了解相同的使用模式适用于无论你的应用程序是怎样创建的及它被部署到哪里。
该教程需要 Java 1.5 及更高本。我们也使用 Apache Maven 作为我们的构建工具,但当然这不是使用 Apache Shiro 所
必需的。你可以获取 Shiro 的 jar 包并按你喜欢的方式合并到你的应用程序,例如可能是一 Apache Ant 和 Ivy。对于本教程,请确保你正在使用 Maven 2.2.1 或更高版本。你应该能够键入 mvn -version 命令行提示符,并看到与下面类似的东西:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.yixing.maven.demo</groupId> <artifactId>maven-shiro-demo</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>war</packaging> <dependencies> <dependency> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> <version>1.1.3</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-core</artifactId> <version>1.4.0</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.2</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-war-plugin</artifactId> <version>2.6</version> <configuration> <failOnMissingWebXml>false</failOnMissingWebXml> </configuration> </plugin> </plugins> </build> </project>在应用程序中启用 Shiro 最先要明白的事情是几乎在 Shiro 中的每个东西都与一个名为 SecurityManager 的主要的/核心的组件有关。对于那些熟悉 Java 安全的人来说,这是 Shiro 的 SecurityManager 概念――它不等同于java.lang.SecurityManager。 现在了解 Shiro 的 SecurityManager 是应用程序的 Shiro
环境的核心及每个应用程序中必须存在一个 SecurityManager 是很有益处的。因此,在我们的教程应用程序中第一件要做的事情就是配置 SecurityManager 实例。
虽然我们能够直接实例化一个 SecurityManager 类,但 Shiro 的 SecurityManager 实现有足够的配置选项及内置组件使得在 Java 源代码做这件事情变得较为痛苦――如果使用一个灵活的基于文本的配置格式来配置 SecurityManager,那么这将是一件很容易的事情。
为此, Shiro 通过基于文本的 INI 配置文件提供了一个默认的"共性(common denominator) "解决方案。近来人们已经相当厌倦了使用笨重的 XML 文件,且 INI INI 文件能够有效地被用来配置简单的对象图,如 SecurityManager。
Shiro 的 SecurityManager 实现及所有支持组件都是兼容 JavaBean 的。这允许 Shiro能够与几乎任何配置格式如 XML(Spring, JBoss, Guice 等等), YAML, JSON, GroovyBuilder markup,以及更多配置被一起配置。 INI 文件只是 Shiro 的“共性”格式,他它允许任何环境下的配置,除非其他选项不可用。
因此,我们将为这个简单的应用程序使用 INI 文件来配置 Shiro SecurityManager。首先,在 pom.xml 所在的同一目录下创建 src/main/resources 目录。然后在新目录下创建包含以下内容的 shiro.ini 文件:
[users] # admin=admin 分别表示账号和密码,administrator 表示逗号前边的账号拥有 administrator 这个角色。 admin=admin,administrator zhangsan=zhangsan,manager lisi=lisi,guest lonestarr=vespa,goodguy,schwartz [roles] # administrator 表示角色名称,* 表示这个角色拥有所有权限 administrator=* manager=user:*,department:* guest=user:query,department:query admin=* schwartz=lightsaber:* goodguy=winnebago:drive:eagle5
如你所见,这个配置基本上建立了一小组静态用户帐户,对于我们的第一个应用程序已经足够了
@Test public void test01() { // 1.读取 shiro.ini 文件内容 // IniSecurityManagerFactory iniSecurityManagerFactory = new IniSecurityManagerFactory("classpath:shiro.ini"); Factory<SecurityManager> iniSecurityManagerFactory = new IniSecurityManagerFactory("classpath:shiro.ini"); // 2. SecurityManager instance = iniSecurityManagerFactory.getInstance(); // 3. SecurityUtils.setSecurityManager(instance); }好了,在仅仅添加了 3 行代码后, Shiro 就在我们的简单应用程序中启用了!很容易是吧?
轻松地运行 mvn compile exec:java,并看到这一切仍然运行成功(由于 Shiro 的默认调试日志或更低版本,你将不会
看到任何的 Shiro 日志消息――如果在启动和运行没有报错,那么你知道一切仍然正常)。
这里是上面增加的代码所做的:
1. 我们使用 Shiro 的 IniSecurityManager 实现来提取我们的 shiro.ini 文件,它位于 classpath 的根目录。该实现反映了 Shiro 对工厂设计模式的支持。 classpath: 前缀是一个资源定位符,用来告诉 shiro 去哪加载 ini 文件(其他前缀,如 url:和 file:也同样被支持)。
2. factory.getInstance()方法被调用,它来解析 INI 文件并返回反映该配置的 SecurityManager 实例。
3. 在这个简单的例子中,我们把 SecurityManager 设置为一个静态的(memory)单例,能够跨 JVM 访问。但请注意,这是不可取的,如果你在单个的 JVM 只中会有不只一个启用 Shiro 的应用程序。对于这个简单的例子
而言,这是没有问题的,但更为复杂的应用程序环境通常将 SecurityManager 置于应用程序特定的存储中(如
在 Web 应用中的 ServletContext 或 Spring, Guice 后 JBoss DI 容器实例)。
现在我们的 SecurityManager 已经设置好并可以使用了,现在我们能够开始做一些我们真正关心的事情――执行安全操作。当保护我们的应用程序时,我们对自己可能提出的最为相关的问题是“当前用户是谁”或“当前用户是否被允许做XXX”。当我们编写代码或设计用户接口时,问这些问题是很常见的:应用程序通常是基于用户的背景情况建立的,且你想基于每个用户标准体现(保障)功能。因此,对于我们考虑应用程序安全的最自然的方式是基于当前用户。Shiro 的 API 使用它的 Subject 概念从根本上代表了“当前用户”的概念。几乎在所有的环境中,你可以通过下面的调用获取当前正在执行的用户:
Subject currentUser = SecurityUtils.getSubject();使用 SecurityUtils.getSubject(),我们可以获得当前正在执行的 Subject。 Subject 是一个安全术语,它基本上的意思是“当前正在执行的用户的特定的安全视图”。它并没有被称为"User"是因为"User"一词通常和人类相关联。在安全界,术语"Subject"可以表示为人类,而且可是第三方进程, cron job, daemon account,或其他类似的东西。它仅仅意味着“该事物目前正与软件交互”。对于大多数的意图和目的,你可以把 Subject 看成是 Shiro 的"User"概念。getSubject()在一个独立的应用程序中调用,可以返回一个在应用程序特定位置的基于用户数据的 Subject,并且在服务器环境中(例如, Web 应用程序),它获取的 Subject 是基于关联了当前线程或传入请求的用户数据的。
现在你拥有了一个 Subject,你能拿它来做什么?
如果你想在应用程序的当前会话中使事物对于用户可用,你可以获得他们的会话:
Session session = currentUser.getSession(); session.setAttribute("someKey", "aValue"); String value = (String) session.getAttribute("someKey");
Session 是一个 Shiro 的特定实例,它提供了大部分你经常与 HttpSessoins 使用的东西,除了一些额外的好处以及一个巨大的区别:它不需要一个 HTTP 环境!如果在一个 Web 应用程序内部部署,默认的 Session 将会是基于 HttpSession 的。但,在一个非 Web 环境中,像这个简单的教程应用程序, Shiro 将会默认自动地使用它的 Enterprise Session Management。这意味着你会使用相同的API 在你的应用程序,在任何层,不论部署环境!这开辟了应用程序的新世界,由于任何需要会话的应用程序不必再被强制使用 HttpSession 或 EJB Stateful Session Beans。并且,任何客户端技术现在能够共享会话数据。因此,现在你能获取一个 Subject 以及他们的 Session。如果他们被允许做某些事,如对角色和权限的检查,像“检查”真正有用的地方在哪呢?
嗯,我们只能为一个已知的用户做这些检查。我们上面的 Subject 实例代表了当前用户,但谁又是当前用户?呃,他们是匿名的――也就是说,直到直到他们至少登录一次。那么,让我像下面这样做:
if (!currentUser.isAuthenticated()) { UsernamePasswordToken token = new UsernamePasswordToken("lonestarr", "vespa"); token.setRememberMe(true); try { currentUser.login(token); } catch (UnknownAccountException uae) { System.out.println("用户名不存在:" + token.getPrincipal()); } catch (IncorrectCredentialsException ice) { System.out.println("账户密码 " + token.getPrincipal() + " 不正确!"); } catch (LockedAccountException lae) { System.out.println("用户名 " + token.getPrincipal() + " 被锁定 !"); } }这就是了!它再简单不过了。
但如果他们的登录尝试失败了会怎样?你能够捕获各种具体的异常来告诉你到底发生了什么,并允许你去处理并作出相应反应:
你能够检查到许多不同类型的异常,或抛出你自己的自定义条件的异常――Shiro 可能不提供的。请参见
AuthenticationException JavaDoc 获取更多。
最安全的做法是给普通的登录失败消息给用户,因为你当然不想帮助试图闯入你系统的攻击者
System.out.println("用户 :" + currentUser.getPrincipal() + " 登陆成功!");我们也可以测试他们是否有特定的角色:
System.out.println("是否拥有goodguy角色:" + currentUser.hasRole("goodguy"));我们还可以判断他们是否有权限在一个确定类型的实体上进行操作:
System.out.println("是否拥有winnebago:drive:eagle5权限:" + currentUser.isPermitted("user:*"));当然,我们可以执行极其强大的实例级权限检查――判断用户是否有能力访问某一类型的特定实例的能力:
System.out.println("是否拥有winnebago:drive:eagle5权限:" + currentUser.isPermitted("winnebago:drive:eagle5"));
小菜一碟,对吧?
最后,当用户完成了对应用程序的使用,他们可以注销
currentUser.logout();下面是完整的代码:
package com.yixing.demo; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.IncorrectCredentialsException; import org.apache.shiro.authc.LockedAccountException; import org.apache.shiro.authc.UnknownAccountException; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.config.IniSecurityManagerFactory; import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.session.Session; import org.apache.shiro.subject.Subject; import org.apache.shiro.util.Factory; import org.junit.Test; public class ShiroDemo01 { @SuppressWarnings("deprecation") @Test public void test01() { // 1.读取 shiro.ini 文件内容 // IniSecurityManagerFactory iniSecurityManagerFactory = new IniSecurityManagerFactory("classpath:shiro.ini"); Factory<SecurityManager> iniSecurityManagerFactory = new IniSecurityManagerFactory("classpath:shiro.ini"); // 2. SecurityManager instance = iniSecurityManagerFactory.getInstance(); // 3. SecurityUtils.setSecurityManager(instance); // 4.获取当前登陆的用户 Subject currentUser = SecurityUtils.getSubject(); /* * Session session = currentUser.getSession(); * session.setAttribute("someKey", "aValue"); String value = (String) * session.getAttribute("someKey"); if (value.equals("aValue")) { * System.out.println("someKey 的值:" + value); } */ // 认证失败 if (!currentUser.isAuthenticated()) { UsernamePasswordToken token = new UsernamePasswordToken("lonestarr", "vespa"); token.setRememberMe(true); try { currentUser.login(token); } catch (UnknownAccountException uae) { System.out.println("用户名不存在:" + token.getPrincipal()); } catch (IncorrectCredentialsException ice) { System.out.println("账户密码 " + token.getPrincipal() + " 不正确!"); } catch (LockedAccountException lae) { System.out.println("用户名 " + token.getPrincipal() + " 被锁定 !"); } } // 认证成功后 if (currentUser.isAuthenticated()) { System.out.println("用户 :" + currentUser.getPrincipal() + " 登陆成功!"); // 测试角色 System.out.println("是否拥有goodguy角色:" + currentUser.hasRole("goodguy")); // 测试权限 System.out.println("是否拥有winnebago:drive:eagle5权限:" + currentUser.isPermitted("winnebago:drive:eagle5")); // 用户退出 currentUser.logout(); } } }
pom.xml文件信息
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.yixing.maven.demo</groupId> <artifactId>maven-shiro-demo</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>war</packaging> <dependencies> <dependency> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> <version>1.1.3</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-core</artifactId> <version>1.4.0</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.2</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-war-plugin</artifactId> <version>2.6</version> <configuration> <failOnMissingWebXml>false</failOnMissingWebXml> </configuration> </plugin> </plugins> </build> </project>
shiro.ini信息:
[users] # admin=admin 分别表示账号和密码,administrator 表示逗号前边的账号拥有 administrator 这个角色。 admin=admin,administrator zhangsan=zhangsan,manager lisi=lisi,guest lonestarr=vespa,goodguy,schwartz [roles] # administrator 表示角色名称,* 表示这个角色拥有所有权限 administrator=* manager=user:*,department:* guest=user:query,department:query admin=* schwartz=lightsaber:* goodguy=winnebago:drive:eagle5
运行结果: