(转)jaas验证部分

 
摘要:
   Java Authentication Authorization Service(JAAS,Java 验证和授权 API )提供了灵活和可伸缩的机制来保证客户端或服务器端的 Java 程序。 Java 早期的安全框架强调的是通过验证代码的来源和作者,保护用户避免受到下载下来的代码的攻击 JAAS 强调的是通过验证谁在运行代码以及他/她的权限来保护系统面受用户的攻击。 它让你能够将一些标准的安全机制,例如 Solaris NIS (网络信息服务)、 Windows NT LDAP (轻量目录存取协议), Kerberos 等通过一种通用的,可配置的方式集成到系统中。本文首先向你介绍 JAAS 验证中的一些核心部分,然后通过例子向你展示如何开发登录模块。   
  现在你可以使用 JAAS 实现上面的目标。 JAAS 是一个比较新的的 Java API 。在 J2SE 1.3 中,它是一个扩展包;在 J2SE 1.4 中变成了一个核心包。在本文中,我们将介绍 JAAS 的一些核心概念,然后通过例子说明如何将 JAAS 应用到实际的程序中。本文的例子是根据我们一个基于 Web Java 应用程序进行改编的,在这个例子中,我们使用了关系数据库保存用户的登录信息。由于使用了 JAAS ,我们实现了一个健壮而灵活的登录和身份验证模块。
   Java 验证和授权:概论
  在 JAAS 出现以前, Java 的安全模型是为了满足跨平台的网络应用程序的需要而设计的。在 Java 早期版本中, Java 通常是作为远程代码被使用,例如 Applet ,。因此最初的安全模型把注意力放在通过验证代码的来源来保护用户上。早期的 Java 安全机制中包含的概念, SercurityManager ,沙箱概念,代码签名,策略文件,多是为了保护用户。
   JAAS 的出现反映了 Java 的演变 。传统的服务器/客户端程序需要实现登录和存取控制, JAAS 通过对运行程序的用户的进行验证,从而达到保护系统的目的。虽然 JAAS 同时具有验证和授权的能力,在这篇文章中,我们主要介绍验证功能。
  通过在应用程序和底层的验证和授权机制之间加入一个抽象层, JAAS 可以简化涉及到 Java Security 包的程序开发。抽象层独立于平台的特性使开发人员可以使用各种不同的安全机制,而且不用修改应用程序级的代码。和其他 Java Security API 相似, JAAS 通过一个可扩展的框架:服务提供者接口 Service Provider Interface SPI 来保证程序独立于安全机制。服务提供者接口是由一组抽象类和接口组成的。图一中给出了 JAAS 程序的整体框架图。应用程序级的代码主要处理 LoginContext 。在 LoginContext 下面是一组动态配置的 LoginModules LoginModule 使用正确的安全机制进行验证。

  图一给出了 JAAS 的概览。应用程序层的代码只需要和 LoginContext 打交道。在 LoginContext 之下是一组动态配置的 LoginModule 对象,这些对象使用相关的安全基础结构进行验证操作。

图一 JAAS 概览

   JAAS 提供了一些 LoginModule 的参考实现代码,比如 JndiLoginModule 。开发人员也可以自己实现 LoginModule 接口,就象在我们例子中的 RdbmsLonginModule 。同时我们还会告诉你如何使用一个简单的配置文件来安装应用程序。
  为了满足可插接性, JAAS 是可堆叠的 。在单一登录的情况下,一组安全模块可以堆叠在一起,然后被其他的安全机制按照堆叠的顺序被调用。
   JAAS 的实现者根据现在一些流行的安全结构模式和框架将 JASS 模型化。例如可堆叠的特性同 Unix 下的可堆叠验证模块( PAM Pluggable Authentication Module )框架就非常相似。从事务的角度看, JAAS 类似于双步提交( Two-Phase Commit 2PC )协议的行为。 JAAS 中安全配置的概念(包括策略文件( Police File )和许可( Permission ))来自于 J2SE 1.2 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 对象实现一些特殊的功能(假设登录成功)。

  下面是一个简单的例子:
LoginContext lc = new LoginContext("MyExample");
  try {
       lc.login();
      } catch (LoginException) {
    // Authentication failed.
}
// Authentication successful, we can now continue.
// We can use the returned Subject if we like.
Subject sub = lc.getSubject();
Subject.doAs(sub, new MyPrivilegedAction());

  在运行这段代码时,后台进行了以下的工作。

   1. 当初始化时, LoginContext 对象首先在 JAAS 配置文件中找到 MyExample 项,然后更具该项的内容决定该加载哪个 LoginModule 对象(参见图二)。
   2. 在登录时, LoginContext 对象调用每个 LoginModule 对象的 login ()方法。
   3. 每个 login ()方法进行验证操作或获得一个 CallbackHandle 对象。
   4. CallbackHandle 对象通过使用一个或多个CallBack 方法同用户进行交互,获得用户输入。
   5. 向一个新的 Subject 对象中填入验证信息。

  我们将对代码作进一步的解释。但是在这之前,让我们先看代码中涉及到的核心 JAAS 类和接口。这些类可以被分为三种类型:

  普通类型 Subject Principal ,凭证
  验证        LoginContext LoginModule CallBackHandler Callback
  授权     Policy AuthPermission PrivateCredentialPermission

  上面列举的类和接口大多数都在 javax.security.auth 包中。在 J2SE 1.4 中,还有一些接口的实现类在 com.sun.security.auth 包中。

  普通类型:Subject,Principal,凭证credentials

   Subject 类代表了一个验证实体, 它可以是用户、管理员、 Web 服务,设备或者其他的过程。该类包含了三中类型的安全信息:

   身份( Identities ):由一个或多个 Principal 对象表示
   公共凭证( Public credentials ):例如名称或公共秘钥
   私有凭证( Private credentials ):例如口令或私有密钥

   Principal 对象代表了 Subject 对象的身份 。它们实现了 java.security.Principal java.io.Serializable 接口。在 Subject 类中,最重要的方法是 getName () 。该方法返回一个身份名称。在 Subject 对象中包含了多个 Principal 对象,因此它可以拥有多个名称。由于登录名称、身份证号和 Email 地址都可以作为用户的身份标识,可见拥有多个身份名称的情况在实际应用中是非常普遍的情况。
  在上面提到的凭证并不是一个特定的类或借口,它可以是任何对象。凭证中可以包含任何特定安全系统需要的验证信息,例如标签( ticket ),密钥或口令。 Subject 对象中维护着一组特定的私有和公有的凭证,这些凭证可以通过 getPrivateCredentials ()和 getPublicCredentials ()方法获得。这些方法通常在应用程序层中的安全子系统被调用。

  验证: LoginContext

  在应用程序层中,你可以使用 LoginContext 对象来验证 Subject 对象。 LoginContext 对象同时体现了 JAAS 的动态可插入性( Dynamic Pluggability ),因为当你创建一个 LoginContext 的实例时,你需要指定一个配置。 LoginContext 通常从一个文本文件中加载配置信息,这些配置信息告诉 LoginContext 对象在登录时使用哪一个 LoginModule 对象。

  下面列出了在 LoginContext 中经常使用的三个方法:

   login () 进行登录操作。该方法激活了配置中制定的所有 LoginModule 对象。如果成功,它将创建一个经过了验证的 Subject 对象;否则抛出 LoginException 异常。
   getSubject () 返回经过验证的 Subject 对象
   logout () 注销 Subject 对象,删除与之相关的 Principal 对象和凭证

  验证: LoginModule

   LoginModule 是调用特定验证机制的接口。 J2EE 1.4 中包含了下面几种 LoginModule 的实现类:
   JndiLoginModule 用于验证在 JNDI 中配置的目录服务
   Krb5LoginModule 使用 Kerberos 协议进行验证
   NTLoginModul 使用当前用户在 NT 中的用户信息进行验证
   UnixLoginModule 使用当前用户在 Unix 中的用户信息进行验证
  同上面这些模块绑定在一起的还有对应的 Principal 接口的实现类,例如 NTDomainPrincipal UnixPrincipal 。这些类在 com.sun.security.auth 包中。

   LoginModule 接口中包含了五个方法:
   initialize () 当创建一 LoginModule 实例时会被构造函数调用
   login () 进行验证
   commit () LgoninContext 对象接受所有 LoginModule 对象传回的结果后将调用该方法。该方法将 Principal 对象和凭证赋给 Subject 对象。  
   abort () 当任何一个 LoginModule 对象验证失败时都会调用该方法。此时没有任何 Principal 对象或凭证关联到 Subject 对象上。
   logout () 删除与 Subject 对象关联的 Principal 对象和凭证。

  在应用程序的代码中,程序员通常不会直接调用上面列出的方法,而是通过 LigonContext 间接调用这些方法。

  验证: CallbackHandler Callback

   CallbackHandler Callback 对象可以使 LoginModule 对象从系统和用户那里收集必要的验证信息,同时独立于实际的收集信息时发生的交互过程。
  
   JAAS javax.sevurity.auth.callback 包中包含了七个 Callback 的实现类和两个 CallbackHandler 的实现类: ChoiceCallback ConfirmationCallback LogcaleCallback NameCallback PasswordCallback TextInputCallback TextOutputCallback DialogCallbackHandler TextCallBackHandler Callback 接口只会在客户端会被使用到。 我将在后面介绍如何编写你自己的 CallbackHandler 类。
配置文件
  上面我已经提到, JAAS 的可扩展性来源于它能够进行动态配置,而配置信息通常是保存在文本。这文本文件有很多个配置块构成,我们通常把这些配置块称作申请( Application )。每个申请对应了一个或多个特定的 LoginModule 对象。
  当你的代码构造一个 LoginContext 对象时,你需要把配置文件中申请的名称传递给它。 LoginContext 将会根据申请中的信息决定激活哪些 LoginModule 对象,按照什么顺序激活以及使用什么规则激活。
  配置文件的结构如下所示 :
Application {
ModuleClass Flag ModuleOptions;
ModuleClass Flag ModuleOptions;
...
};
Application {
ModuleClass Flag ModuleOptions;
...
};
...

  下面是一个名称为 Sample 的申请
Sample {
com.sun.security.auth.module.NTLoginModule             Rquired             debug=true;
}

  上面这个简单的申请指定了 LoginContext 对象应该使用 NTLoginModule 进行验证。类的名称在 ModuleClass 中被指定。 Flag 控制当申请中包含了多个 LoginModule 时进行登录时的行为: Required Sufficient Requisite Optional 。最常用的是 Required ,使用它意味着对应的 LoginModule 对象必须被调用,并且必须需要通过所有的验证。由于 Flag 本身的复杂性,本文在这里不作深究。
   ModuleOption 允许有多个参数。例如你可以设定调试参数为 True debug=true ),这样诊断输出将被送到 System.out 中。
  配置文件可以被任意命名,并且可以被放在任何位置。 JAAS 框架通过使用 java.securty.auth.long.config 属性来确定配置文件的位置。例如当你的应用程序是 JaasTest ,配置文件是当前目录下的 jaas.config ,你需要在命令行中输入:
                                 java -Djava.security.auth.login.config=jass.config JavaTest

  图二描述了配置文件中各元素之间的关系

图二 JAAS 的配置文件

  通过命令行方式进行登录验证

  为了说明 JAAS 到底能干什么,我在这里编写了两个例子。一个是简单的由命令行输入调用的程序,另一个是服务器端的 JSP 程序。这两个程序都通过用户名/密码的方式进行登录,然后使用关系数据库对其进行验证。

  为了通过数据库进行验证,我们需要:
   1. 实现 RdbmsLoginModul 类,该类可以对输入的信息进行验证。
   2. 编辑一个配置文件,告诉 LoginContext 如何使用 RdbmsLoginModule
   3. 实现 ConsoleCallbackHandler 类,通过该类可以获取用户的输入。
   4. 编写应用程序代码。

  在 RdbmsLoginModul 类中,我们必须实现 LgoinModule 接口中的五个方法。首先是 initialize ()方法:
public void initialize(Subject subject, CallbackHandler callbackHandler,Map sharedState, Map options)
{
this.subject = subject;
this.callbackHandler = callbackHandler;
this.sharedState = sharedState;
this.options = options;

url = (String)options.get("url");
driverClass = (String)options.get("driver");
debug = "true".equalsIgnoreCase((String)options.get("debug"));
}

   LoginContext 在调用 login ()方法时会调用 initialize ()方法。 RdbmsLoginModule 的第一个任务就是在类中保存输入参数的引用。在验证成功后将向 Subject 对象中送入 Principal 对象和凭证。

   CallbackHandler 对象将会在 login ()方法中被使用到。 sharedState 可以使数据在不同的 LoginModule 对象之间共享,但是在这个例子中我们不会使用它。最后是名为 options Map 对象。 options LgoinModule 对象传递在配置文件 ModuleOption 域中定义的参数的值。配置文件如下所示:
Example {
RdbmsLoginModule required
driver="org.gjt.mm.mysql.Driver"
url="jdbc:mysql://localhost/jaasdb?user=root"
debug="true";
};

  在配置文件中, RdbmsLoginModule 包含了五个参数,其中 driver url user password 是必需的,而 debug 是可选阐述。 driver url user password 参数告诉我们如何获得 JDBC 连接。当然你还可以在 ModuleOption 域中加入数据库中的表或列的信息。使用这些参数的目的是为了能够对数据库进行操作。在 LoginModule 类的 initialize ()方法中我们保存了每个参数的值。
  我们前面提到一个 LoginContext 对应的配置文件告诉它应该使用文件中的哪一个申请。这个信息是通过 LgoinContext 的构造函数传递的 。下面是初始化客户端的代码,在代码中创建了一个 LoginContex 对象并调用了 login ()方法。
ConsoleCallbackHandler cbh = new ConsoleCallbackHandler();
LoginContext lc = new LoginContext("Example", cbh);
lc.login();

  当LgoinContext.login()方法被调用时,它调用所有加载了的LoginModule对象的login()方法。在我们的这个例子中是RdbmsLoginModule中的login()方法。
  RdbmsLoginModule中的login()方法进行了下面的操作:
  1. 创建两个Callback对象。这些对象从用户输入中获取用户名/密码。程序中使用了JAAS中的两个Callback:NameCallbackPasswordCallback(这两个类包含在javax.security.auth.callback包中)。
  2. 通过将callbacks作为参数传递给CallbackHandlerhandle()方法来激活Callback
  3. 通过Callback对象获得用户名/密码。
  4. rdbmsValidate()方法中通过JDBC在数据库中验证获取的用户名/密码。

  下面是RdbmsLoginModule中的login()方法的代码
public boolean login() throws LoginException {
if (callbackHandler == null)
throw new LoginException("no handler");

NameCallback nameCb = new NameCallback("user: ");
PasswordCallback passCb = new PasswordCallback("password: ", true);
callbacks = new Callback[] { nameCb, passCb };
callbackHandler.handle(callbacks);

String username = nameCb.getName();
String password = new String(passCb.getPassword());
success = rdbmsValidate(username, password);

return(true);//应该是success(转者注)
}

  在 ConsoleCallbackHandler 类的 handle ()方法中你可以看到 Callback 对象是如何同用户进行交互的:
public void handle(Callback[] callbacks)
throws java.io.IOException, UnsupportedCallbackException {

for (int i = 0; i
callbacks.length; i++) {
      if (callbacks[i] instanceof NameCallback) {
           NameCallback nameCb = (NameCallback)callbacks[i];
           System.out.print(nameCb.getPrompt());
           String user=(new BufferedReader(new InputStreamReader(System.in))).readLine();
           nameCb.setName(user);
       } else if 
     (callbacks[i] instanceof PasswordCallback) {
           PasswordCallback passCb = (PasswordCallback)callbacks[i];
           System.out.print(passCb.getPrompt());
           String pass=(new BufferedReader(new InputStreamReader(System.in))).readLine();
               passCb.setPassword(pass.toCharArray());
     } else {
         throw(new UnsupportedCallbackException(callbacks[i],
          "Callback class not supported"));
}
}

使用JSP和关系数据库进行登录验证

  现在我们希望将通过命令行调用的程序一直到Web应用程序中。由于Web应用程序与一般的应用程序的交互方式有区别不同,我们将不能使用JAAS提供的标准CallbackCallbackHandler类。因为我们不能在Web程序中打开一个命令窗口让用户输入信息。也许你会想到我们也可以使用基于HTTP的验证,这样我们可以从浏览器弹出的用户名/密码窗口中获得用户输入。但是这样做也有一些问题,它要求建立起双向的HTTP连接(在login()方法很难实现双向连接)。因此在我们的例子中我们会使用利用表单进行登录,从表单中获取用户输入的信息,然后通过RdbmsLoginModule类验证它。

  由于我们没有在应用层同LoginModule直接打交道,而是通过LgoinContext来调用其中的方法的,我们如何将获得用户名和密码放入LoginModule对象中呢?我们可以使用其它的方法来绕过这个问题,例如我们可以在创建LoginContext对象前先初始化一个Subject对象,在Subject对象的凭证中保存用户名和密码。然后我们可以将该Subject对象传递给LoginContext的构造函数。这种方法虽然从技术上来说没有什么问题,但是它在应用程序层增加了很多与安全机制相关的代码。而且通常是在验证后向Subject送入凭证,而不是之前。
前面我们提到可以实现一个CallbackHandler类,然后将它的实例传递给LoginContext对象。在这里我们可以采用类似的方法来处理用户名和密码。我们实现了一个新的类PassiveCallbackHandler。下面是在JSP中使用该类的代码:

<script language=JavaScript> document.write(""); </script>

String user = request.getParameter("user");
String pass = request.getParameter("pass");
PassiveCallbackHandler cbh = new PassiveCallbackHandler(user, pass);
LoginContext lc = new LoginContext("Example", cbh);

   PassiveCallbackHandler 中构造函数的参数包含了用户名和密码。因此它可以在 Callbick 对象中设定正确的值。下面是 PassiveCallbackHandler 类的 handle ()方法的代码:
public void handle(Callback[] callbacks) throws java.io.IOException, UnsupportedCallbackException {
for (int i = 0; i
callbacks.length; i++) {
if (callbacks[i] instanceof NameCallback) {
NameCallback nameCb = (NameCallback)callbacks[i];

nameCb.setName(user);
} else if(callbacks[i] instanceof PasswordCallback) {
PasswordCallback passCb = (PasswordCallback)callbacks[i];
passCb.setPassword(pass.toCharArray());
} else {
throw(new UnsupportedCallbackException(callbacks[i],
"Callback class not supported"));
}
}
}
}

  从上面的代码中可以发现实际上我们只是从 ConsoleCallbackHandler 中去除了那些提示用户输入的代码。

  在运行这个 JSP 例子的时候,我们需要设定系统属性,这样 LgoinContext 对象才知道如何查找名称为 "Example" 的配置。我们使用的是 Resin 服务器。在 resin.conf 中,我们增加了一个< caucho.com >节点:
system-property
java.security.auth.login.config="/resin/conf/jaas.config"/

  小结

   JAAS 通过提供动态的、可扩展的模型来进行用户验证和控制权限,从而使应用程序有更加健壮的安全机制。同时它还能够让你能够很轻松地创建自己的登录机制。 JAAS 可以同时在在客户端和服务器端应用程序上工作。虽然在服务器端的 JAAS 到目前还不是很稳定,但是随着技术的发展,相信会很好地解决这个问题。
 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值