认证是身份验证的过程,也就是试图验证一个用户的有效性。为此,用户本身就需要提供系统可识别和可信任的身份标识。

这篇指南的目标在于引导如何在java中使用Shiro的认证机制。如果现在还没做好准备工作,那么可以先去了解“​​10分钟教会你Apache Shiro​​”来帮助你理解Shiro是如何工作的。

须知术语

Subject:Subject是应用程序中用户在安全领域特定用户的缩影。它可以是真实的用户、第三方进程、连接到应用的server、甚至是corn作业。换句话说,Subject是连接到应用程序的任何东西。

Principals:一个Subject定义的属性。比如:first name, last name, social security number, username。

Credentials:用来验证身份的数据。密码、Biometric data、x509 证书等。

Realms:安全相关的Dao、数据访问对象、与后台安全数据源交互的组件。比如在LDAP中存储用户名与密码信息,那么就需要一个LDAP realm与LDAP交互。

如何在Java中使用Shiro进行身份验证

和其他安全框架一样,Shiro中Java身份认证过程也可以归结为三个步骤。

步骤

  1. 获取subject的权限(principals)与证书(credentials)。
  2. 提交这些权限与证书到身份认证系统。
  3. 允许访问,重新认证,或者中断访问。

下面是在Shiro中如何实现上述步骤的代码。

步骤1:获取subject的权限(principals)与证书(credentials)

//最常使用场景实例: //字符类型用户名与密码。在系统特定方式下获取,比如HTTP请求,GUI等等。 UsernamePasswordToken token = new UsernamePasswordToken( username, password ); //内置的“Remember Me”是这样使用的: token.setRememberMe(true);


在这种情况下,使用的是UsernamePasswordToken类,它是Shiro框架中最常用的认证token。

UsernamePasswordToken用来将从Java应用程序通过某种方式获取到的用户名和密码绑定到一起。用户名和密码可能是通过web表单提交,包含在HTTP请求头中,抑或通过命令行方式提交,但是在Shiro中通过哪种方式获取根本不重要,因为UsernamePasswordToken是与协议无关的。

在这个例子中,当用户返回时,让应用程序记住用户状态。所以当token被创建后,将内置的“Remember Me”激活,激活“Remember Me”是通过设置token的setRmemberMe()为true实现的。

步骤2:提交这些权限与证书到身份认证系统

现在已经在token中记录下了用户信息,并且为再次返回的用户开启了“Remember Me”服务。身份认证的下一步是将这个token提交到身份认证系统。这里的身份认证系统在Shiro中是与安全相关的Dao,也叫做realm。想了解realm更多的信息,请查阅​​Shiro Realm指南​​。

在Shiro中尽最大可能让这个提交的过程快速、容易的完成。实现它仅仅需要一行Java代码!

//在Shiro中,通常都希望使用当前正在执行的用户,也就是subject。 Subject currentUser = SecurityUtils.getSubject(); //认证用户是通过将包含用户名、密码信息的token当做参数传递给login方法。 currentUser.login(token);


首先我们需要获取到当前用户,也就是subject,subject是应用程序中用户在安全领域特定用户的缩影。它可以是真实的用户、第三方进程、连接到应用的server、甚至是corn作业。在Shiro中,通常都会为当前线程绑定一个subject。subject是Shiro的核心概念,其他类似的安全框架也是围绕subject展开的。在示例中将这个subject实例命名为currentUser。

通过Shiro API的核心类SecurityUtils来获取当前subject,通过它的getSubject()方法获取当前正在执行的用户,而后获取到代表当前正在与系统交互用户的subject。示例中的currentUser是没有身份标识的匿名用户。

现在已经得到一个用户实例,然后调用login()方法,将刚刚创建好的token提交即可进行身份认证。

步骤3:允许访问,重新认证,或者中断访问

仍然非常非常简洁,只有一句方法调用。如果login()方法正常执行,那么用户就成功登陆,并且与特定用户账户或身份标识相关联。这样用户就可以使用应用程序了,可以将自身的标识存储到session中,由于在示例中设置了“Remember Me”,或许还可以存储更长时间。

但是如果身份认证失败会发生什么事情呢?比如密码错误、访问系统次数过多,或者用户账户被锁定等。在Shiro中,如果身份认证失败,则会抛出异常,这时Shiro全面的异常层次结构就起到了其应有的作用。

try {     currentUser.login(token); } catch ( UnknownAccountException uae ) { ... } catch ( IncorrectCredentialsException ice ) { ... } catch ( LockedAccountException lae ) { ... } catch ( ExcessiveAttemptsException eae ) { ... } ... catch your own ... } catch ( AuthenticationException ae ) { //unexpected error? } //未检测到异常,则显示已认证页面


将login()方法调用包含在try/catch块中,如果想对异常进行处理并作出回应,那么可以捕获所有异常序列。除了Shiro本身提供的大量的异常,也可以根据需要创建自定义异常。欲了解自定异常更多的信息,请查阅​​AuthenticationException​​。

安全提示

安全最佳实践建议将登陆错误信息反馈给用户,因为你总不会愿意帮助入侵者入侵你的应用程序吧。

“Remember Me”的支持

就像上面示例中展现的那样,Shiro在常规的登陆过程中支持“remember me”概念。

Shiro中Subject对象提供两个方法:isRemembered() and isAuthenticated().。

一个“被记住的”subject对象拥有自己的身份标识,属性(通常也叫做权限),这些信息都是在身份认证成功后的session中获取的。

通过认证的subject在当前session中提供身份标识信息。

注意

被记住的subject与通过认证的subject是不一样的。

Remembered vs Authenticated

Shiro中区分被记住的subject和通过认证的subject是非常重要的,因为认证是验证用户身份的过程,所以isAuthenticated()方法是一个更严格的检查。当用户被系统记住,被记住的身份标识仅仅能告诉系统它可能是怎么的一个用户,但事实上,是没办法保证被记住的Subject就是当前正在使用应用程序的用户。一旦subject通过身份认证,由于身份标识在当前session中已经得到了验证,所以它们就不仅仅是“被记住的”。

尽管应用程序中的大部分可以基于记住的principals执行用户特定的逻辑,比如自定义视图等。但是在用户完成身份认证之前都不会执行高度敏感的操作。

比如验证一个subject是否可以访问财务数据时,通常都要依靠isAuthenticated()而不是isRemembered()来确认该身份标识是否已通过验证。

下面这个场景帮助我们理解区分isAuthenticated 和isRemembered的重要性。

就拿使用Amazon.com来说,一天你登陆了,随后在购物车中添加了几本书,一天之后,session过期,你自然被系统登出。但是Amazon通过名字记住了你,依然会针对你推荐个性的书籍。这时,对于Amazon来说isRemembered()的返回值就是true。但是如果试图使用文件上的信用卡或者更改账户信息时会发生什么呢?尽管Amozon已经记住了你,isRemembered()也等于true,但是这些并不能确保你就是当前用户,isAuthenticated()=false。所以在进行敏感操作前Amazon会通过跳转到登陆页面强制进行身份标识认证。登陆之后你的身份得到了验证,这时isAuthenticated()=true。

在web应用中,这个场景经常发生,所以Shiro内置了这个功能来帮助轻松的做出区分。

登出

最后,用户使用应用完毕,则可以登出系统。Shiro中的登出非常快捷和简单,一句调用就搞定。

currentUser.logout(); //删除所有身份识别信息,并销毁session


使用Shiro进行登出时,会删除subject实例中的身份识别信息,还会将session销毁。如果在web环境下使用了Remember Me,logout()方法默认会从浏览器中删除Remember Me的cookie信息。

​​