JAAS – 机动的Java安详机制
副标题#e#
摘要:
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,安详框架却完全纷歧样。
#p#分页标题#e#
此刻你可以或许领略为什么我们需要从客户端和处事器端的角度来看JAAS了。我 们将在后头列出两种环境下的例子。为了使处事器端的例子措施越发简朴,我们 利用了Resin应用处事器。
#p#副标题#e#
焦点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工具从系统和用户哪里 收集须要的验证信息,同时独立于实际的收集信息时产生的交互进程。
#p#分页标题#e#
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"));
}
#p#分页标题#e#
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类验证它。
#p#分页标题#e#
由于我们没有在应用层同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到今朝还不是很不变,可是跟着技能的成长,相信会很好地办理这个问题。