Java Authentication Authorization Service(JAAS,Java验证和授权API)提供了灵活和可伸缩的机制来保证客户端或服务器端的Java程序。Java早期的安全框架强调的是通过验证代码的来源和作者,保护用户避免受到下载下来的代码的攻击。JAAS强调的是通过验证谁在运行代码以及他/她的权限来保护系统面受用户的攻击。它让你能够将一些标准的安全机制,例如Solaris NIS(网络信息服务)、Windows NT、LDAP(轻量目录存取协议),Kerberos等通过一种通用的,可配置的方式集成到系统中。本文首先向你介绍JAAS验证中的一些核心部分,然后通过例子向你展示如何开发登录模块。
你是否曾经需要为一个应用程序实现登录模块呢?如果你是一个比较有经验的程序员,相信你这样的工作做过很多次,而且每次都不完全一样。你有可能把你的登录模块建立在Oracle数据库的基础上,也有可能使用的是NT的用户验证,或者使用的是 LDAP目录。如果有一种方法可以在不改变应用程序级的代码的基础上支持上面提到的所有这一些安全机制,对于程序员来说一定是一件幸运的事。
现在你可以使用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,凭证
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类:NameCallback和 PasswordCallback(这两个类包含在javax.security.auth.callback包中)。
2. 通过将callbacks作为参数传递给CallbackHandler的handle()方法来激活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);
}
在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提供的标准Callback和CallbackHandler类。因为我们不能在Web程序中打开一个命令窗口让用户输入信息。也许你会想到我们也可以使用基于 HTTP的验证,这样我们可以从浏览器弹出的用户名/密码窗口中获得用户输入。但是这样做也有一些问题,它要求建立起双向的HTTP连接(在 login()方法很难实现双向连接)。因此在我们的例子中我们会使用利用表单进行登录,从表单中获取用户输入的信息,然后通过 RdbmsLoginModule类验证它。
由于我们没有在应用层同LoginModule直接打交道,而是通过 LgoinContext来调用其中的方法的,我们如何将获得用户名和密码放入LoginModule对象中呢?我们可以使用其它的方法来绕过这个问题,例如我们可以在创建LoginContext对象前先初始化一个Subject对象,在Subject对象的凭证中保存用户名和密码。然后我们可以将该 Subject对象传递给LoginContext的构造函数。这种方法虽然从技术上来说没有什么问题,但是它在应用程序层增加了很多与安全机制相关的代码。而且通常是在验证后向Subject送入凭证,而不是之前。
前面我们提到可以实现一个CallbackHandler类,然后将它的实例传递给LoginContext对象。在这里我们可以采用类似的方法来处理用户名和密码。我们实现了一个新的类PassiveCallbackHandler。下面是在JSP中使用该类的代码:
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到目前还不是很稳定,但是随着技术的发展,相信会很好地解决这个问题。
文章转载自: http://www.yesky.com/20030114/1648365.shtml
你是否曾经需要为一个应用程序实现登录模块呢?如果你是一个比较有经验的程序员,相信你这样的工作做过很多次,而且每次都不完全一样。你有可能把你的登录模块建立在Oracle数据库的基础上,也有可能使用的是NT的用户验证,或者使用的是 LDAP目录。如果有一种方法可以在不改变应用程序级的代码的基础上支持上面提到的所有这一些安全机制,对于程序员来说一定是一件幸运的事。
现在你可以使用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,凭证
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类:NameCallback和 PasswordCallback(这两个类包含在javax.security.auth.callback包中)。
2. 通过将callbacks作为参数传递给CallbackHandler的handle()方法来激活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);
}
在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提供的标准Callback和CallbackHandler类。因为我们不能在Web程序中打开一个命令窗口让用户输入信息。也许你会想到我们也可以使用基于 HTTP的验证,这样我们可以从浏览器弹出的用户名/密码窗口中获得用户输入。但是这样做也有一些问题,它要求建立起双向的HTTP连接(在 login()方法很难实现双向连接)。因此在我们的例子中我们会使用利用表单进行登录,从表单中获取用户输入的信息,然后通过 RdbmsLoginModule类验证它。
由于我们没有在应用层同LoginModule直接打交道,而是通过 LgoinContext来调用其中的方法的,我们如何将获得用户名和密码放入LoginModule对象中呢?我们可以使用其它的方法来绕过这个问题,例如我们可以在创建LoginContext对象前先初始化一个Subject对象,在Subject对象的凭证中保存用户名和密码。然后我们可以将该 Subject对象传递给LoginContext的构造函数。这种方法虽然从技术上来说没有什么问题,但是它在应用程序层增加了很多与安全机制相关的代码。而且通常是在验证后向Subject送入凭证,而不是之前。
前面我们提到可以实现一个CallbackHandler类,然后将它的实例传递给LoginContext对象。在这里我们可以采用类似的方法来处理用户名和密码。我们实现了一个新的类PassiveCallbackHandler。下面是在JSP中使用该类的代码:
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到目前还不是很稳定,但是随着技术的发展,相信会很好地解决这个问题。
文章转载自: http://www.yesky.com/20030114/1648365.shtml
相关推荐
JAAS是Java安全模型的重要组成部分,它允许开发者构建安全的应用程序,满足不同级别的安全需求。 在JAAS中,认证(Authentication)是指验证用户身份的过程,而授权(Authorization)则是确定已认证的用户是否有权...
### JAAS:灵活的Java安全机制 #### 一、引言 Java Authentication and Authorization Service (JAAS, Java验证和授权API)为Java应用程序提供了一种扩展标准Java 2安全模型的方法。通过添加验证主题(即用户或其他...
JAAS允许开发者集成多种标准的安全机制,如Solaris NIS、Windows NT、LDAP和Kerberos等,通过一个通用且可配置的方式。这样,开发者无需修改应用程序的主体代码,就能适应不同的安全需求。JAAS自J2SE 1.3起作为扩展...
总的来说,"Java JAAS安全认证 demo"是一个实用的学习资源,它展示了Java平台内置的安全机制,这对于构建安全的Web应用或企业级服务至关重要。开发者可以通过研究这个示例,掌握在实际项目中实施安全认证的最佳实践...
### 基于JAAS的Java安全编程 #### JAAS概览 JAAS(Java Authentication and Authorization Service,Java认证与授权服务)是Sun Microsystems为Java 2平台开发的一套安全框架扩展,旨在加强Java应用程序的安全性。...
JAAS作为Java安全编程的一个重要组成部分,为开发者提供了一套完整且灵活的安全框架,不仅能够满足不同类型的应用场景,还能够适应不断变化的安全需求。通过深入了解JAAS的核心概念、关键类和编程实践,开发者可以...
本文基于给定的文件信息,深入探讨Java安全机制的关键组成部分及其运作原理。 ### Java安全机制的体系结构 Java安全架构的核心在于其设计时就充分考虑了安全因素,而不是后期添加的。这一设计思路主要体现在以下三...
JAAS:灵活的Java安全机制 Java Authentication Authorization Service(JAAS,Java验证和授权API)提供了灵活和可伸缩的机制来保证客户端或服务器端的Java程序。Java早期的安全框架强调的是通过验证代码的来源和作者...
"Jaas in Action"这本书,虽然目前还未正式出版,但据称会深入探讨JAAS的相关概念和技术,对于理解和应用Java安全机制具有很高的参考价值。 JAAS的主要目标是解决以下几个核心问题: 1. **身份验证...
JAAS提供了一种标准的方式来实现这一目标,允许开发者在不深入了解底层安全机制的情况下,构建安全的应用程序。 **JAAS 登录验证机制** JAAS的核心概念是登录模块(LoginModule),它是处理用户身份验证逻辑的组件...
JavaEE安全机制是Java平台企业版(Java Enterprise Edition)中至关重要的组成部分,它涉及到一系列确保应用程序安全性的技术、模型和策略。本讲主要探讨以下几个核心主题: 1. **Java安全模型**:Java的安全模型...
最后,要在Java应用中启用JAAS,你需要在Java安全策略中指定使用这个配置文件。例如,通过 `-Djava.security.auth.login.config=/path/to/jaas.conf` 参数启动JVM。 以上就是JAAS的基本介绍和示例代码实现。通过...
总结来说,`jaas.jar` 是Java安全基础设施的关键组成部分,它提供了灵活的认证和授权机制,让开发者能够为Java应用程序构建安全的用户访问控制。通过理解和有效利用JAAS,开发者可以更好地保护他们的应用免受未授权...
2. **定义策略**:`demo.policy` 文件是Java安全策略文件,它指定了哪些权限被授予哪个主体(principal)。策略文件使用一种特定的语法,允许开发者精确地控制哪些代码段或用户可以执行特定操作。例如,你可以指定...
Java Authentication and Authorization Service (JAAS) 是Java平台中用于安全认证和授权的核心组件。这个"JAAS.rar_Action!_Jaas_Kerber_jaas_KERBEROS_kerberos_Java_login.a"压缩包文件包含了关于如何在Java环境...
- **《Java Security》**: 本书深入介绍了Java平台的安全模型和机制,包括JAAS在内的多个安全框架。 - **Java.net**: 官方网站提供了丰富的JAAS相关文档和技术文章,是学习JAAS的好地方。 - **Oracle官方文档**: ...
Java 平台是一个安全的平台,提供了多种安全机制来保护 Java 应用程序和数据。Java 平台安全机制包括身份验证、授权、加密、数字签名和访问控制等。这些机制可以帮助开发人员构建安全的 Java 应用程序。 安全开发...
总结来说,Jaas JDBC是Java安全体系中的一个重要组成部分,它提供了将用户认证数据存储在数据库中的能力,从而实现更灵活和安全的管理。通过理解其工作原理和实践应用,开发者可以更好地构建和维护具有强大安全特性...
这个框架使得开发者可以轻松地在Java应用程序中集成安全性,而不必深入理解底层的复杂安全机制。JAAS的核心理念是将认证(Authentication)和授权(Authorization)功能模块化,允许开发人员通过插件式的方式进行...
总之,《Java安全》中文版为Java开发人员提供了一个全面了解和应用Java安全机制的平台,帮助他们构建更加安全的应用程序。无论是对于初学者还是有经验的开发者,这本书都是一本宝贵的参考资料。