一 ,Authentication(身份验证)
1.shiro的身份认证
Authentication 是指身份验证的过程——即证明一个用户实际上是不是他们所说的他们是谁。对于一个用户证明自己 的身份来说,他们需要提供一些身份识别信息,以及某些你的系统能够理解和信任的身份证明。 这是通过提交用户的身份和凭证给 Shiro,以判断它们是否和应用程序预期的相匹配。
2.三个要素
subject
subject相当于 “ 用户 ”,就是当前与应用程序交互的用户
//获取subject
Subject subject = SecurityUtils.getSubject();
Principals
principals是subject的标识属性,可以理解为身份信息,可以是任何能够证明 Subject 的东西,如名,姓氏,用户名,社会保险号(相当于我们的身份证号码)等等,虽然 Shiro 可以代表任意数量的 Principals(身份),但 Shiro 期望应用程序有一个确切的‘主要 的’Principals(身份)——一个单一的值在应用程序内部唯一标识 Subject。这通常是一个用户名,电子邮件地址 或者在大多数应用中的全球唯一用户 ID
//获取principals
subject.getPrincipal();
Credentials
Credentials(凭证)通常是只被 Subject 知道的秘密值,它用来作为一种起支持作用的证据
subject认证过程
验证 Subjects 的过程中,可以有效地分解成三个不同的步骤:
- 收集 Subjects 提交的 Principals(身份)和 Credentials(凭证);
- 提交 Principals(身份)和 Credentials(凭证)进行身份验证;
- 如果提交成功,则允许访问,否则重新进行身份验证或者阻止访问
shiro提供非常丰富的运行时 AuthenticationException 层次结构,可以指出尝试失败的确切原因。你可以用一个 try/catch 块将 login 方法包围起来,然后捕捉任何你期望的异常并进行相应的反应
try {
currentUser.login(token);
} catch ( UnknownAccountException uae ) { … }
catch ( IncorrectCredentialsException ice ) { … }
catch (LockedAccountException lae ) { … }
catch (ExcessiveAttemptsException eae ) { … }
catch your own … }
catch ( AuthenticationException ae ) { //unexpected error? }
shiro官方推荐
虽然你的代码可以以特定的异常作出反应,并执行必要的逻 辑,最安全的做法是只显示通用的失败消息给终端用户,例如, “错误的用户名或密码。”。这样将确保具体的信息提供给黑 客可能试图攻击的媒介
详细分析下认证过程
1.收集 Subject 的 Principals(身份)和 Credentials(凭证)
先看一段源码
//Shiro的org.apache.shiro.authc.AuthenticationToken的接口,是Shiro代表提交的Principals(身份)和Credentials(凭 证)的身份验证系统所使用的基本接口
public interface AuthenticationToken extends Serializable {
Object getPrincipal();
Object getCredentials();
}
他有两个实现类
RememberMeAuthenticationToken和HostAuthenticationToken
而我们常用的UsernamePasswordToken则实现了这两个接口,通过构造方法封装了用户名和密码,记住我服务默认是关闭的,需要手动开启
public class UsernamePasswordToken implements HostAuthenticationToken, RememberMeAuthenticationToken {
private String username;
private char[] password;
private boolean rememberMe;
private String host;
public UsernamePasswordToken() {
this.rememberMe = false;
}
public UsernamePasswordToken(String username, char[] password) {
this(username, (char[])password, false, (String)null);
}
public UsernamePasswordToken(String username, String password) {
this(username, (char[])(password != null ? password.toCharArray() : null), false, (String)null);
}
Shiro不关心你是如何获取此信息的:也许获得的数据是由用户提交的一个 HTML表单, 或者是从 HTTP 头中捕获,或者它是从一个 Swing 或 Flex GUI 密码表单,或者通过命令行参数。从终端用户收 集信息的过程与 Shiro 的 Authentication Token 概念是不挂钩的。
2.提交 Subject 的 Principals(身份)和 Credentials(凭证)
在 Principals(身份)和 Credentials(凭证)被收集以及被实例化为 AuthenticationToken 实例后,我们需要提交这个 token 给 Shiro 来执行真正的身份验证尝试:
Subject currentUser = SecurityUtils.getSubject();
currentUser.login(token);
在捕获到当前执行的 Subject 后,我们获得一个单一的 login 方法调用,并将之前获得的 AuthenticationToken 实例传递给它。 通过调用 login 方法,有效地体现了身份验证尝试。
3.处理登录
如果 login 方法返回平静地,就是它——我们所做的一切!该 Subject 已通过验证。应用程序线程可以不受干 扰地继续下去,而且所有进一步对 SecurityUtils.getSubject()的调用将返回认证后的 Subject 实例,同时任何对 subject.isAuthenticated()的调用将返回 true。
Remembered vs. Authenticated(记住我对比认证)
shiro对记住我subject和认证subject做了精确的区分
关系 — 互斥关系
RememberedMe:一个记住我的subject不是匿名的,是有确定身份的,也就是说,
subject.getPrincipals()`返回是非空的
subject.isRemembered()返回 true
但是,这个身份是之前session中被认证的
Authenticated(已认证):一个已认证的 Subject 是指在当前 Session 中被成功地验证过了(也就是说,login 方法被调用并且没有抛出异常)。如果 subject.isAuthenticated()返回 true 则认为 Subject 已通过验证。
为何有这样的区别?
“身份验证”这个词有很强的证明的意思在里面。也就是说,有一个预期保证 Subject 已经证明他们是他们所 说的谁。 当用户只记得之前与应用的交互时,认证将不复存在:被记住的身份 ID 使系统明白这个用户可能是谁,但在 现实中没有办法绝对保证被记住的 Subject 代表期望的用户。一旦 Subject 通过验证,它们将不再仅仅被认为 是被记住的,由于它们的身份已经在当前 session 中被证实。 尽管应用程序的许多部分仍然能够执行基于被记住身份 ID 的用户特定逻辑,像自定义视图,但它绝不应该执 行高度敏感的操作,除非用户通过执行一个成功的认证尝试来合法地验证自己的身份。 例如,一个检查来判断一个 Subject 可以访问财务信息应该几乎总是取决于 isAuthenticated(),而不是 isRemembered(),以保证一个预期和核实的身份。
注销
进行身份验证的反面是释放所有已知的的识别状态。当 Subject 完成了与应用程序的交互后,你可以调用 subject.logout()来释放所有的识别信息
currentUser.logout(); //removes all identifying information an invalidates their session too.
当你调用 logout,任何现有的 Session 都将会失效,而且任何身份都将会失去关联(例如,在 Web 应用程序 中,RememberMe cookie 也将被删除)。 在 Subject 注销后,该 Subject 的实例被再次认为是匿名的,当然,除了 Web 应用程序,它还可以重新用于 login 如果需要的话。
Web Application Notice 由于在 Web 应用程序记住身份往往是依靠 Cookies,然而 Cookies 只能在 Response 被 committed 之前被删除,所以强烈建议在调用 subject.logout()后立即将终端用户重定向到一个新的视图或页面。 这样能够保证任何与安全相关的Cookies都能像预期的一样被删除。这是HTTP cookies的功能限制,而不是Shiro 的。
登录验证过程分析
1.应用程序代码调用 Subject.login 方法,传递创建好的包含终端用户的 Principals(身份)和 Credentials(凭 证)的 AuthenticationToken 实例
Subject.login(token);
2.通常是 DelegatingSubject(或子类)委托应用程序的 SecurityManager 通过调用 securityManager.login(token)开始真正的验证工作
Subject subject = this.securityManager.login(this, token);
3.SubjectManager 作为一个基本的“保护伞”的组成部分,接收 token 以及简单地委托给内部的 Authenticator 实例通过调用 authenticator.authenticate(token)。这通常是一个ModularRealmAuthenticator 实例, 支持在身份验证中协调一个或多个 Realm 实例。ModularRealmAuthenticator 本质上为 Apache Shiro 提供了 PAM-style 范式(其中在 PAM 术语中每个 Realm 都是一个’module’)。
public Subject login(Subject subject, AuthenticationToken token) throws AuthenticationException {
AuthenticationInfo info;
try {
info = this.authenticate(token);
} catch (AuthenticationException var7) {
AuthenticationException ae = var7;
......
4.如果应用程序中配置了一个以上的 Realm,ModularRealmAuthenticator 实例将利用配置好的 AuthenticationStrategy 来启动 Multi-Realm 认证尝试。在 Realms 被身份验证调用之前,期间和以后, AuthenticationStrategy 被调用使其能够对每个 Realm 的结果作出反应。我们马上就会涉及到 AuthenticationStrategies
`
protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException {
this.assertRealmsConfigured();
Collection<Realm> realms = this.getRealms();
return realms.size() == 1 ? this.doSingleRealmAuthentication((Realm)realms.iterator().next(), authenticationToken) : this.doMultiRealmAuthentication(realms, authenticationToken);
}
5.认证策略
AuthenticationStrategy 组件来确定这些认证尝试的成功或失败条件。
public interface AuthenticationStrategy {
//1. 在任何 Realm 被调用之前被询问;
AuthenticationInfo beforeAllAttempts(Collection<? extends Realm> var1, AuthenticationToken var2) throws AuthenticationException;
//2. 在一个单独的 Realm 的 getAuthenticationInfo 方法被调用之前立即被询问;
AuthenticationInfo beforeAttempt(Realm var1, AuthenticationToken var2, AuthenticationInfo var3) throws AuthenticationException;
//3. 在一个单独的 Realm 的 getAuthenticationInfo 方法被调用之后立即被询问;
AuthenticationInfo afterAttempt(Realm var1, AuthenticationToken var2, AuthenticationInfo var3, AuthenticationInfo var4, Throwable var5) throws AuthenticationException;
//4. 在所有的 Realm 被调用后询问
AuthenticationInfo afterAllAttempts(AuthenticationToken var1, AuthenticationInfo var2) throws AuthenticationException;
}
实现类
public abstract class AbstractAuthenticationStrategy implements AuthenticationStrategy
三个子类,就是三个认证策略
AtLeastOneSuccessfulStrategy
如果一个(或更多)Realm 验证成功,则整体的 尝试被认为是成功的。如果没有一个验证成功,则整体尝试失败。
FirstSuccessfulStrategy
只有第一个成功地验证的 Realm 返回的信息将被 使用。所有进一步的 Realm 将被忽略。如果没有 一个验证成功,则整体尝试失败。
AllSucessfulStrategy
为了整体的尝试成功,所有配置的 Realm 必须验 证成功。如果没有一个验证成功,则整体尝试失 败