shiro实战系列(五)之Authentication(身份验证)

 

 建议学习shiro读读官方文档,虽然不一定读的懂,但是建议要大致浏览,心中有个大概,这样对于学习还是有一定帮助

官网地址:https://shiro.apache.org/

 

 

Authentication 是指身份验证的过程——即证明一个用户实际上是不是他们所说的他们是谁。对于一个用户证明自己 的身份来说,他们需要提供一些身份识别信息,以及某些你的系统能够理解和信任的身份证明。  

 

这是通过提交用户的身份和凭证给 Shiro,以判断它们是否和应用程序预期的相匹配。

 

Principals(身份)

Principals是 Subject 的‘identifying attributes(标识属性)’。Principals(身份)可以是任何能够证明 Subject 的东西,如名,姓氏,用户名,社会保险号(相当于我们的身份证号码)等等。当然像姓氏这样的 用来标识 Subject 并不是很好,因此,最好的用来进行身份验证的 Principals(身份)是对应用程序来说应 该是独一无二的——通常是用户名或电子邮件地址。

 

Primary Principal(主要身份)

虽然 Shiro 可以代表任意数量的 Principals(身份),但 Shiro 期望应用程序有一个确切的‘主要 的’Principals(身份)——一个单一的值在应用程序内部唯一标识 Subject。这通常是一个用户名,电子邮件地址 或者在大多数应用中的全球唯一用户 ID。

 

Credentials(凭证)

通常是只被 Subject 知道的秘密值,它用来作为一种起支持作用的证据,此证据事实上 包含着所谓的身份证明。一些常见 credentials(凭证)的例子有密码,生物特征数据如指纹和视网膜扫描, 以及 X.509 证书。

 

principal/credential 配对最常见的例子是用户名和密码。用户名是所声称的身份,密码是匹配所声称的身份的 证明。如果提交的密码与应用程序期望的相匹配,应用程序可以很大程度上假设用户真的是他们说的他们是谁,因为其他人都应该不知道同样的密码。

 

Authenticating Subjects(验证 Subjects):

 

验证 Subjects 的过程中,可以有效地分解成三个不同的步骤:

1. 收集 Subjects 提交的 Principals(身份)和 Credentials(凭证);

2. 提交 Principals(身份)和 Credentials(凭证)进行身份验证;

3. 如果提交成功,则允许访问,否则重新进行身份验证或者阻止访问。

 

Step 1:收集 Subject 的 Principals(身份)和 Credentials(凭证)

在这种特殊情况下,我们使用 UsernamePasswordToken 来支持最常见的用户名/密码的身份验证方法。这是 Shiro的org.apache.shiro.authc.AuthenticationToken的接口,是Shiro代表提交的Principals(身份)和Credentials(凭证)的身份验证系统所使用的基本接口的一个实现。   这里需要重要注意的是 Shiro不关心你是如何获取此信息的:也许获得的数据是由用户提交的一个 HTML表单, 或者是从 HTTP 头中捕获,或者它是从一个 Swing 或 Flex GUI 密码表单,或者通过命令行参数。从终端用户收集信息的过程与 Shiro 的 Authentication Token 概念是不挂钩的。   你可以创建和实例化你喜欢的 Authentication Token 实例——它是与协议无关的。   这个例子也说明了我们希望 Shiro 为身份验证的尝试执行“记住我”的服务。这将确保在以后的日子,如果用 户返回到应用程序时,Shiro 能够记得用户的身份。我们将在后面的章节讨论 Remember Me 服务。

 

Step 2:提交 Subject 的 Principals(身份)和 Credentials(凭证)

 

在 Principals(身份)和 Credentials(凭证)被收集以及被实例化为 AuthenticationToken 实例后,我们需要提交这个 token 给 Shiro 来执行真正的身份验证尝试:

 

 

 

在捕获到当前执行的 Subject 后,我们获得一个单一的 login 方法调用,并将之前获得的 AuthenticationToken 实例传递给它。   通过调用 login 方法,有效地体现了身份验证尝试。

 

 

Step3:处理成功或失败 如果 login 方法返回平静地,就是它——我们所做的一切!该 Subject 已通过验证。应用程序线程可以不受干 扰地继续下去,而且所有进一步对 SecurityUtils.getSubject()的调用将返回认证后的 Subject 实例,同时任何对 subject.isAuthenticated()的调用将返回 true。   但是如果登录尝试失败会发生什么呢?例如,如果终端用户提供了不正确的密码,或这访问系统的次数太多, 亦或是他们的帐户被锁定?   Shiro 拥有丰富的运行时 AuthenticationException 层次结构,可以指出尝试失败的确切原因。你可以用一个 try/catch 块将 login 方法包围起来,然后捕捉任何你期望的异常并进行相应的反应。例如:

 

 

如果现有的异常类不符合您的要求,可以自定义 AuthenticationExceptions 来代表具体的异常情况。

Login Failure Tip 

虽然你的代码可以以特定的异常作出反应,并执行必要的逻辑,最安全的做法是只显示通用的失败消息给终端用户,例如, “错误的用户名或密码。”。这样将确保具体的信息提供给黑 客可能试图攻击的媒介。

 

Remembered vs. Authenticated(记住我对比认证)

如上面的例子所示,Shiro 支持除了普通的登录过程的所有“记住我”的概念。此时值得指出的是,Shiro 对记 住我的 Subject 和通过验证的 Subject 作了精确的区分:  Remembered(记住我):

一个记住我的 Subject 不是匿名的,而且有一个已知的身份 ID(也就是 subject.getPrincipals()是非空的)。但是这个被记住的身份 ID 是在之前的 session 中被认证的。如果 subject.isRemembered()返回 true,则 Subject 被认为是被记住的。  Authenticated(已认证):

一个已认证的 Subject 是指在当前 Session 中被成功地验证过了(也就是说,login 方法被调用并且没有抛出异常)。如果 subject.isAuthenticated()返回 true 则认为 Subject 已通过验证。

 

Mutually Exclusive(互斥)   Remembered 和 Authenticated 是互斥的——若其中一个为真则另一个为假,反之亦然。

 

为什么会有这样的区别?

“身份验证”这个词有很强的证明的意思在里面。也就是说,有一个预期保证 Subject 已经证明他们是他们所说的谁。 当用户只记得之前与应用的交互时,认证将不复存在:被记住的身份 ID 使系统明白这个用户可能是谁,但在现实中没有办法绝对保证被记住的 Subject 代表期望的用户。一旦 Subject 通过验证,它们将不再仅仅被认为 是被记住的,由于它们的身份已经在当前 session 中被证实。 尽管应用程序的许多部分仍然能够执行基于被记住身份 ID 的用户特定逻辑,像自定义视图,但它绝不应该执 行高度敏感的操作,除非用户通过执行一个成功的认证尝试来合法地验证自己的身份。 例如,一个检查来判断一个 Subject 可以访问财务信息应该几乎总是取决于 isAuthenticated(),而不是 isRemembered(),以保证一个预期和核实的身份。  

 

一个说明的例子 下面是一个相当普遍的情况:

有助于说明 Remembered 和 Authenticated 之间区别的重要性。 比方说,你正在访问 Amazon.com。你已经登录成功并添加了几本书到你的购物车。但你心烦意乱地跑出去开会,却忘了注销。会议结束后,已经到了回家的时候,于是你离开了办公室。   第二天你工作的时候,你意识到你没有完成购买,于是你返回到 amazon.com。这一次,Amazon“记得”你是 谁,给出了你的欢迎页面,并仍然为你提供一些个性化的建议书籍。对 Amazon 而言,subject.isRemembered() 将返回 true。   但是,当你尝试访问你的帐户来更新你的信用卡信息为你书付账时会发生什么呢?尽管 Amazon“记住”你 (isRemembered() = = true),它不能保证你就是实际上的你(例如,也许一个同事正在使用你的计算机)。

所以,在你能够执行像更新信用卡信息等敏感行为之前,Amazon 将强制让你登录,使它们能够保证你的身份。 在登录后,你的身份已经被核实,同时对 Amazon 而言,isAuthenticated()现在返回是 true。   这种情况在许多类型的应用中发生的是如此的频繁,所以这些功能被内置在 Shiro 中,这样你就能利用它来为 你的应用服务了。现在,无论你使用的是 isRemembered()还是 isAuthenticated()来定制你的视图和工作流都由 你来决定,但 Shiro 将维持这一基本情况以防你需要它。  

 

注销:

进行身份验证的反面是释放所有已知的的识别状态。当 Subject 完成了与应用程序的交互后,你可以调用 subject.logout()来释放所有的识别信息: 

 

当你调用 logout,任何现有的 Session 都将会失效,而且任何身份都将会失去关联(例如,在 Web 应用程序 中,RememberMe cookie 也将被删除)。 在 Subject 注销后,该 Subject 的实例被再次认为是匿名的,当然,除了 Web 应用程序,它还可以重新用于 login 如果需要的话。

 

Web Application Notice  由于在 Web 应用程序记住身份往往是依靠 Cookies,然而 Cookies 只能在 Response 被 committed 之前被删除,所以强烈建议在调用 subject.logout()后立即将终端用户重定向到一个新的视图或页面。 这样能够保证任何与安全相关的Cookies都能像预期的一样被删除。这是HTTP cookies的功能限制,而不是Shiro 的。   

 

验证顺序

到现在为止,我们只了解了如何从应用程序代码中验证一个 Subject。现在我们将涉及到当一个认证尝试出现 时 Shiro 内部会发什么。 我们采用了 Architecture 那一章的体系结构图,并只留下与 Authentication 有关的组件突出显示。每个数字代 表认证尝试中的一个步骤:

 

 

Step 1:应用程序代码调用 Subject.login 方法,传递创建好的包含终端用户的 Principals(身份)和 Credentials(凭 证)的 AuthenticationToken 实例。

 

Step 2:Subject 实例,通常是 DelegatingSubject(或子类)委托应用程序的 SecurityManager 通过调用 securityManager.login(token)开始真正的验证工作。

 

Step3:SubjectManager 作为一个基本的“保护伞”的组成部分,接收 token 以及简单地委托给内部的 Authenticator 实例通过调用 authenticator.authenticate(token)。这通常是一个 ModularRealmAuthenticator 实例, 支持在身份验证中协调一个或多个 Realm 实例。ModularRealmAuthenticator 本质上为 Apache Shiro 提供了 PAM-style 范式(其中在 PAM 术语中每个 Realm 都是一个'module')。

 

Step 4:如果应用程序中配置了一个以上的 Realm,ModularRealmAuthenticator 实例将利用配置好的 AuthenticationStrategy 来启动 Multi-Realm 认证尝试。在 Realms 被身份验证调用之前,期间和以后, AuthenticationStrategy 被调用使其能够对每个 Realm 的结果作出反应。我们马上就会涉及到 AuthenticationStrategies。

 

Single-Realm Application  如果只有一个单一的 Realm 被配置,它将被直接调用——没有必要为一个单一 Realm 的应用使用 AuthenticationStrategy。

 

Step 5:每个配置的 Realm 用来帮助看它是否支持提交的 AuthenticationToken。如果支持,那么支持 Realm 的 getAuthenticationInfo 方法将会伴随着提交的 token 被调用。getAuthenticationInfo 方法有效地代表一个特定 Realm 的单一的身份验证尝试。我们将在不久涉及到 Realm 验证行为。

 

Authenticator(认证器) 如前所述,Shiro SecurityManager 的实现默认使用一个 ModularRealmAuthenticator 实例。 ModularRealmAuthenticator 同样支持单一的 Realm 以及那些多个 Realm 的应用。 在一个单一的 Realm 应用中,ModularRealmAuthenticator 将直接调用这个单一的 Realm。如果有两个或两个 以上的 Realm 配置,它将使用 AuthenticationStrategy 实例来调整这些尝试如何出现。下面我们将介绍 AuthenticationStrategies。 如果你想配置 SecurityManager 通过一个自定义的 Authenticator 实现,你可以在 shiro.ini 中做,例如:

 

尽管在实践中,ModularRealmAuthenticator 很可能是适用于大多数需要的。

 

AuthenticationStrategy 当一个应用程序配置了两个或两个以上的 Realm 时,ModularRealmAuthenticator 依靠内部的 AuthenticationStrategy 组件来确定这些认证尝试的成功或失败条件。

例如,如果只有一个 Realm 验证一个 AuthenticationToken 成功,但所有其他的都失败,这被认为是成功的身份验证尝试吗?或者必须所有的 Realm 验证成功才被认为样子成功吗?或者,如果一个 Realm 验证成功,是否有必要进一步征询其他 Realm?AuthenticationStrategy 基于程序需要作出合适的决定。 AuthenticationStrategy 是一个无状态的组件,它在身份验证尝试中被询问 4 次(这 4 次交互所需的任何必要的 状态将被作为方法参数):

1. 在任何 Realm 被调用之前被询问;

2. 在一个单独的 Realm 的 getAuthenticationInfo 方法被调用之前立即被询问;

3. 在一个单独的 Realm 的 getAuthenticationInfo 方法被调用之后立即被询问;

4. 在所有的 Realm 被调用后询问。

 

另外,AuthenticationStrategy 负责从每一个成功的 Realm 汇总结果并将它们“捆绑”到一个单一的 AuthenticationInfo 再现。这最后汇总的 AuthenticationInfo 实例就是从 Authenticator 实例返回的值以及 Shiro 所用来代表 Subject 的最终身份 ID 的值(即 Principals(身份))。

 

Subject Identity 'View' 如果在你的应用程序中使用多个Realm从多个数据源获取账户资料,AuthenticationStrategy是最终为最后的“合 并”能够被应用程序理解的 Subject 的身份的视图的负责人

 

 

Shiro 有 3 个具体的 AuthenticationStrategy 实现:

 

 

ModularRealmAuthenticator 默认的是 AtLeastOneSuccessfulStrategy 实现,因为这是最常所需的方案。然而,如果你愿意的话,你可以配置一个不同的方案:

 

Realm 的验证顺序

 需要指出非常重要的一点是,ModularRealmAuthenticator 将与 Realm 实例以迭代的顺序进行交互。 在SecurityManager中已经配置好了ModularRealmAuthenticator对Realm实例的访问。当执行一个认证尝试时, 它将会遍历该集合,并对每一个支持提交 AuthenticationToken 的 Realm 调用 Realm 的 getAuthenticationInfo 方法。

 

Implicit ordering(隐式排列)

 

当使用 Shiro 的 INI 配置文件格式时,你应该配置 Realm 处理 AuthenticationToken 的顺序,你想要的顺序。例 如,在 shiro.ini 中,Realm 将会以它们在 INI 文件中定义好的顺序被请求到。也就是说,对于下面的 shiro.ini 示例:

 

Explicit Realm Inclusion   当你显式地配置 securityManager.realms 的属性是,只有已引用的 Realm 将会在 SecurityManager 中被配置。这意味着你能够在 INI 文件中定义 5 个 realm,但是实际上只能使用 3 个如果只有 这 3 个被引用到 realm 的属性中的话.这是和隐式 realm 顺序不同的,所有可用的隐式的 realm 都将被使用。

 

Realm 验证 本章涵盖了 Shiro 的主要工作流程,解释了一个认证尝试是如何产生的。内部工作流,指在验证过程中一个单 一的 realm 被访问时发生的事情(也就是'Step 5'之上),已经包含在 Realm的 Realm Authentication 部分

 

posted @ 2018-06-08 20:18  挑战者V  阅读(1092)  评论(0编辑  收藏  举报