日期:2014-05-16  浏览次数:20985 次

Apache Shiro 验证
Apache Shiro验证(Authentication)
 
验证(Authentication):身份验证的过程--也就是证明一个用户的真实身份。为了证明用户身份,需要提供系统理解和相信的身份信息和证据。
需要通过向shiro提供用户的身份(Principals)和证明(credentials)来判定是否和系统所要求的匹配。

身份(Principals)是Subject的“身份属性”,可以是任何与Subject相关的标识,比如说名称(给定名称)、名字(姓或者昵称)、用户名、安全号码等等,当然像昵称这样的内容不能很好的对Subject进行独特标识,所以最好的身份信息(Principals)是使用在程序中唯一的标识--典型的使用用户名或邮件地址。

最主要的身份
虽然shiro可以使用任何数量的身份,Shiro还是希望一个程序精确地使用一个主要的身份--一个仅有的唯一标识Subject值。在多数程序中经常会是一个用户名、邮件地址或者全局唯一的用户ID。

证明(Credentials)通常是只有Subject知道的机密内容,用来证明他们真正拥有所需的身份,一些简单的证书例子如密码、指纹、眼底扫描和X.509证书等。

最常见的身份/证明是用户名和密码,用户名是所需的身份说明,密码是证明身份的证据。如果一个提交的密码和系统要求的一致,程序就认为该用户身份正确,因为其他人不应该知道同样的密码。

Authenticating对象
Subject验证的过程可以有效地划分分以下三个步骤:
1.收集Subject提交的身份和证明;
2.向Authenticating提交身份和证明;
3.如果提交的内容正确,允许访问,否则重新尝试验证或阻止访问。

下面的代码示范了Shiro API如何实现这些步骤:
第一步:收集用户身份和证明
//Example using mostcommon scenario of username/password pair:
UsernamePasswordTokentoken = new UsernamePasswordToken(username, password);

//”Remember Me” built-in:
token.setRememberMe(true);

在这里我们使用UsernamePasswordToken,支持所有常用的用户名/密码验证途径,这是一个org.apache.shiro.authc.AuthenticationToken接口的实现,这个接口被shiro认证系统用来提交身份和证明。

注意shiro并不关心你如何获取这些住处:也许是用户从一个HTML表单中提交的,或者可能从一个HTTP请求字串中解析的,也可能来自于Swing或者Flex GUI的密码表单,或者通过命令行参数得到。从程序终端用户获取信息的过程与shiro的AuthenticationToken完全无关。
你可以随自己喜欢构造和引用AuthenticationToken--它是协议不可知论者。

这个例子同样显示我们希望shiro在尝试验证时执行“Remember Me”服务,这确保shiro在用户今后返回系统时能记住他们的身份,我们会在以后的章节讨论“Remember Me”服务。

第二步:提交身份和证明
当身份和证明住处被收集并实例化为一个认证令牌后,我们需要向shiro提交令牌以执行真正的验证尝试:
Subject currentUser =SecurityUtils.getSubject();

currentUser.login(token);

在获取当前执行的Subject后,我们执行一个单独的login命令,将之前创建的认证令牌实例传给它。
使用login方法将有效地执行身份验证。

第三步:处理成功或失败
当login函数没有返回信息时表明验证通过了。程序可以继续运行,此时执行SecurityUtils.getSubject()将返回验证后的Subject实例,subject.isAuthenticated()将返回true。

但是如果login失败了呢?例如,用户提供了一个错误的密码或者因访问系统次数过多而被锁定将会怎样呢?

shiro拥有丰富的运行期异常(AuthenticationException)可以精确标明为何验证失败,你可以将login放入到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?
}

//No problems,continue on as expected...

如果原有的异常不能满足你的需求,可以创建自定义的AuthenticationExceptions来表示特定的失败场景。

登录失败小贴士
虽然你的代码可以对指定的异常做出处理并执行某些所需的逻辑,但有经验的安全做法是仅向终端用户输出一般的失败信息,例如“错误的用户名和密码”。这确保不向尝试攻击你的黑客提供有用的信息。

已记住(Remembered) vs 已验证(Authenticated)
如上例所示,shiro支持在登录过程中执行"remember me",在此值得指出,一个已记住的Subject(remembered Subject)和一个正常通过认证的Subject(authenticated Subject)在shiro是完全不同的。

记住的(Remembered):一个被记住的Subject没有已知身份(也就是说subject.getPrincipals()返回空),但是它的身份被先前的认证过程记住,并存于先前session中,一个被认为记住的对象在执行subject.isRemembered()返回真。

已验证(Authenticated):一个被验证的Subject是成功验证后(如登录成功)并存于当前session中,一个被认为验证过的对象调用subject.isAuthenticated()将返回真。

互斥的
已记住(Remembered)和已验证(Authenticated)是互斥的--一个标识值为真另一个就为假,反过来也一样。

为什么区分?
单词验证(authentication)有明显的证明含义,也就是说,需要担保Subject已经被证明他们是谁。
当一个用户仅仅在上一次与程序交互时被记住,证明的状态已经不存在了:被记住的身份只是给系统一个信息这个用户可能是谁,但不确定,没有办法担保这个被记住的Subject是所要求的用户,一旦这个subject被验证通过,他们将不再被认为是记住的因为他们的身份已经被验证并存于当前的session中。
所以尽管程序大部分情况下仍可以针对记住的身份执行用户特定的逻辑,比如说自定义的视图,但不要执行敏感的操作直到用户成功执行身份验证使其身份得到确定。

例如,检查一个Subject是否可以访问金融信息应该取决于是否被验证(isAuthenticated())而不是被记住(isRemembered()),要确保该Subject是所需的和通过身份验证的。

一个例子说明
下面是一个非常常见的场景帮助说明被记住和被验证之间差别为何重要。
假设你使用卓越网,你已经成功登录并且在购物蓝中添加了一些书籍,但你由于临时要参加一个会议,匆忙中你忘记退出登录,当会议结束,回家的时间到了,于是你离开了办公室。
第二天当你回到工作,你意识到你没有完成你的购买动作,于是你回到卓越网,这时,卓越网“记得”你是认证,通过你的名字向你打招呼,仍旧给你提供个性化的图书推荐,对于卓越,subject.isRemembered()将返回真