3.4. 主题(Subject)验证
主题验证要求 JAAS 登录。登录过程由下列要点组成:
- 如
LoginModule配置所要求的,用程序实例化一个LoginContext并传入登录配置的名称和CallbackHandler来填充Callback对象。 LoginContext咨询Configuration来加载包括在命名登录配置里的所有LoginModules。如果不存在这个名称的配置,那么默认会使用other配置。- 应用程序调用
LoginContext.login方法。 - 登录模块调用所有加载的
LoginModule。每个LoginModule都会试图验证这个主题,它调用相关联的CallbackHandler上的 handle 犯法来获取验证过程所需的信息。这些信息以Callback对象队列的方式被传入 handle 方法。成功后,LoginModule将主题和相关的主体(principal)和凭证关联。 LoginContext返回验证状态给应用程序。从 login 方法返回则表示成功。如果 login 方法抛出异常则表示失败。- 如果验证成功,应用程序使用
LoginContext.getSubject方法获取已验证的主题。 - 在主题验证的作用域完成后,所有的主体以及和
login方法的主题相关的信息都可以通过调用LoginContext.logout方法进行删除。
LoginContext 类提供了用于验证主题的基本方法以及开发独立于底层验证技术的应用程序的途径。LoginContext 咨询 Configuration 来决定为特定应用程序配置的验证服务。LoginModule 类代表验证服务。因此,你可以插入不同的等录模块到应用程序而无需修改应用程序自身。下面的步骤显示了应用程序验证主题所需的步骤。
开发者通过创建
LoginModule 接口的实现集成验证技术。这允许管理员插入不同的验证技术到应用程序里。你可以将多个 LoginModule 链接在一起来允许多种验证技术来参与验证过程。例如,一个 LoginModule 可以执行基于用户/密码的验证,而另外一个则可以连接硬件设备如智能卡读写器或生物特征识别器。
LoginModule 的生命周期是由客户创建和发行 login 方法所根据的 LoginContext 对象驱动的。这个过程由两阶段组成:
LoginContext使用其公共的 no-arg 构造器创建每个已配置的LoginModule。- 每个
LoginModule都是通过其 initialize 方法来初始化的。Subject参数需保证是非 null 的。initialize 方法的签名是:public void initialize(Subject subject, CallbackHandler callbackHandler, Map sharedState, Map options)。 login被调用来启动验证过程。例如,某个方法实现可能提示用户输入用户名和密码,然后根据存储在命名服务(如 NIS 或 LDAP)里的数据进行检验。其他的实现可能连接智能卡和生物设备,或者简单地从底层操作系统抽取信息。每个LoginModule对用户标识符的检验会在 JAAS 验证的第一阶段来考虑。login方法的签名是boolean login() throws LoginException。LoginException表示失败。返回值为 true 表示方法调用成功,而 false 表示登录模块应该被忽略。- 如果
LoginException的总体验证成功,commit将在每个LoginModule上调用。如果LoginModule的第一阶段验证成功,commit 方法将继续第二阶段并将相关的主体、公共凭证和/或私有凭证和关联主题。如果LoginModule的第一阶段失败,那commit将删除任何之前保存的验证状态,如用户名或密码。commit方法的签名是:boolean commit() throws LoginException。LoginException的抛出表示完成提交阶段失败。返回值为 true 表示方法调用成功,而 false 表示登录模块应该被忽略。 - 如果
LoginException的总体验证失败,abort将在每个LoginModule上调用。abort方法删除或销毁任何之前 login 或 initialize 方法创建的验证状态。abort方法的签名是:boolean abort() throws LoginException。LoginException的抛出表示完成abort阶段失败。返回值为 true 表示方法调用成功,而 false 表示登录模块应该被忽略。 - 要在成功登录后删除验证状态,应用程序将调用
LoginContext上的logout。这会导致在每个LoginModule上调用logout方法。logout方法删除原来在commit阶段和主题相关联的主体和凭证。凭证应该在删除时被销毁。logout方法的签名是:boolean logout() throws LoginException。LoginException的抛出表示完成登出阶段失败。返回值为 true 表示方法调用成功,而 false 表示登录模块应该被忽略。
当
LoginModule 必须和用户进行通讯来获取验证信息时,它使用 CallbackHandler 对象。应用程序实现 CallbackHandler 接口并将其传入 LoginContext,这会直接发送验证信息到底层的等录模块。
登录模块使用
CallbackHandler 来获取用户的输入,如密码或智能卡 PIN,并提供信息给用户,如状态信息。通过允许应用程序指定 CallbackHandler,底层的 LoginModule 保持对应用程序和用户交互的独立。例如,GUI 应用程序的 CallbackHandler 实现可能显示一个窗口来让用户输入。另一方面,非 GUI 的应用程序可能 CallbackHandler 简单地使用应用服务器的 API 来获取凭证信息。 CallbackHandler 接口有一个方法需要被实现:
void handle(Callback[] callbacks)
throws java.io.IOException,
UnsupportedCallbackException;
void handle(Callback[] callbacks)
throws java.io.IOException,
UnsupportedCallbackException;
Callback 接口是我们最后将了解的验证类。它是为几个默认实现提供的一个标记接口,其中包括 NameCallback 和在以前示例里使用的 PasswordCallback。LoginModule 使用 Callback 来请求验证机制所需的信息。在验证的登录阶段,LoginModule 将一个 Callback 队列直接传入 CallbackHandler.handle 方法。如果 callbackhandler 无法理解如何使用传递到 handle 方法的 Callback,它会抛出 UnsupportedCallbackException 来中止 login 的调用。