Shiro简介及使用
Apache Shiro™ is a powerful and easy-to-use Java security framework that performs authentication, authorization, cryptography, and session management. With Shiro’s easy-to-understand API, you can quickly and easily secure any application – from the smallest mobile applications to the largest web and enterprise applications.
Apache Shiro™是一个功能强大且易于使用的Java安全框架,可执行身份验证,授权,加密和会话管理。 借助Shiro易于理解的API,您可以快速轻松地保护任何应用程序 - 从最小的移动应用程序到最大的Web和企业应用程序。
#一、Apache Shiro简介
Apache Shiro的首要目标是易于使用和理解。安全有时可能非常复杂,甚至是痛苦的,但并非必须如此。框架应尽可能掩盖复杂性,并提供简洁直观的API,以简化开发人员确保其应用程序安全的工作。
##1、Apache Shiro可以做的一些事情:
- 验证用户以验证其身份
- 为用户执行访问控制,例如:
- 确定是否为用户分配了某个安全角色
- 确定是否允许用户执行某些操作
- 在任何环境中使用Session API,即使没有Web容器或EJB容器也是如此。
- 在身份验证,访问控制或会话生命周期内对事件做出反应。
- 聚合用户安全数据的1个或多个数据源,并将其全部显示为单个复合用户“视图”。
- 启用单点登录(SSO)功能
- 无需登录即可为用户关联启用“记住我”服务
Shiro尝试为所有应用程序环境实现这些功能 - 从最简单的命令行应用程序到最大的企业应用程序,而不会强制依赖其他第三方框架,容器或应用程序服务器。当然,该项目旨在尽可能地融入这些环境,但它可以在任何环境中开箱即用。
##2、Shiro的功能特性
Apache Shiro是一个具有许多功能的综合应用程序安全框架。下图显示了Shiro主要关注点:
Shiro针对应用程序安全的四大基础-Authentication, Authorization, Session Management, and Cryptography:
- Authentication: 身份认证/登录,验证用户是不是拥有相应的身份;
- Authorization: 授权,即权限验证,验证某个已认证的用户是否拥有某个权限;
- Session Management: 会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话中;
- Cryptography: 加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储;
在不同的应用程序环境中还有其他功能可以支持和强化这些问题,尤其是:
- Web Support:Shiro的Web支持API可帮助轻松保护Web应用程序。
- Caching:缓存,确保安全操作保持快速高效。
- Concurrency:Apache Shiro支持具有并发功能的多线程应用程序。
- Testing:存在测试支持以帮助您编写单元和集成测试,并确保您的代码按预期受到保护。
- “Run As”:允许用户假定其他用户的身份(如果允许)的功能,有时在管理方案中很有用。
- “Remember Me”:记住用户在会话中的身份,这样他们只需要在强制要求时登录。
3、Shiro架构
Apache Shiro的设计目标是通过直观和易用来简化应用程序安全性。Shiro的核心设计模拟了大多数人对应用程序安全性的看法 - 在某人(或某事)与应用程序交互的环境中。
shiro架构有三个主要概念:Subject,SecurityManager和Realms。下图是这些组建如何交互的高级概述、之后介绍每个概念:
Subject: 主体,
Subject
代表了与当前应用程序进行安全交互的“用户”。虽然“用户”这个词通常意味着一个具体的人,但是它可以是一个人,但它也可以代表第三方服务,守护进程帐户,cron作业或任何类似的东西 - 基本上任何与软件交互的东西。Subject
实例都绑定到SecurityManager
,当您与Subject
‘进行交互时,这些交互会委托给Subject
绑定的SecurityManager
。SecurityManager: 安全管理器,
SecurityManager
是shiro架构的核心,SecurityManager
管理着所有的Subject
,所有与安全有关的操作都会与SecurityManager
交互.Realm: 域,充当Shiro与应用程序安全数据之间的“桥梁”或“连接器”。当实际与安全相关的数据(如用户帐户)进行交互以执行身份验证(登录)和授权(访问控制)时,Shiro会从为应用程序配置的一个或多个领域中查找许多这些内容。
从这个意义上讲,Realm本质上是一个特殊的安全性的DAO:它封装了数据源的连接细节,并根据需要使相关数据可用于Shiro。配置Shiro时,必须至少指定一个Realm用于身份验证和/或授权。
Shiro提供了开箱即用的领域,可以连接到许多安全数据源(也称为目录),如LDAP,关系数据库(JDBC),文本配置源(如INI和属性文件等)。如果默认域不符合您的需要,您可以插入自己的Realm实现来表示自定义数据源
注意: 从上述可以看出,shiro不提供维护用户和权限的功能,而是通过realm让用户自己来实现。
shiro详细架构
下图显示了Shiro的核心架构概念,后面是每个概述的简短摘要:
- Subject(
org.apache.shiro.subject.Subject
)
当前与软件交互的实体(用户,第三方服务等)的“用户”。 - SecurityManager(org.apache.shiro.mgt.SecurityManager)
如上所述,这SecurityManager
是Shiro架构的核心,协调各组件稳定运行,所有的安全相关的操作都是通过SecurityManager
进行控制。 - Authenticator(org.apache.shiro.authc.Authenticator)
认证器,负责执行验证用户认证的组件。当用户尝试登录时,由Authenticator
执行用户认证的逻辑。该Authenticator
知道如何与一个或多个存储用户/帐户信息的Realms
协调工作。从这些Realms
获取的数据用于验证用户的身份,以保证用户确实是他们所说的人。- Authentication Strategy(org.apache.shiro.authc.pam.AuthenticationStrategy)
认证策略,如果多于一个Realm
被配置时,AuthenticationStrategy
将与realm
协调,以确定在其下的认证尝试成功或失败(例如,如果一个realm
认证成功但是其他认证失败,那么这次认证是成功的吗?必须所有realm
都要认证成功?还是只需要第一个?)。
- Authentication Strategy(org.apache.shiro.authc.pam.AuthenticationStrategy)
- Authorizer(org.apache.shiro.authz.Authorizer)
访问控制器,Authorizer
确定“用户”在该应用程序的访问控制权限。决定用户是否有权限进行某项操作,控制用户能访问应用程序的哪些功能。 - SessionManager(org.apache.shiro.session.mgt.SessionManager)
SessionManager
知道如何创建和管理用户Session
生命周期,提供在所有环境中的用户强大的会话体验。这是安全框架领域的一项独特功能 - 即使没有可用的Web / Servlet或EJB容器,Shiro也能够在任何环境中本地管理用户会话。默认情况下,Shiro将使用现有的会话机制(例如Servlet Container),但如果没有,例如在独立应用程序或非Web环境中,它将使用其内置的企业会话管理提供相同的编程经验。的SessionDAO
存在允许任何数据源被用来坚持的会议。- SessionDAO(org.apache.shiro.session.mgt.eis.SessionDAO)
SessionDAO
执行Session
代表的持久性(CRUD)操作SessionManager
。这允许将任何数据存储插入会话管理基础结构。
- SessionDAO(org.apache.shiro.session.mgt.eis.SessionDAO)
- CacheManager(org.apache.shiro.cache.CacheManager)
CacheManager
创建和管理Cache
其他四郎组件使用实例的生命周期。由于Shiro可以访问许多后端数据源以进行身份验证,授权和会话管理,因此缓存一直是框架中的一流架构功能,可在使用这些数据源时提高性能。任何现代开源和/或企业缓存产品都可以插入Shiro,以提供快速有效的用户体验。 - Cryptography(org.apache.shiro.crypto)
密码模块,Shiro提供了一些常见的加密组件用于如密码加密/解密的。该软件包中的所有类都经过精心设计,易于使用且易于理解。 - Realms(org.apache.shiro.realm.Realm)
如上所述,Realms充当Shiro和应用程序安全数据之间的“桥接”或“连接器”。当实际与安全相关的数据(如用户帐户)进行交互以执行身份验证(登录)和授权(访问控制)时,Shiro会从为应用程序配置的一个或多个领域中查找许多这些内容。您可以根据Realms
需要配置任意数量(通常每个数据源一个),Shiro将根据需要进行身份验证和授权协调。
二、Shiro核心功能
上面主要介绍了shiro的功能特性及架构,下面介绍shiro的核心功能的使用。
1、Shiro身份验证
身份验证过程就是证明用户实际上是他们所说的人,为了让用户证明自己的身份,他们需要提供一些能够让系统识别和信任的身份证明。
在shiro中,用户需要提供principals (身份)和credentials(证明)给shiro,从而应用能验证用户身份:
principals:身份,即主体的标识属性,可以是任何东西,如用户名、邮箱等,唯一即可。一个主体可以有多个principals,但只有一个Primary principals,一般是用户名/密码/手机号。
credentials:证明/凭证,即只有主体知道的安全值,如密码/数字证书等。
最常见的principals和credentials组合就是用户名/密码了。接下来先进行一个基本的身份认证。
2、身份验证步骤
Subject身份验证的过程主要分为以下三个步骤:
- 1、收集用户身份/凭证,即如用户名/密码。
- 2、提交用户身份/凭证进行身份验证。
- 3、如果提交成功,则允许访问,否则重试身份验证或阻止访问。
接一下来结合代码对上述的三个步骤进行说明。
步骤一:收集用户身份/凭证,即如用户名/密码
//Example using most common scenario of username/password pair:
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
//"Remember Me" built-in:
token.setRememberMe(true);
在当前的例子中,我们使用了UsernamePasswordToken,支持最常见的用户名/密码身份验证方法。这是Shiro的org.apache.shiro.authc.AuthenticationToken接口的实现,该接口是Shiro的身份验证系统用来表示提交的身份信息和凭据的基本接口。
这里需要注意的是,Shiro不关心如何获取这些信息,你可以从http表单中获取,也可以从http请求头中获取,或者通过命令行参数获取。应用程序获取用户身份信息和凭证的过程与与Shiro的AuthenticationToken概念完全分离。
这个例子还表明我们已经表示希望Shiro为身份验证尝试执行“记住我”服务。这可确保Shiro在以后返回应用程序时记住用户身份。
步骤二:提交用户身份和凭证
在收集了用户身份和凭据并将其表示为AuthenticationToken实例后,我们需要将token提交给Shiro以执行实际的身份验证尝试:
Subject currentUser = SecurityUtils.getSubject();
currentUser.login(token);
获取当前正在执行的Subject后,我们进行一次登录调用,传入我们之前创建的AuthenticationToken实例。
步骤三:处理验证结果(成功或失败)
如果登录方法正常地返回,此次验证完成,用户已经过身份验证。 应用程序线程可以继续不间断,所有对SecurityUtils.getSubject()的进一步调用将返回经过身份验证的Subject实例以及对subject的任何调用。 isAuthenticated()
将返回true。
但是如果登录尝试失败会发生什么? 例如,如果最终用户提供了错误的密码,或者访问了系统太多次并且他们的帐户被锁定了怎么办?
Shiro具有丰富的运行时AuthenticationException层次结构,可以准确指示尝试失败的原因。 您可以在try / catch块中包装登录并捕获您希望的任何异常并相应地对它们做出反应。 例如:
try {
currentUser.login(token);
} catch ( UnknownAccountException uae ) { ...//用户名错误
} catch ( IncorrectCredentialsException ice ) { ...//密码错误
} catch ( LockedAccountException lae ) { ...//账号锁定
} catch ( ExcessiveAttemptsException eae ) { ...//超过最大登录失败尝试次数
} ... catch your own ...
} catch ( AuthenticationException ae ) {
//unexpected error?
}
如果其中一个现有异常类不满足您的需求,则可以创建自定义AuthenticationExceptions以表示特定的异常。
Remembered vs. Authenticated
如上例所示,除了正常的登录过程外,Shiro还支持“记住我”的概念。 值得指出的是,Shiro在remembered Subject和实际authenticated Subject之间做了非常精确的区分:
Remembered:remembered Subject不是匿名的并且具有已知的身份(即subject.getPrincipals()非空)。 但是,在先前的会话期间,先前的身份验证会记住此身份。 如果subject.isRemembered()返回true,则认为subject被记住。
Authenticated:authenticated Subject是在subject的当前会话期间已成功通过身份验证的主题(即,在不抛出异常的情况下调用登录方法)。 如果subject.isAuthenticated()返回true,则认为subject已经过身份验证。
三、身份验证流程
到目前为止,我们只研究了如何从应用程序代码中验证subject。 现在我们将介绍在发生身份验证时Shiro内部发生的事情。
我们从架构章节中获取了我们之前的架构图,并且只保留了与身份验证相关的组件。 每个数字代表身份验证尝试期间的一个步骤:
步骤1:应用程序代码调用Subject.login方法,传入构造的AuthenticationToken实例,表示最终用户的身份和凭证。
步骤2:Subject实例,通常是DelegatingSubject(或子类),通过调用securityManager.login(token)委托应用程序的SecurityManager,其中实际的身份验证工作开始。
步骤3:SecurityManager接收tokent,并通过调用authenticator.authenticate(token)简单地委托给其内部Authenticatorinstance。一般是ModularRealmAuthenticator实例,它支持在身份验证期间协调一个或多个Realm实例。 ModularRealmAuthenticator实质上为Apache Shiro提供了PAM风格的范例(其中每个realm都是PAM术语中的“模块”)。
步骤4:如果为应用程序配置了多个Realm,则ModularRealmAuthenticator实例将使用其配置的AuthenticationStrategy启动多Realm身份验证尝试。在调用Realms进行身份验证之前,期间和之后,将调用AuthenticationStrategy以允许它对每个Realm的结果做出反应。我们很快就会介绍AuthenticationStrategies。
- 注意:如果只配置了一个Realm,则直接调用它 ,在单Realm应用程序中不需要AuthenticationStrategy。
步骤5:查询每个配置的Realm以查看它是否支持提交的AuthenticationToken。 如果是这样,将使用提交的token调用支持的Realm的getAuthenticationInfo方法。 getAuthenticationInfo方法有效地表示该特定Realm的单个身份验证尝试。 我们将很快介绍Realm身份验证行为。
如上所述,Shiro SecurityManager默认使用ModularRealmAuthenticator实例。 ModularRealmAuthenticator同时支持具有单个Realm的应用程序以及具有多个Realm的应用程序。
在单领域应用程序中,ModularRealmAuthenticator将直接调用单个领域。如果配置了两个或更多领域,它将使用AuthenticationStrategy实例来协调尝试的发生方式。我们将在下面介绍AuthenticationStrategies。
ModularRealmAuthenticator能适合大多数应用场景,如果你希望使用自定义Authenticator,可以将自定义的Authenticator配置到SecurityManager。
当为应用程序配置两个或多个领域时,ModularRealmAuthenticator依赖于内部AuthenticationStrategy组件来确定身份验证尝试成功或失败的条件。
例如,如果只有一个Realm成功验证了AuthenticationToken,但其他所有验证失败,那么验证尝试是否成功?或者所有领域是否必须成功验证才能被认为是成功的整体尝试?或者,如果领域成功验证,是否有必要进一步咨询其他领域? AuthenticationStrategy根据应用程序的需要做出适当的决策。
AuthenticationStrategy是一个无状态组件,在身份验证尝试期间会被查询4次(这4次交互所需的任何必要状态将作为方法参数提供):
- 1、在调用任何Realm之前
- 2、在调用单个Realm的getAuthenticationInfo方法之前
- 3、在调用单个Realm的getAuthenticationInfo方法之后立即执行
- 4、在所有Realm被调用之后
AuthenticationStrategy还负责聚合每个成功Realm的结果,并将它们“绑定”到一个AuthenticationInfo表示中。最终聚合AuthenticationInfo实例是Authenticator实例返回的实例,也是Shiro用来表示Subject的最终身份(又名Principals)的实例。
Shiro有3个具体的AuthenticationStrategy实现:
AuthenticationStrategy class |
说明 |
---|---|
AtLeastOneSuccessfulStrategy |
如果一个(或多个)领域成功进行身份验证,则认为整体尝试成功。 如果没有成功进行身份验证,则尝试失败。 |
FirstSuccessfulStrategy |
仅使用从第一个成功通过身份验证的Realm返回的信息。 所有进一步的领域都将被忽略。 如果没有成功验证,则尝试失败。 |
AllSuccessfulStrategy |
所有已配置的域必须成功进行身份验证才能将整体尝试视为成功。 如果任何人未成功验证,则尝试失败。 |
ModularRealmAuthenticator默认为AtLeastOneSuccessfulStrategy实现,因为这是最常用的策略。
*注:如果您想自己创建自己的AuthenticationStrategy实现,可以使用org.apache.shiro.authc.pam.AbstractAuthenticationStrategy作为起点。 AbstractAuthenticationStrategy类自动实现将每个Realm的结果合并到单个AuthenticationInfo实例中的“捆绑”/聚合行为。
Realm Authentication Order:认证顺序
ModularRealmAuthenticator以迭代顺序与Realm实例交互。ModularRealmAuthenticator可以访问SecurityManager上配置的Realm实例。 执行身份验证尝试时,它将遍历该集合,并且对于支持提交的AuthenticationToken的每个Realm,调用Realm的getAuthenticationInfo方法。
ModularRealmAuthenticator根据SecurityManager中Realm配置按序调用的。