在贴出windows集成认证代码之前,先用一个简单的例子来介绍JAAS。
Java Authentication Authorization Service(JAAS,Java验证和授权API)提供了灵活和可伸缩的机制来保证客户端或服务器端的Java程序。Java早期的安全框架强调的是通过验证代码的来源和作者,保护用户避免受到下载下来的代码的攻击。JAAS强调的是通过验证谁在运行代码以及他/她的权限来保护系统面受用户的攻击。它让你能够将一些标准的安全机制,例如Solaris NIS(网络信息服务)、Windows NT、LDAP(轻量目录存取协议),Kerberos等通过一种通用的,可配置的方式集成到系统中。
你是否曾经需要为一个应用程序实现登录模块呢?如果你是一个比较有经验的程序员,相信你这样的工作做过很多次,而且每次都不完全一样。你有可能把你的登录模块建立在Oracle数据库的基础上,也有可能使用的是NT的用户验证,或者使用的是LDAP目录。如果有一种方法可以在不改变应用程序级的代码的基础上支持上面提到的所有这一些安全机制,对于程序员来说一定是一件幸运的事。
现在你可以使用JAAS实现上面的目标。JAAS是一个比较新的的Java API。在J2SE 1.3中,它是一个扩展包;在J2SE 1.4中变成了一个核心包。在本文中,我们将介绍JAAS的一些核心概念,然后通过例子说明如何将JAAS应用到实际的程序中。本文的例子是根据我们一个基于Web的Java应用程序进行改编的,在这个例子中,我们使用了关系数据库保存用户的登录信息。由于使用了JAAS,我们实现了一个健壮而灵活的登录和身份验证模块。
客户端和服务器端的JAAS
开发人员可以将JAAS应用到客户端和服务器端。在客户端使用JAAS很简单。在服务器端使用JAAS时情况要复杂一些。目前在应用服务器市场中的 JAAS产品还不是很一致,使用JAAS的J2EE应用服务器有一些细微的差别。例如JBossSx 使用自己的结构,将JAAS集成到了一个更大的安全框架中;而虽然WebLogic 6 .x也使用了JAAS,安全框架却完全不一样。
现在你能够理解为什么我们需要从客户端和服务器端的角度来看JAAS了。我们将在后面列出两种情况下的例子。为了使服务器端的例子程序更加简单,我们使用了Resin应用服务器。
核心JAAS类
在使用JAAS之前,你首先需要安装JAAS。在J2SE 1.4中已经包括了JAAS,但是在J2SE 1.3中没有。如果你希望使用J2SE 1.3,你可以从SUN的官方站点上下载JAAS。当正确安装了JAAS后,你会在安装目录的lib目录下找到jaas.jar。你需要将该路径加入 Classpath中。(注:如果你安装了应用服务器,其中就已经包括了JAAS,请阅读应用服务器的帮助文档以获得更详细的信息)。在Java 安全属性文件java.security中,你可以改变一些与JAAS相关的系统属性。该文件保存在<jre_home>/lib/security目录中。
在应用程序中使用JAAS验证通常会涉及到以下几个步骤:
1. 创建一个LoginContext的实例。
2. 为了能够获得和处理验证信息,将一个CallBackHandler对象作为参数传送给LoginContext。
3. 通过调用LoginContext的login()方法来进行验证。
4. 通过使用login()方法返回的Subject对象实现一些特殊的功能(假设登录成功)。
下面是一个简单的例子
1. 配置文件(假设为D:\login.config):
com.demo { com.demo.SimpleLoginModule required debug = true; };
2.SimpleLogin.java
package com.demo; import javax.security.auth.login.LoginContext; import javax.security.auth.login.LoginException; public class SimpleLogin { public static void main(String[] args) { LoginContext loginContext = null; try { System.setProperty("java.security.auth.login.config", "D:\\login.config"); loginContext = new LoginContext("com.demo", new SimpleCallbackHandler()); loginContext.login(); } catch (LoginException e) { System.out.println(e.getMessage()); } } }
3.SimpleLoginModule.java
package com.demo; import java.io.IOException; import java.util.Map; import javax.security.auth.Subject; import javax.security.auth.callback.Callback; import javax.security.auth.callback.CallbackHandler; import javax.security.auth.callback.NameCallback; import javax.security.auth.callback.PasswordCallback; import javax.security.auth.callback.UnsupportedCallbackException; import javax.security.auth.login.LoginException; import javax.security.auth.spi.LoginModule; public class SimpleLoginModule implements LoginModule { private String userName; private char[] password; private Subject subject; private CallbackHandler callbackHandler; private Map sharedState; private Map options; private String debug; public boolean abort() throws LoginException { System.out.println("abort()"); return false; } public boolean commit() throws LoginException { System.out.println("commit()"); return false; } public void initialize(Subject subject, CallbackHandler callbackHandler, Map sharedState, Map options) { this.subject = subject; this.callbackHandler = callbackHandler; this.sharedState = sharedState; this.options = options; debug = (String) options.get("debug"); } public boolean login() throws LoginException { Callback[] callbacks = new Callback[2]; callbacks[0] = new NameCallback("用户名: "); callbacks[1] = new PasswordCallback("密码: ", false); try { callbackHandler.handle(callbacks); userName = ((NameCallback) callbacks[0]).getName(); password = ((PasswordCallback) callbacks[1]).getPassword(); if (debug.equals("true")) { System.out.println("你输入的用户名为:" + userName); System.out.println("你输入的密码为:" + new String(password)); } if (userName.equals("dudd") && new String(password).equals("11111111")) { System.out.println("验证成功"); return true; } else { System.out.println("验证失败"); userName = null; password = null; } } catch (IOException e) { e.printStackTrace(); } catch (UnsupportedCallbackException e) { e.printStackTrace(); } return false; } public boolean logout() throws LoginException { System.out.println("logout()"); return false; } }
4.SimpleCallbackHandler.java
package com.demo; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import javax.security.auth.callback.Callback; import javax.security.auth.callback.CallbackHandler; import javax.security.auth.callback.NameCallback; import javax.security.auth.callback.PasswordCallback; import javax.security.auth.callback.UnsupportedCallbackException; public class SimpleCallbackHandler implements CallbackHandler { public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException { for (Callback callback : callbacks) { if (callback instanceof NameCallback) { NameCallback nc = (NameCallback) callback; System.out.print(nc.getPrompt()); System.out.flush(); nc.setName((new BufferedReader(new InputStreamReader(System.in))).readLine()); } else if (callback instanceof PasswordCallback) { PasswordCallback pcb = (PasswordCallback) callback; System.out.print(pcb.getPrompt()); System.out.flush(); pcb.setPassword((new BufferedReader(new InputStreamReader(System.in))).readLine().toCharArray()); } } } }
在运行这段代码时,后台进行了以下的工作。
1. 当初始化时,LoginContext对象首先在JAAS配置文件中找到com.demo 项,然后根据该项的内容决定该加载哪个LoginModule对象。
2. 在登录时,LoginContext对象调用每个LoginModule对象的login()方法。
3. 每个login()方法进行验证操作或获得一个CallbackHandle对象。
4. CallbackHandle对象通过使用一个或多个CallBack方法同用户进行交互,获得用户输入。
5. 向一个新的Subject对象中填入验证信息。