https://www.ibm.com/developerworks/cn/web/wa-apacheshiro/index.htmlhtml
Apache Shiro 是一个框架,可用于身份验证和受权。本文提供了几个示例用来展现如何在 Java™ 应用程序中使用 Shiro 并给出了如何在一个 Grails web 应用程序中使用它的概述。为了从本文中最大限度地受益,您应该习惯于建立 Java 应用程序并安装了以下的几个组件:java
在对系统进行安全保障时,有两个安全性元素很是重要:身份验证和受权。虽然这两个术语表明的是不一样的含义,但出于它们在应用程序安全性方面各自的角色考虑,它们有时会被交换使用。ios
身份验证 指的是验证用户的身份。在验证用户身份时,须要确认用户的身份的确如他们所声称的那样。在大多数应用程序中,身份验证是经过用户名和密码的组合完成的。只要用户选择了他人很难猜到的密码,那么用户名和密码的组合一般就足以确立身份。可是,还有其余的身份验证方式可用,好比指纹、证书和生成键。web
一旦身份验证过程成功地创建起身份,受权 就会接管以便进行访问的限制或容许。 因此,有这样的可能性:用户虽然经过了身份验证能够登陆到一个系统,可是未通过受权,不许作任何事情。还有一种多是用户虽然具备了某种程度的受权,却并未通过身份验证。算法
在为应用程序规划安全性模型时,必须处理好这两个元素以确保系统具备足够的安全性。身份验证是应用程序常见的问题(特别是在只有用户和密码组合的状况下),因此让框架来处理这项工做是一个很好的作法。合理的框架可提供通过测试和维护的优点,让您能够集中精力处理业务问题,而不是解决其解决方案已经实现的问题。数据库
Apache Shiro 提供了一个可用的安全性框架,各类客户机均可将这个框架应用于它们的应用程序。本文中的这些例子旨在介绍 Shiro 并着重展现对用户进行身份验证的基本任务。apache
回页首编程
Shiro 是一个用 Java 语言实现的框架,经过一个简单易用的 API 提供身份验证和受权。使用 Shiro,您就可以为您的应用程序提供安全性而又无需从头编写全部代码。设计模式
因为 Shiro 提供具备诸多不一样数据源的身份验证,以及 Enterprise Session Management,因此是实现单点登陆(SSO)的理想之选 — 大型企业内的一个理想特性,由于在大型企业内,用户须要在一天内常常登陆到并使用不一样系统。这些数据源包括 JDBC、LDAP、 Kerberos 和 Microsoft® Active Directory® Directory Services (AD DS)。
Shiro 的 Session
对象容许无需 HttpSession
便可使用一个用户会话。经过使用一个通用的 Session
对象,即使该代码没有在一个 Web 应用程序中运行,仍可使用相同的代码。没有对应用服务器或 Web 应用服务器会话管理的依赖,您甚至能够在命令行环境中使用 Shiro。换言之,使用 Shiro 的 API 编写的代码让您能够构建链接到 LDAP 服务器的命令行应用程序而且与 web 应用程序内用来访问 LDAP 服务器的代码相同。
Shiro 是预构建的二进制发行版。您能够下载 Shiro JAR 文件或将各项放入到 Apache Maven 或 Apache Ivy 来自动安装这些文件。本例使用 Ivy 下载 Shiro JAR 文件以及其余所须要的库,脚本很简单,如 清单 1 所示。
<?xml version="1.0" encoding="UTF-8"?> <ivy-module version="2.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://ant.apache.org/ivy/schemas/ivy.xsd"> <info organisation="com.nathanagood.examples" module="shirotest" /> <configurations> <conf name="dist" description="Dependency configuration for distribution." /> </configurations> <dependencies> <dependency org="commons-logging" name="commons-logging" rev="1.1.1" conf="dist->default" /> <dependency org="org.slf4j" name="slf4j-log4j12" rev="1.5.8" conf="dist->default" /> <dependency org="org.apache.shiro" name="shiro-core" rev="1.0.0-incubating" conf="dist->default" /> <dependency org="org.apache.shiro" name="shiro-web" rev="1.0.0-incubating" conf="dist->default" /> </dependencies> </ivy-module> <project name="shiroTestApp" default="usage" basedir="." xmlns:ivy="antlib:org.apache.ivy.ant"> <property name="project.lib" value="lib" /> <path id="ivy.task.path"> <fileset dir="${basedir}/ivy-lib"> <include name="**/*.jar" /> </fileset> </path> <target name="resolve"> <taskdef resource="org/apache/ivy/ant/antlib.xml" uri="antlib:org.apache.ivy.ant" classpathref="ivy.task.path" /> <ivy:resolve /> <ivy:retrieve pattern="${project.lib}/[conf]/[artifact].[ext]" sync="true" /> </target> <target name="usage"> <echo message="Use --projecthelp to learn more about this project" /> </target> </project>
有关使用 Ivy 的更多信息,参见 参考资料。若是不使用 Maven 或 Ivy,可用从本文 参考资料 部分提供的下载站点下载这些 Shiro JAR 文件。
下载了这些库后,只需将它们添加到 CLASSPATH。编写 清单 2 内所示的简单代码,它得到对当前用户的一个引用并报告说用户未经身份验证。(使用 Subject
类来表明此用户。)
package com.nathanagood.examples.shirotest; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.config.IniSecurityManagerFactory; import org.apache.shiro.subject.Subject; import org.apache.shiro.util.Factory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class ShiroTest { private static Logger logger = LoggerFactory.getLogger(ShiroTest.class); public static void main(String[] args) { // Using the IniSecurityManagerFactory, which will use the an INI file // as the security file. Factory<org.apache.shiro.mgt.SecurityManager> factory = new IniSecurityManagerFactory("auth.ini"); // Setting up the SecurityManager... org.apache.shiro.mgt.SecurityManager securityManager = factory.getInstance(); SecurityUtils.setSecurityManager(securityManager); Subject user = SecurityUtils.getSubject(); logger.info("User is authenticated: " + user.isAuthenticated()); } }
添加了此代码后,建立一个名为 auth.ini 的文件。此时,这个文件是空白的;它的做用只是为了可以在这里运行这个示例来检查代码是否正常工做。
建立文件后,运行这个示例。应该会看到包含了一个 INFO 登陆消息的输出,报告说用户没有登陆。
SecurityUtils
对象是一个 singleton,这意味着不一样的对象可使用它来得到对当前用户的访问。一旦成功地设置了这个SecurityManager
,就能够在应用程序不一样部分调用 SecurityUtils.getSubject()
来得到当前用户的信息。
在 Shiro 术语中,令牌 指的是一个键,可用它登陆到一个系统。最基本和经常使用的令牌是 UsernamePasswordToken
,用以指定用户的用户名和密码。
UsernamePasswordToken
类实现了 AuthenticationToken
接口,它提供了一种得到凭证和用户的主体(账户身份)的方式。UsernamePasswordToken
适用于大多数应用程序,而且您还能够在须要的时候扩展 AuthenticationToken
接口来将您本身得到凭证的方式包括进来。例如,能够扩展这个接口来提供您应用程序用来验证用户身份的一个关键文件的内容。
至此,这个简单的示例涵盖的内容包括:启动 Shiro SecurityManager
、得到当前用户并记录下用户未经身份验证。接下来的这个例子将会使用 UsernamePasswordToken
和存储在 INI 文件内的一个用户记录来展现如何经过用户名和密码进行身份验证。
清单 3 内所示的 auth.ini 文件如今包含了一个用户记录;这个记录包含了用户名和密码。您能够在这个记录内定义角色以及为应用程序提供受权。
[users] bjangles = dance
如今,建立 前一节 中介绍的这个 UsernamePasswordToken
对象,如 清单 4 所示。
// snipped... same as before. public class ShiroTest { private static Logger logger = LoggerFactory.getLogger(ShiroTest.class); public static void main(String[] args) { // Using the IniSecurityManagerFactory, which will use the an INI file // as the security file. Factory<org.apache.shiro.mgt.SecurityManager> factory = new IniSecurityManagerFactory("auth.ini"); // Setting up the SecurityManager... org.apache.shiro.mgt.SecurityManager securityManager = factory.getInstance(); SecurityUtils.setSecurityManager(securityManager); Subject user = SecurityUtils.getSubject(); logger.info("User is authenticated: " + user.isAuthenticated()); UsernamePasswordToken token = new UsernamePasswordToken("bjangles", "dance"); user.login(token); logger.info("User is authenticated: " + user.isAuthenticated()); } }
UsernamePasswordToken
对象由用户名和密码的组合实例化。随后,令牌被传递至 Subject
类的 login()
方法。
再次运行这个示例。注意到登陆消息如今报告说此用户已经身份验证。
要确保代码正常工做且得到的不是一个误报,在代码内更改密码或更改这个 INI 文件并再次运行此示例。login()
方法如今会抛出一个IncorrectCredentialsException
。在生产代码内这个异常应被明确捕获以便应用程序在用户提供了不正确的代码时可以进行恰当的响应。
若是用户不正确,login()
方法就会抛出一个 UnknownAccountException
。咱们既要考虑如何处理这个异常,但又不该向用户提供太多信息。一种常见的作法是不要向用户提示用户名有效、只有密码不正确。这是由于若是有人试图经过猜想得到访问,那么您绝对不会想要暗示此人他所猜想的用户名是正确的。
LDAP 是用来在 TCP/IP 上查询目录的一种协议。这些目录可保存有关用户的任意数量的信息,包括用户 ID、联系信息以及组成员等。LDAP 目录对于公司的通信簿颇有用并被普遍使用。
AD DS 是一种用于用户和组管理的常见目录,它支持 LDAP。 Shiro 不包含通用的 LDAP 安全域,但它却包含了一个ActiveDirectoryRealm
对象,容许针对 LDAP 进行用户的身份验证。本例使用了在 INI 文件内配置的 ActiveDirectoryRealm
对象来验证用户的身份。虽然 AD DS 与 LDAP 不一样,但本文中使用的 Shiro 的这个版本并无自带通用的 LDAP 对象。
让一个 LDAP 服务器来测试本例要比编写并运行示例自己须要更多工做。若是您不能访问一个 AD DS 服务器,那么能够考虑下载并安装 Apache Directory 来提供一个 LDAP 服务器的样例实现。Apache Directory 是用 Java 语言编写的。一样地,Apache Active Directory Studio 是一个 Eclipse 插件,可用来浏览 LDAP 数据。它还具备一些样例数据,为您提供了一种快捷方式来针对已知值编写代码,而又无需疑惑您遇到的问题是代码问题仍是数据问题。
清单 5 显示了用来对存储在 Apache Directory 内的一个用户进行身份验证所需的代码。
// snipped... public class ShiroLDAPTest { private static Logger logger = LoggerFactory.getLogger(ShiroLDAPTest.class); /** * @param args */ public static void main(String[] args) { // Using the IniSecurityManagerFactory, which will use the an INI file // as the security file. Factory<org.apache.shiro.mgt.SecurityManager> factory = new IniSecurityManagerFactory("actived.ini"); // Setting up the SecurityManager... org.apache.shiro.mgt.SecurityManager securityManager = factory.getInstance(); SecurityUtils.setSecurityManager(securityManager); Subject user = SecurityUtils.getSubject(); logger.info("User is authenticated: " + user.isAuthenticated()); UsernamePasswordToken token = new UsernamePasswordToken( "cn=Cornelius Buckley,ou=people,o=sevenSeas", "argh"); user.login(token); logger.info("User is authenticated: " + user.isAuthenticated()); } }
除了 INI 文件名和用户名及密码以外,代码与以前用 INI 文件内的记录进行身份验证的代码相同。这种相似性的出现是由于您可使用 INI 文件来配置 Shiro。用来设置 Shiro 针对 Apache Directory 进行身份验证的这些 INI 记录如 清单 6 所示。
[main] activeDirectoryRealm = org.apache.shiro.realm.activedirectory.ActiveDirectoryRealm activeDirectoryRealm.systemUsername = uid=admin,ou=system activeDirectoryRealm.systemPassword = secret activeDirectoryRealm.searchBase = o=sevenSeas,ou=people activeDirectoryRealm.url = ldap://localhost:10389
注意: 我使用 Apache Directory Studio 来将用户的密码更改成一个能放入测试代码以确保它工做的值。
您可使用两种基本技巧将 Shiro 应用到 web 应用程序。首先,您可使用这个 API 来将这里所展现的的代码并入一个基础 servlet。其次,您可使用 Shiro 自带的 HTTP 过滤器。本例展现了第二种技巧,由于使用过滤器充分利用了内置 web 应用服务器技术以及来自 Shiro 项目的预先编写好的代码。
本例显示了如何使用 Grails 内的这些过滤器。Grails 是一个项目,旨在让您经过使用一种惯例优先原则(convention-over-configuration)的方式尽快地编写 Groovy web 应用程序。有关 Grails 的更多信息,请参见 参考资料。
对于 Shiro 过滤器,您一般会手动地向 web.xml 文件添加必需的过滤器项。可是,Grails 会在您每次启动应用程序时生成 web.xml 文件,所以没必要手动修改 web.xml。
幸运的是,Grails 提供了插件,可集成到 web.xml 生成过程并会让您也能够参与在 web.xml 文件内编写这些项。现在,Grails 的可用插件不少,包括面向 Shiro 的这个插件。建议尝试使用这个 Shiro Grails 插件,它提供了几个新的脚本,运行这些脚本能够建立不一样的域和控制器。
或者,若是您更愿意本身添加这些项并进行配置,您也能够编写您本身的插件。对于 Grails,编写一个新的插件很容易。要建立能将必需的 Shiro 过滤器项添加到 web.xml 文件的 Grails 插件,可使用以下命令:
> grails create-plugin ShiroWebXml
建立了这个插件项目后,编辑 ShiroWebXmlPlugin.groovy 文件,并添加如 清单 7 所示的代码。
class ShiroWebXmlPlugin { // snipped plugin details... def doWithWebDescriptor = { xml -> def filterElement = xml.'filter' def lastFilter = filterElement[filterElement.size() - 1] lastFilter + { 'filter' { 'filter-name'("ShiroFilter") 'filter-class'("org.apache.shiro.web.servlet.IniShiroFilter") 'init-param' { 'param-name'("config") 'param-value'("\n#config") } } } def filterMappingElement = xml.'filter-mapping' def lastFilterMappingElement = filterMappingElement[filterMappingElement.size() - 1] lastFilterMappingElement + { 'filter-mapping' { 'filter-name'("ShiroFilter") 'url-pattern'("/*") } } } }
当 Grails 执行这个 web.xml 文件时,此代码就会运行。
在启动这个插件应用程序测试它以前,先将用 Ivy 下载的 Shiro JAR 文件复制到此插件的 lib 文件夹。有了 JAR 文件后,用如下命令测试此插件是否能正常工做:
grails run-app
若是此插件应用程序成功启动,您能够将它打包以便用在一个示例项目中。要打包这个插件,使用以下命令:
grails package-plugin
能够用以下命令安装这个新的 ShiroWebXmlPlugin
:
cd myapp grails install-plugin /path/to/shiro-web-xml-1.0.zip
若是出现了 UnavailableSecurityManagerException
,那么多是 SecurityManager
未被正确设置。在调用 getSubject()
方法以前,请确保它设在 SecurityUtils
对象上。
链接到 LDAP 服务器可能会有些困难。若是获得一个 javax.naming.CommunicationException
,那么请检查此 LDAP 服务器的主机名和端口。即使您没有使用 Apache Directory,Apache Directory Studio(可单独安装)也能够帮助您排除链接故障以及名称问题。
若是您没有使用 Ant Ivy 脚原本初始化您的环境,那么有可能会获得丢失类的错误。即使没有 Apache Commons Logging library (commons-logging
),这个 INI 文件示例仍可运行,可是运行这个 LDAP 示例会致使一个异常。这时,可使用 Apace Commons BeanUtils
来解析这个 INI 文件并设置对象上的值。
Shiro 是 Apache Incubator 内的一个框架,可以让您向应用程序中添加身份验证和受权。它支持不一样的身份验证存储方式,好比 LDAP、Kerberos 和 AD DS。Shiro 最小限度的依赖性再加上它相对简单的配置让它成为了应用程序内安全性框架的一个很好的选择。
在尝试保护你的应用时,你是否有过挫败感?是否以为现有的Java安全解决方案难以使用,只会让你更糊涂?本文介绍的Apache Shiro,是一个不一样寻常的Java安全框架,为保护应用提供了简单而强大的方法。本文还解释了Apache Shiro的项目目标、架构理念以及如何使用Shiro为应用安全保驾护航。
Apache Shiro(发音为“shee-roh”,日语“堡垒(Castle)”的意思)是一个强大易用的Java安全框架,提供了认证、受权、加密和会话管理功能,可为任何应用提供安全保障 - 从命令行应用、移动应用到大型网络及企业应用。
Shiro为解决下列问题(我喜欢称它们为应用安全的四要素)提供了保护应用的API:
Shiro还支持一些辅助特性,如Web应用安全、单元测试和多线程,它们的存在强化了上面提到的四个要素。
对于一个框架来说,使其有存在价值的最好例证就是有让你去用它的缘由,它应该能完成一些别人没法作到的事情。要理解这一点,须要了解Shiro的历史以及建立它时的其余替代方法。
在2008年加入Apache软件基金会以前,Shiro已经5岁了,以前它被称为JSecurity项目,始于2003年初。当时,对于Java应用开发人员而言,没有太多的通用安全替代方案 - 咱们被Java认证/受权服务(或称为JAAS)牢牢套牢了。JAAS有太多的缺点 - 尽管它的认证功能尚可忍受,但受权方面却显得拙劣,用起来使人沮丧。此外,JAAS跟虚拟机层面的安全问题关系很是紧密,如判断JVM中是否容许装入一个类。做为应用开发者,我更关心应用最终用户能作什么,而不是个人代码在JVM中能作什么。
因为当时正从事应用开发,我也须要一个干净、容器无关的会话机制。在当时,“这场游戏”中惟一可用的会话是HttpSessions,它须要Web容器;或是EJB 2.1里的有状态会话Bean,这又要EJB容器。而我想要的一个与容器脱钩、可用于任何我选择的环境中的会话。
最后就是加密问题。有时,咱们须要保证数据安全,可是Java密码架构(Java Cryptography Architecture)让人难以理解,除非你是密码学专家。API里处处都是Checked Exception,用起来很麻烦。我须要一个干净、开箱即用的解决方案,能够在须要时方便地对数据加密/解密。
因而,纵观2003年初的安全情况,你会很快意识到尚未一个大一统的框架知足全部上述需求。有鉴于此,JSecurity(即以后的Apache Shiro)诞生了。
从2003年至今,框架选择方面的状况已经改变了很多,但今天仍有使人信服的理由让你选择Shiro。其实理由至关多,Apache Shiro:
Shiro及其前身JSecurity已被各类规模和不一样行业的公司项目采用多年。自从成为Apache软件基金会的顶级项目后,站点流量和使用呈持续增加态势。许多开源社区也正在用Shiro,这里有些例子如Spring,Grails,Wicket,Tapestry,Tynamo,Mule和Vaadin。
如Katasoft,Sonatype,MuleSoft这样的商业公司,一家大型社交网络和多家纽约商业银行都在使用Shiro来保护他们的商业软件和站点。
既然已经描述了Shiro的好处,那就让咱们看看它的API,好让你可以有个感性认识。Shiro架构有三个主要概念 - Subject,SecurityManager和Realms。
在考虑应用安全时,你最常问的问题多是“当前用户是谁?”或“当前用户容许作X吗?”。当咱们写代码或设计用户界面时,问本身这些问题很日常:应用一般都是基于用户故事构建的,而且你但愿功能描述(和安全)是基于每一个用户的。因此,对于咱们而言,考虑应用安全的最天然方式就是基于当前用户。Shiro的API用它的Subject概念从根本上体现了这种思考方式。
Subject一词是一个安全术语,其基本意思是“当前的操做用户”。称之为“用户”并不许确,由于“用户”一词一般跟人相关。在安全领域,术语“Subject”能够是人,也能够是第三方进程、后台账户(Daemon Account)或其余相似事物。它仅仅意味着“当前跟软件交互的东西”。但考虑到大多数目的和用途,你能够把它认为是Shiro的“用户”概念。在代码的任何地方,你都能轻易的得到Shiro Subject,参见以下代码:
清单1. 得到Subject
import org.apache.shiro.subject.Subject; import org.apache.shiro.SecurityUtils; ... Subject currentUser = SecurityUtils.getSubject();
一旦得到Subject,你就能够当即得到你但愿用Shiro为当前用户作的90%的事情,如登陆、登出、访问会话、执行受权检查等 - 稍后还会看到更多。这里的关键点是Shiro的API很是直观,由于它反映了开发者以‘每一个用户’思考安全控制的天然趋势。同时,在代码的任何地方都能很轻松地访问Subject,容许在任何须要的地方进行安全操做。
Subject的“幕后”推手是SecurityManager。Subject表明了当前用户的安全操做,SecurityManager则管理全部用户的安全操做。它是Shiro框架的核心,充当“保护伞”,引用了多个内部嵌套安全组件,它们造成了对象图。可是,一旦SecurityManager及其内部对象图配置好,它就会退居幕后,应用开发人员几乎把他们的全部时间都花在Subject API调用上。
那么,如何设置SecurityManager呢?嗯,这要看应用的环境。例如,Web应用一般会在Web.xml中指定一个Shiro Servlet Filter,这会建立SecurityManager实例,若是你运行的是一个独立应用,你须要用其余配置方式,但有不少配置选项。
一个应用几乎老是只有一个SecurityManager实例。它实际是应用的Singleton(尽管没必要是一个静态Singleton)。跟Shiro里的几乎全部组件同样,SecurityManager的缺省实现是POJO,并且可用POJO兼容的任何配置机制进行配置 - 普通的Java代码、Spring XML、YAML、.properties和.ini文件等。基原本讲,可以实例化类和调用JavaBean兼容方法的任何配置形式均可使用。
为此,Shiro借助基于文本的INI配置提供了一个缺省的“公共”解决方案。INI易于阅读、使用简单而且须要极少依赖。你还能看到,只要简单地理解对象导航,INI可被有效地用于配置像SecurityManager那样简单的对象图。注意,Shiro还支持Spring XML配置及其余方式,但这里只咱们只讨论INI。
下列清单2列出了基于INI的Shiro最简配置:
清单2. 用INI配置Shiro
[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
在清单2中,咱们看到了用于配置SecurityManager实例的INI配置例子。有两个INI段落:[main]和[users].
[main]段落是配置SecurityManager对象及其使用的其余任何对象(如Realms)的地方。在示例中,咱们看到配置了两个对象:
[users]段落是指定用户账户静态列表的地方 - 为简单应用或测试提供了方便。
就介绍而言,详细了解每一个段落的细节并非重点。相反,看到INI配置是一种配置Shiro的简单方式才是关键。关于INI配置的更多细节,请参见Shiro文档。
清单3. 装入shiro.ini配置文件
import org.apache.shiro.SecurityUtils; import org.apache.shiro.config.IniSecurityManagerFactory; import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.util.Factory; ... //1.装入INI配置 Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini"); //2. 建立SecurityManager SecurityManager securityManager = factory.getInstance(); //3. 使其可访问 SecurityUtils.setSecurityManager(securityManager);
在清单3的示例中,咱们看到有三步:
Shiro的第三个也是最后一个概念是Realm。Realm充当了Shiro与应用安全数据间的“桥梁”或者“链接器”。也就是说,当切实与像用户账户这类安全相关数据进行交互,执行认证(登陆)和受权(访问控制)时,Shiro会从应用配置的Realm中查找不少内容。
从这个意义上讲,Realm实质上是一个安全相关的DAO:它封装了数据源的链接细节,并在须要时将相关数据提供给Shiro。当配置Shiro时,你必须至少指定一个Realm,用于认证和(或)受权。配置多个Realm是能够的,可是至少须要一个。
Shiro内置了能够链接大量安全数据源(又名目录)的Realm,如LDAP、关系数据库(JDBC)、相似INI的文本配置资源以及属性文件等。若是缺省的Realm不能知足需求,你还能够插入表明自定义数据源的本身的Realm实现。下面的清单4是经过INI配置Shiro使用LDAP目录做为应用Realm的示例。
清单4. Realm配置示例片断:链接存储用户数据的LDAP
[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环境,下面让咱们来讨论,做为一名开发者该如何使用这个框架。
认证是核实用户身份的过程。也就是说,当用户使用应用进行认证时,他们就在证实他们就是本身所说的那我的。有时这也理解为“登陆”。它是一个典型的三步骤过程。
这个过程的常见例子是你们都熟悉的“用户/密码”组合。多数用户在登陆软件系统时,一般提供本身的用户名(当事人)和支持他们的密码(证书)。若是存储在系统中的密码(或密码表示)与用户提供的匹配,他们就被认为经过认证。
Shiro以简单直观的方式支持一样的流程。正如咱们前面所说,Shiro有一个以Subject为中心的API - 几乎你想要用Shiro在运行时完成的全部事情都能经过与当前执行的Subject进行交互而达成。所以,要登陆Subject,只须要简单地调用它的login方法,传入表示被提交当事人和证书(在这种状况下,就是用户名和密码)的AuthenticationToken实例。示例如清单5中所示:
清单5. Subject登陆
//1. 接受提交的当事人和证书: AuthenticationToken token = new UsernamePasswordToken(username, password); //2. 获取当前Subject: Subject currentUser = SecurityUtils.getSubject(); //3. 登陆: currentUser.login(token);
你能够看到,Shiro的API很容易地就反映了这个常见流程。你将会在全部的Subject操做中继续看到这种简单风格。在调用了login方法后,SecurityManager会收到AuthenticationToken,并将其发送给已配置的Realm,执行必须的认证检查。每一个Realm都能在必要时对提交的AuthenticationTokens做出反应。可是若是登陆失败了会发生什么?若是用户提供了错误密码又会发生什么?经过对Shiro的运行时AuthenticationException作出反应,你能够控制失败,参见清单6。
清单6. 控制失败的登陆
//3. 登陆: try { currentUser.login(token); } catch (IncorrectCredentialsException ice) { … } catch (LockedAccountException lae) { … } … catch (AuthenticationException ae) {… }
你能够选择捕获AuthenticationException的一个子类,做出特定的响应,或者对任何AuthenticationException作通常性处理(例如,显示给用户普通的“错误的用户名或密码”这类消息)。选择权在你,能够根据应用须要作出选择。
Subject登陆成功后,他们就被认为是已认证的,一般你会容许他们使用你的应用。可是仅仅证实了一个用户的身份并不意味着他们能够对你的应用随心所欲。这就引出了另外一个问题,“我如何控制用户能作或不能作哪些事情?”,决定用户容许作哪些事情的过程被称为受权。下面咱们将谈谈Shiro如何进行受权。
受权实质上就是访问控制 - 控制用户可以访问应用中的哪些内容,好比资源、Web页面等等。多数用户执行访问控制是经过使用诸如角色和权限这类概念完成的。也就是说,一般用户容许或不容许作的事情是根据分配给他们的角色或权限决定的。那么,经过检查这些角色和权限,你的应用程序就能够控制哪些功能是能够暴露的。如你指望的,Subject API让你能够很容易的执行角色和权限检查。如清单7中的代码片断所示:如何检查Subject被分配了某个角色:
列表7. 角色检查
if ( subject.hasRole(“administrator”) ) { //显示‘Create User’按钮 } else { //按钮置灰? }
如你所见,你的应用程序可基于访问控制检查打开或关闭某些功能。
权限检查是执行受权的另外一种方法。上例中的角色检查有个很大的缺陷:你没法在运行时增删角色。角色名字在这里是硬编码,因此,若是你修改了角色名字或配置,你的代码就会乱套!若是你须要在运行时改变角色含义,或想要增删角色,你必须另辟蹊径。
为此,Shiro支持了权限(permissions)概念。权限是功能的原始表述,如‘开门’,‘建立一个博文’,‘删除‘jsmith’用户’等。经过让权限反映应用的原始功能,在改变应用功能时,你只须要改变权限检查。进而,你能够在运行时按需将权限分配给角色或用户。
如清单8中,咱们重写了以前的用户检查,取而代之使用权限检查。
清单8. 权限检查
if ( subject.isPermitted(“user:create”) ) { //显示‘Create User’按钮 } else { //按钮置灰? }
这样,任何具备“user:create”权限的角色或用户均可以点击‘Create User’按钮,而且这些角色和指派甚至能够在运行时改变,这给你提供了一个很是灵活的安全模型。
“user:create”字符串是一个权限字符串的例子,它遵循特定的解析惯例。Shiro借助它的WildcardPermission支持这种开箱即用的惯例。尽管这超出了本文的范围,你会看到在建立安全策略时,WildcardPermission很是灵活,甚至支持像实例级别访问控制这样的功能。
清单9. 实例级别的权限检查
if ( subject.isPermitted(“user:delete:jsmith”) ) { //删除‘jsmith’用户 } else { //不删除‘jsmith’ }
该例代表,你能够对你须要的单个资源进行访问控制,甚至深刻到很是细粒度的实例级别。若是愿意,你甚至还能够发明本身的权限语法。参见Shiro Permission文档能够了解更多内容。最后,就像使用认证那样,上述调用最终会转向SecurityManager,它会咨询Realm作出本身的访问控制决定。必要时,还容许单个Realm同时响应认证和受权操做。
以上就是对Shiro受权功能的简要概述。虽然多数安全框架止于受权和认证,但Shiro提供了更多功能。下面,咱们将谈谈Shiro的高级会话管理功能。
在安全框架领域,Apache Shiro提供了一些独特的东西:可在任何应用或架构层一致地使用Session API。即,Shiro为任何应用提供了一个会话编程范式 - 从小型后台独立应用到大型集群Web应用。这意味着,那些但愿使用会话的应用开发者,没必要被迫使用Servlet或EJB容器了。或者,若是正在使用这些容器,开发者如今也能够选择使用在任何层统一一致的会话API,取代Servlet或EJB机制。
但Shiro会话最重要的一个好处或许就是它们是独立于容器的。这具备微妙但很是强大的影响。例如,让咱们考虑一下会话集群。对集群会话来说,支持容错和故障转移有多少种容器特定的方式?Tomcat的方式与Jetty的不一样,而Jetty又和Websphere不同,等等。但经过Shiro会话,你能够得到一个容器无关的集群解决方案。Shiro的架构容许可插拔的会话数据存储,如企业缓存、关系数据库、NoSQL系统等。这意味着,只要配置会话集群一次,它就会以相同的方式工做,跟部署环境无关 - Tomcat、Jetty、JEE服务器或者独立应用。无论如何部署应用,毋须从新配置应用。
Shiro会话的另外一好处就是,若是须要,会话数据能够跨客户端技术进行共享。例如,Swing桌面客户端在须要时能够参与相同的Web应用会话中 - 若是最终用户同时使用这两种应用,这样的功能会颇有用。那你如何在任何环境中访问Subject的会话呢?请看下面的示例,里面使用了Subject的两个方法。
清单10. Subject的会话
Session session = subject.getSession(); Session session = subject.getSession(boolean create);
如你所见,这些方法在概念上等同于HttpServletRequest API。第一个方法会返回Subject的现有会话,或者若是尚未会话,它会建立一个新的并将之返回。第二个方法接受一个布尔参数,这个参数用于断定会话不存在时是否建立新会话。一旦得到Shiro的会话,你几乎能够像使用HttpSession同样使用它。Shiro团队以为对于Java开发者,HttpSession API用起来太舒服了,因此咱们保留了它的不少感受。固然,最大的不一样在于,你能够在任何应用中使用Shiro会话,不只限于Web应用。清单11中显示了这种类似性。
清单11. 会话的方法
Session session = subject.getSession(); session.getAttribute("key", someValue); Date start = session.getStartTimestamp(); Date timestamp = session.getLastAccessTime(); session.setTimeout(millis); ...
加密是隐藏或混淆数据以免被偷窥的过程。在加密方面,Shiro的目标是简化并让JDK的加密支持可用。
清楚一点很重要,通常状况下,加密不是特定于Subject的,因此它是Shiro API的一部分,但并不特定于Subject。你能够在任何地方使用Shiro的加密支持,甚至在不使用Subject的状况下。对于加密支持,Shiro真正关注的两个领域是加密哈希(又名消息摘要)和加密密码。下面咱们来看看这两个方面的详细描述。
若是你曾使用过JDK的MessageDigest类,你会马上意识到它的使用有点麻烦。MessageDigest类有一个笨拙的基于工厂的静态方法API,它不是面向对象的,而且你被迫去捕获那些永远都没必要捕获的Checked Exceptions。若是须要输出十六进制编码或Base64编码的消息摘要,你只有靠本身 - 对上述两种编码,没有标准的JDK支持它们。Shiro用一种干净而直观的哈希API解决了上述问题。
打个比方,考虑比较常见的状况,使用MD5哈希一个文件,并肯定该哈希的十六进制值。被称为‘校验和’,这在提供文件下载时经常使用到 - 用户能够对下载文件执行本身的MD5哈希。若是它们匹配,用户彻底能够认定文件在传输过程当中没有被篡改。
不使用Shiro,你须要以下步骤才能完成上述内容:
清单12. JDK的消息摘要
try { MessageDigest md = MessageDigest.getInstance("MD5"); md.digest(bytes); byte[] hashed = md.digest(); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); }
对于这样简单广泛的需求,这个工做量实在太大了。如今看看Shiro是如何作一样事情的:
String hex = new Md5Hash(myFile).toHex();
当使用Shiro简化全部这些工做时,一切都很是简单明了。完成SHA-512哈希和密码的Base64编码也同样简单。
String encodedPassword = new Sha512Hash(password, salt, count).toBase64();
你能够看到Shiro对哈希和编码简化了很多,挽救了你处理在这类问题上所消耗的脑细胞。
加密是使用密钥对数据进行可逆转换的加密算法。咱们使用其保证数据的安全,尤为是传输或存储数据时,以及在数据容易被窥探的时候。
若是你曾经用过JDK的Cryptography API,特别是javax.crypto.Cipher类,你会知道它是一头须要驯服的极其复杂的野兽。对于初学者,每一个可能的加密配置老是由一个javax.crypto.Cipher实例表示。必须进行公钥/私钥加密?你得用Cipher。须要为流操做使用块加密器(Block Cipher)?你得用Cipher。须要建立一个AES 256位Cipher来保护数据?你得用Cipher。你懂的。
那么如何建立你须要的Cipher实例?您得建立一个非直观、标记分隔的加密选项字符串,它被称为“转换字符串(transformation string)”,把该字符串传给Cipher.getInstance静态工厂方法。这种字符串方式的cipher选项,并无类型安全以确保你正在用有效的选项。这也暗示没有JavaDoc帮你了解相关选项。而且,若是字符串格式组织不正确,你还须要进一步处理Checked Exception,即使你知道配置是正确的。如你所见,使用JDK Cipher是一项至关繁重的任务。好久之前,这些技术曾经是Java API的标准,可是世事变迁,咱们须要一种更简单的方法。
Shiro经过引入它的CipherService API试图简化加密密码的整个概念。CipherService是多数开发者在保护数据时求之不得的东西:简单、无状态、线程安全的API,可以在一次方法调用中对整个数据进行加密或解密。你所须要作的只是提供你的密钥,就可根据须要加密或解密。以下列清单13中,使用256位AES加密:
清单13. Apache Shiro的加密API
AesCipherService cipherService = new AesCipherService(); cipherService.setKeySize(256); //建立一个测试密钥: byte[] testKey = cipherService.generateNewKey(); //加密文件的字节: byte[] encrypted = cipherService.encrypt(fileBytes, testKey);
较之JDK的Cipher API,Shiro的示例要简单的多:
Shiro的CipherService API还有其余好处,如同时支持基于字节数组的加密/解密(称为“块”操做)和基于流的加密/解密(如加密音频或视频)。
没必要再忍受Java Cryptography带来的痛苦。Shiro的Cryptography支持就是为了减小你在确保数据安全上付出的努力。
最后,但并不是不重要,咱们将简单介绍一下Shiro的Web支持。Shiro附带了一个帮助保护Web应用的强建的Web支持模块。对于Web应用,安装Shiro很简单。惟一须要作的就是在web.xml中定义一个Shiro Servlet过滤器。清单14中,列出了代码。
清单14. web.xml中的ShiroFilter
<filter> <filter-name>ShiroFilter</filter-name> <filter-class> org.apache.shiro.web.servlet.IniShiroFilter </filter-class> <!-- 没有init-param属性就表示从classpath:shiro.ini装入INI配置 --> </filter> <filter-mapping> <filter-name>ShiroFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
这个过滤器能够读取上述shiro.ini配置,这样不论什么开发环境,你都拥有了一致的配置体验。一旦完成配置,Shiro Filter就会过滤每一个请求而且确保在请求期间特定请求的Subject是可访问的。同时因为它过滤了每一个请求,你能够执行安全特定的逻辑以保证只有知足必定标准的请求才被容许经过。
Shiro经过其创新的URL过滤器链功能支持安全特定的过滤规则。它容许你为任何匹配的URL模式指定非正式的过滤器链。这意味着, 使用Shiro的过滤器机制,你能够很灵活的强制安全规则(或者规则的组合) - 其程度远远超过你单独在web.xml中定义过滤器时所得到的。清单15中显示了Shiro INI中的配置片断。
清单15. 路径特定的Filter链
[urls] /assets/** = anon /user/signup = anon /user/** = user /rpc/rest/** = perms[rpc:invoke], authc /** = authc
如你所见,Web应用可使用[urls] INI段落。对于每一行,等号左边的值表示相对上下文的Web应用路径。等号右边的值定义了过滤器链 - 一个逗号分隔的有序Servlet过滤器列表,它会针对给出的路径进行执行。每一个过滤器都是普通的Servlet过滤器,你看到的上面的过滤器名字(anon,user,perms,authc)是Shiro内置的安全相关的特殊过滤器。你能够搭配这些安全过滤器来建立高度定制的安全体验。你还能够指定任何其余现有的Servlet过滤器。
相比起使用web.xml,在其中先定义过滤器块,而后定义单独分离的过滤器模式块,这种方式带来的好处有多少?采用Shiro的方法,能够很容易就准确知道针对给定匹配路径执行的过滤器链。若是想这么作,你能够在web.xml中仅定义Shiro Filter,在shiro.ini中定义全部其余的过滤器和过滤器链,这要比web.xml简洁得多,并且更容易理解过滤器链定义机制。即便不使用Shiro的任何安全特性,单凭这样小小的方便之处,也值得让你使用Shiro。
Shiro还提供了JSP标签库,容许你根据当前Subject的状态控制JSP页面的输出。一个有用的常见示例是在用户登陆后显示“Hello <username>"文本。但如果匿名用户,你可能想要显示其余内容,如换而显示“Hello! Register Today!”。清单16显示了如何使用Shiro的JSP标签实现这个示例:
清单16. JSP 标签库示例
<%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %> ... <p>Hello <shiro:user> <!-- shiro:principal打印出了Subject的主当事人 - 在这个示例中,就是用户名: --> <shiro:principal/>! </shiro:user> <shiro:guest> <!-- 没有登陆 - 就认为是Guest。显示注册连接: --> ! <a href=”register.jsp”>Register today!</a> </shiro:guest> </p>
除了上面例子用到的标签,还有其余标签可让你根据用户属于(或不属于)的角色,分配(或未分配)的权限,是否已认证,是否来自“记住我”服务的记忆,或是匿名访客,包含输出。
Shiro还支持其余许多Web特性,如简单的“记住我”服务,REST和BASIC认证。固然,若是想使用Shiro原生的企业会话,它还提供透明的HttpSession支持。参见Apache Shiro Web文档能够了解更多内容。
最后值得一提的是Shiro在Web环境中对会话的支持。
对于Web应用,Shiro缺省将使用咱们习觉得常的Servlet容器会话做为其会话基础设施。即,当你调用subject.getSession()和subject.getSession(boolean)方法时,Shiro会返回Servlet容器的HttpSession实例支持的Session实例。这种方式的曼妙之处在于调用subject.getSession()的业务层代码会跟一个Shiro Session实例交互 - 尚未“认识”到它正跟一个基于Web的HttpSession打交道。这在维护架构层之间的清晰隔离时,是一件很是好的事情。
若是你因为须要Shiro的企业级会话特性(如容器无关的集群)而打开了Shiro的原生会话管理,你固然但愿HttpServletRequest.getSession()和HttpSession API能和“原生”会话协做,而非Servlet容器会话。若是你不得不重构全部使用HttpServletRequest和HttpSession API的代码,使用Shiro的Session API来替换,这将很是使人沮丧。Shiro固然历来不会指望你这么作。相反,Shiro完整实现了Servlet规范中的Session部分以在Web应用中支持原生会话。这意味着,无论什么时候你使用相应的HttpServletRequest或HttpSession方法调用,Shiro都会将这些调用委托给内部的原生会话API。结果,你无需修改Web代码,即使是你正在使用Shiro的‘原生’企业会话管理 - 确实是一个很是方便(且必要)的特性。
Apache Shiro框架还包含有对保护Java应用很是有用的其余特性,如:
常识告诉咱们,Apache Shiro不是“银弹” - 它不能绝不费力的解决全部安全问题。以下是Shiro还未解决,可是值得知道的:
Apache Shiro社区天天都在壮大,借此,Shiro的特性亦是如此。在即将发布的版本中,你可能会看到:
Apache Shiro是一个功能齐全、健壮、通用的Java安全框架,你能够用其为你的应用护航。经过简化应用安全的四个领域,即认证、受权、会话管理和加密,在真实应用中,应用安全能更容易被理解和实现。Shiro的简单架构和兼容JavaBean使其几乎可以在任何环境下配置和使用。附加的Web支持和辅助功能,好比多线程和测试支持,让这个框架为应用安全提供了“一站式”服务。Apache Shiro开发团队将继续前进,精炼代码库和支持社区。随着持续被开源和商业应用采纳,能够预期Shiro会继续发展壮大。
关于做者
Les Hazlewood是Apache Shiro PMC主席以及Katasoft的CTO和合伙创办人,该公司是专一于应用安全产品以及提供Apache Shiro专业支持的创业公司。做为专业Java开发人员及企业架构师,以及以前Bloomberg,Delta Airlines和JBoss的高级角色,Les拥有10年的的经验。Les积极从事开源开发已有9年时间,提交或作出贡献的项目有Spring框架、Hibernate、JBoss,OpenSpaces,固然还有JSecurity,Apache Shiro的前身。Les目前居住在加州的圣马特奥,不编程时,他会练习剑道和学习日语。