Apache Shiro官网:http://shiro.apache.org/
概念:
shiro是一个强大灵活的开源安全框架,可以干净利落的处理认证、授权、企业会话管理和加密。
shiro的首要目标是简单和易于理解。安全有时候是非常复杂令人头疼的问题,shiro隐藏了复杂
的细节,暴露了简介直观的API来简化开发者使他们的应用程序更安全的努力。
功能:进行用户认证、执行访问控制、单点登录、Remember Me服务
安全的一些概念:身份认证、授权、会话管理、加密被认为是构成应用安全的基础要素。
身份认证,就是验证用户身份,典型的“登录”过程就是身份认证的过程。
授权:也就是访问控制(ac),也即某用户可以可以使用应用的哪些功能?
会议管理:管理用户特定的会话,即使在非Web或EJB应用。
加密:保持数据的安全使用的加密算法,同时仍然易于使用。
对于Session,在WEB应用中它基于HttpSession,而一般应用中默认使用企业会话管理。shiro的Session
用起来和HttpSession差不多,并且使用它不需要Http环境。
在最高的层次上讲,shiro主要有三个主要的概念,它们分别是Subject、SecurityManager和Realms。
1、shiro使用Subject这一概念来描述当前用户,‘User’这个词通常隐含的指一个人,但是Subject可以是一个
人也可以代表第三方服务,守护账户,定时作业,或者其他类似的与软件交互的事物。
2、所有Subject实例都与一个SecurityManager绑定,当你和一个Subject交互时,所有交互都会通过SecurityManager
转换为特定Subject的交互。
SecurityManager是shiro架构的核心,它扮演‘保护伞’对象的角色,与其他内部安全组件一起构成对象图的核心。
SecurityManager及其内贸部对象图一旦被应用配置,它基本上就被放在一边不再被程序员使用了,
开发人员基本上总是在与Subject的相关API打交道。它在幕后支持Subject的安全操作。
3、Realms在shiro与你的应用程序的安全数据之间充当着桥梁或者说是连接器的作用。
从这种意义上讲,Reaml就是一个特定的安全DAO,它为shiro提供所需的数据。
在配置shiro时,你必须为认证、授权指定至少一个可用的Realm。
shiro提供了现成的Realm连接到LDAP、关系数据库(JDBC)、ini和properties文件等。
用户可以使用自己的Realm
Shiro 是一个用 Java 语言实现的框架,通过一个简单易用的 API 提供身份验证和授权。使用 Shiro,您就能够为您的应用程序提供安全性而又无需从头编写所有代码。
Shiro 的Session
对象允许无需HttpSession
即可使用一个用户会话。通过使用一个通用的Session
对象,即便该代码没有在一个 Web 应用程序中运行,仍可以使用相同的代码。没有对应用服务器或 Web 应用服务器会话管理的依赖,您甚至可以在命令行环境中使用 Shiro。换言之,使用 Shiro 的 API 编写的代码让您可以构建连接到 LDAP 服务器的命令行应用程序并且与 web 应用程序内用来访问 LDAP 服务器的代码相同。由于 Shiro 提供具有诸多不同数据源的身份验证,以及 Enterprise Session Management,所以是实现单点登录(SSO)的理想之选 — 大型企业内的一个理想特性,因为在大型企业内,用户需要在一天内经常登录到并使用不同系统。这些数据源包括 JDBC、LDAP、 Kerberos 和 Microsoft Active Directory Directory Services (AD DS)。
Shiro 是预构建的二进制发行版。您可以下载 Shiro JAR 文件或将各项放入到 Apache Maven 或 Apache Ivy 来自动安装这些文件。本例使用 Ivy 下载 Shiro JAR 文件以及其他所需要的库,脚本很简单,如清单 1所示。
1. Apache Ivy 文件和 Apache Ant 脚本
<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>
有关使用 Ivy 的更多信息,参见参考资料。如果不使用 Maven 或 Ivy,可用从本文参考资料部分提供的下载站点下载这些 Shiro JAR 文件。
下载了这些库后,只需将它们添加到 CLASSPATH。编写清单 2内所示的简单代码,它获得对当前用户的一个引用并报告说用户未经身份验证。(使用Subject
类来代表此用户。)
清单 2. ShiroTest Java 类
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
接口来将您自己获得凭证的方式包括进来。例如,可以扩展这个接口来提供您应用程序用来验证用户身份的一个关键文件的内容。
简单的身份验证
至此,这个简单的示例涵盖的内容包括:启动 ShiroSecurityManager
、获得当前用户并记录下用户未经身份验证。接下来的这个例子将会使用UsernamePasswordToken
和存储在 INI 文件内的一个用户记录来展示如何通过用户名和密码进行身份验证。
清单 3内所示的 auth.ini 文件现在包含了一个用户记录;这个记录包含了用户名和密码。您可以在这个记录内定义角色以及为应用程序提供授权。
清单 3. auth.ini 文件
[users]
bjangles = dance
现在,创建前一节中介绍的这个UsernamePasswordToken
对象,如清单 4所示。
清单 4. 使用 UsernamePasswordToken 类
// 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 进行身份验证
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 内的一个用户进行身份验证所需的代码。
清单 5. 使用 LDAP 进行身份验证
// 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所示。
清单 6. actived.ini 文件
[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 来将用户的密码更改为一个能放入测试代码以确保它工作的值。
运行一个 Grails web 应用程序
您可以使用两种基本技巧将 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所示的代码。
清单 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'("
#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 CommonsBeanUtils
来解析这个 INI 文件并设置对象上的值。
结束语
Shiro 是 Apache Incubator 内的一个框架,可让您向应用程序中添加身份验证和授权。它支持不同的身份验证存储方式,比如 LDAP