刘景林
(泉州经贸职业技术学院信息技术系, 福建 泉州 362000)
随着Java技术应用的不断普及,J2EE已日渐成为企业级软件开发的首选平台,基于J2EE架构的Web系统广泛应用于电子政务、电子商务及网上银行等其他安全性要求较高的领域,但由于Internet中存在诸多不安全因素,对于在实现网上业务的同时,如何提高系统用户认证和数据传输等方面的安全问题,已成为系统开发人员关注的重点,关于J2EE架构安全方面的研究也在不断地深入.JAAS技术作为Java2安全体系结构的一个重要组成部分,其主要功能是实现用户认证与访问控制.JAAS认证是建立在可插入的认证模块PAM(Pluggable Authentication Module)的安全体系结构之上的[1],PAM是模块化的,可以集成多种认证技术,这可使J2EE应用系统独立于底层的认证技术,方便其灵活地选择与修改所使用的认证技术,也可以实现多种认证机制的堆叠认证,该认证技术提供了一种实现J2EE Web系统安全认证的解决方案.
JAAS(Java Authentication and Authorization Service)是J2EE架构的验证与授权框架,它提供了灵活、可伸缩的实体认证与访问控制的安全机制,解决了安全管理系统中的认证与授权问题.JAAS侧重于通过验证运行代码的用户及其权限来保护系统免受攻击,通过在应用程序和底层的验证与授权机制之间加入一个抽象层,该抽象层具有独立于平台的特性,使得各种不同的安全机制和应用程序级代码隔离,从而支持多种不同的安全认证方式[2].JAAS技术可以实现应用程序独立于底层的认证机制,应用程序可以使用一个新的或升级的认证技术而不需要修改其代码.因此,JAAS提供了一种可以实现将各种认证机制通过一种通用的、可配置的方式集成在一起的框架,使得Web应用系统的登录与授权模块更加具有灵活性和可扩展性.
JAAS包括两方面的内容:一是认证(Authentication), 提供可靠而安全的机制,是系统通过主体提交的身份信息对主体进行身份鉴别的过程.典型的过程就是提交用户名和密码来进行认证,保证合法的用户才能执行应用程序;二是授权(Authorization) ,通过声明性授权或编程授权的方式将访问资源的权限授予已认证的用户,用户只允许访问其授权范围内的资源.
用户在访问J2EE应用程序时,首先必须通过LoginContext 对象进行身份验证,LoginContext对象调用LoginModules,由LoginModules执行具体的登录认证.JAAS整体框架如图1所示,其中的“Security and Authentication Services”为LoginModules 提供底层认证服务,开发人员也可以通过实现LoginModule 接口,编写自己的验证机制.
图1 JAAS总体框架
J2EE Web应用程序只与LoginContext打交道,不需要与底层的安全机制直接交互.在LoginContext之下是一组可以动态配置的LoginMudules对象,LoginMudule负责使用正确的安全机制进行验证.利用在登录配置文件中进行动态指定要使用的哪个LoginModule登录模块,采取相应的安全机制进行认证.验证机制LoginModules 是可以堆叠的,一次验证过程可以选择多个验证机制,例如在用户认证过程中既实现口令认证又进行客户端数字证书的双重堆叠安全认证.验证机制的增加或者修改不影响应用程序的使用,体现了JAAS认证机制的可插入性.
与JAAS安全机制中用户认证过程相关的类主要有:
(1)Subject类:以javax.security.auth.Subject类表示,用于代表请求的来源,可以是任何验证的实体,例如是用户或服务.
(2)LoginContext类:称为登录上下文,应用程序主要使用LoginContext来验证和授权Subject对象.当创建一个LoginContext的实例时,需要指定一个配置.LoginContext通常从JAAS登录配置文件中加载配置信息,这些配置信息告诉LoginContext对象在登录时所使用的哪个或哪些LoginModule对象.
(3)LoginModule类:LoginContext对象实际调用的认证模块类.LoginModule的实现类可以支持图1中的常见认证方式.用户也可以通过实现LoginModule接口进行自定义LoginModule登录模块.
(4)CallbackHandler接口和Callback接口:应用程序实现CallbackHandler接口,并将其传递到JAAS框架,通过与应用程序交互实现获取身份验证数据(如用户名与密码).CallbackHandler类处理Callback类型的回调(Callback).常用的回调方式有:NameCallback用于提示输入用户名,PasswordCallback用于提示用户输入密码.LoginModule类可以传递一系列的Callback对象给CallbackHandler的handle进行处理.
利用JAAS技术实现用户认证的基本流程是首先要初始化一个名为LoginContext对象的登录环境,其作用是建立用户与系统之间身份验证的一个会话,然后再调用该对象的Login方法执行具体的登录认证过程,最后只有当用户身份被验证合法之后才能访问系统资源.
在使用JAAS安全机制实现用户身份认证过程中,需要先创建一个登录配置文件以指明系统登录时所使用的认证模块.JAAS登录配置文件可以由一个或多个项组成,每个项为某个特定的应用指定底层所使用的认证模块.在本系统中创建了名为jaas.conf 的登录配置文件,其中包含一个名为JAASExample的项,该项对应UserModule和CertModule两个自定义认证模块作为用户登录时所使用的底层认证模块.该文件包含内容如下:
JAASExample{
auth.UserModule required debug=true;
auth.CertModule required debug=true;
}
接下来系统首先实例化一个登录上下文LoginContext对象lc,并分别传入两个参数,构造方法的第一个参数为 “JAASExample”,即为配置文件jaas.conf中的一个项的名字,LoginContext将自动读取并加载登录配置文件中该项名称对应的登录模块;然后通过创建一个UsrPwdServletCallbackHandler实例对象作为LoginContext初始化时的第二个参数,该类用于获取认证用户的用户名和密码.LoginContext初始化一个新的、空的javax.security.auth.Subject对象,通过使用该Subject对象和UsrPwdServletCallbackHandler对象初始化UserModule认证模块.LoginContext对象的Login方法调用登录认证模块中的Login方法执行登录认证,此时LoginContext将UsrPwdServletCallbackHandler实例对象转交给参数“JAASExample”指定的底层认证模块UserModule.当登录认证模块需要与用户交互获取用户名和密码时,该认证模块是不直接与用户打交道的,而是调用从LoginContext对象中传递过来的UsrPwdServletCallbackHandler对象来执行与用户的交互,通过使用一个或多个Callback对象来执行与用户的交互,以获取用户名和密码的认证信息,并将之传递给底层认证模块实现登录用户的身份认证.
以下代码首先实例化一个LoginContext对象lc, lc在jaas.conf文件中查找名字为“JAASExample”的项,找到该项指定要求的底层认证技术模块,即分别调用UserModule和CertModule两个模块实现具体认证.当需要改变认证模块时,只需修改该项中指定的模块即可.LoginContext构造方法的第二个参数为新建的UsrPwdServletCallbackHandler对象,其中username和password为通过request.getParameter()方法获取从表单输入的用户名和密码,接下来调用LoginContext对象lc的login方法执行认证.
LoginContext lc = new LoginContext(“JAASExample”,new
UsrPwdServletCallbackHandler(username, password) );
lc.login().
自定义的用户交互类UsrPwdServletCallbackHandler实现了CallbackHandler接口,该类用于获取认证信息(例如:用户名和密码), LoginContext将该CallbackHandler实例对象传递给底层的UserModule登录认证模块.在UsrPwdServletCallbackHandler()构造方法中传入获取的用户名和密码,首先将用户名和密码保存在对象的属性中,然后提取注册到其中的所有Callback实例,将用户名和密码传递给相应的Callback实例,该用户交互类主要实现如下:
public class UsrPwdServletCallbackHandler implements CallbackHandler {
String paramName;
String paramPasswd;
public UsrPwdServletCallbackHandler(String paramName, String paramPasswd) {
if (paramName == null || paramPasswd == null)
throw new NullPointerException();
this.paramName = paramName; //通过构造函数传入表单中输入的用户名与密码
this.paramPasswd = paramPasswd;
}
public void handle(Callback[] callbacks) throws IOException,
UnsupportedCallbackException {
for (int i = 0; i < callbacks.length; i++) {
if (callbacks、[i] instanceof NameCallback)
{
// 获取用户名,将用户名保存在NameCallback实例对象nc中
NameCallback nc = (NameCallback)callbacks[i];
nc.setName(paramName);
} else if (callbacks[i] instanceof PasswordCallback) {
//获取密码,将密码保存在PasswordCallback实例对象pc中
PasswordCallback pc = (PasswordCallback)callbacks[i];
pc.setPassword(paramPasswd.toCharArray());
}
…
}
以上的handle方法通过遍历Callback数组中的各个元素,提取注册到CallbackHandler中的所有Callback实例,将用户名和密码传递给相应的NameCallback实例和PasswordCallback实例.
当系统在执行LoginContext对象的login( )方法登录时,将依次自动调用登录配置文件中设置的各个指定登录模块的login( )方法来实现登录验证.本例中分别使用基于用户口令的登录认证模块UserModule和基于用户证书的认证模块CertModule共同实现用户的身份认证,只有全部通过两种认证模块的验证才能实现成功登录.
2.3.1 自定义用户信息登录模块UserModule
用户个人信息登录模块UserModule是通过获取用户名和密码实现认证.该认证模块给回调处理器的handle()方法传递一个适当的Callback类型的callbacks字节数组,callbacks含有用于获取用户名的NameCallback类型对象和用于获取密码的PasswordCallback类型对象,然后CallbackHandler执行所请求的用户交互,并将适当的值存入callbacks数组,登录认证模块通过从callbacks数组中获取用户名与密码.最后由自定义的UserProxy类访问MySQL数据库中的用户信息表,通过调用validateUser()方法以验证输入的用户登录信息是否与数据库用户表中某一用户信息相匹配.UserModule类实现LoginModule接口,其login( )方法主要实现代码为:
public boolean login( ) throws LoginException {
…
//创建充当登录模块与回调处理器之间桥梁的Callback对象
Callback[] callbacks = new Callback[2];
callbacks[0]= new NameCallback(“User name:”);
callbacks[1] = new PasswordCallback(“Password: ”, false);
…
callbackHandler.handle(callbacks); //执行回调处理器handle方法并传入callbacks数组
loginName = ((NameCallback)callbacks[0]).getName();
tmpPasswd = ((PasswordCallback)callbacks[1]).getPassword();
if(tmpPasswd == null) { tmpPasswd[0] = ' ';}
passwd = new String(tmpPasswd);
// 通过调用validateUser( )方法到MySQL数据库查询该用户信息实现认证
UserProxy up = new UserProxy();
boolean isValid = up.validateUser(loginName, passwd);
…
}
2.3.2 自定义证书认证模块CertModule
证书认证模块CertModule主要实现验证客户证书.首先在Tomcat服务器中配置HTTPS安全协议和双向SSL认证,当用户通过HTTPS方式访问Web站点时,则要求出示客户证书.由于对于同一CA,其颁发的所有证书都有一个唯一、不重复的证书序列号,因此可利用证书序列号作为用户身份的标志.服务器获取客户证书并将客户数字证书存到当前会话的“javax.servlet.request.X509Certificate”属性中,在CertModule登录模块的login( )方法中将得到的客户证书的序列号与MySQL数据库中保存的证书序列号字段进行比较,以认证用户身份[3].在读取客户端数字证书时,可以使用request.getAttribute( )方法获得X509Certificate数组,然后再读取X509Certificate对象中的序列号,该模块的login( )方法主要实现如下:
X509Certificate[]certs certs=(X509Certificate[])request.getAttribute(“javax.servlet.request.X509Certificate”);
if(ca.length >0)
{ X509Certificate x509certificate=ca[0];
String strDN;
String strSerialNumber;
strSerialNumber=x509certificate.getSerialNumber().toString();//获取证书序列号
strDN=x509certificate.getSubjectDN().getName();//获取用户名
//查询MySQL数据库用户列表,验证序列号为strSerialNumber的证书用户是否存在,并根据查询结果返回true或false
...
}
本系统的软件开发测试平台是基于JDK1.5,J2EE Web服务器使用Apache Tomcat5.5,用户个人身份信息存放于MySQL5.0开源数据库.
3.2.1 构建并布署J2EE Web应用
创建名称为myApp的J2EE Web应用,其文档结构和系统登录页面如图2所示.
图2 J2EE Web应用文档结构与系统登录页面
图中的login.jsp为系统测试用的登录页面;loginprocess.jsp为登录处理页面,实现成功登录时转入访问系统资源,否则提示出错,要求重新进行登录;在auth文件夹中存放自定义的登录认证模块UserModule和CertModule以及自定义的回调处理器类UsrPwdServletCallbackHandler.java;在WEB-INF文件夹的classes存放auth文件夹中所有java源程序相应的.class文件以及第三方的jar包.最后将整个系统打包生成myApp.war文件,将该文件复制到Tomcat的安装目录下的webapps文件夹进行布署.
3.2.2 利用OpenSSL工具构建数字证书
使用OpenSSL构建自签名根证书,并使用该根证书分别签发服务器证书和客户证书.由于CA根证书为自签发的,未经权威CA认证,因此应将根证书分别导入到服务器与客户机浏览器中的“受信任的根证书颁发机构”中;在服务器端分别将根证书和服务器证书复制到Tomcat安装目录下的conf文件夹;在客户机浏览器中导入客户证书,以便于服务器与客户端之间相互认证对方的证书[4].另外,由于要实现客户机的证书认证,所以还必须将所颁发的客户证书的序列号及用户个人信息存放在MySQL数据库中的用户信息表.
3.2.3 配置Tomcat服务器
首先配置Apache Tomcat 服务器的“configure Tomcat”项,指定JAAS登录配置文件jaas.conf(如图3),以便告知Java虚拟机JAAS配置文件的所在位置,也可通过修改%JAVA_HOME%jrelibsecurity文件夹下的java.security文件,在其中添加一行代码“java.security.auth.login.config= d:myAppWEB-INFjaas.conf”来实现,其中“d:myApp”为J2EE应用系统所在的文件夹,最后启动Tomcat服务器.
图3 在Tomcat服务器配置JAAS登录配置文件
接下来必须在Tomcat服务器中配置HTTPS安全协议及双向SSL认证,编辑Tomcat安装目录下的confserver.xml文件如下:
maxHttpHeaderSize=“8192” maxThreads=“150”minSpareThreads=“25” maxSpareThreads=“75” enableLookups=“false”disableUploadTimeout=“true” acceptCount=“100” scheme=“https” secure=“true” clientAuth=“true” sslProtocol=“TLS” /> 其中clientAuth设置为“true”,意味着服务器与客户机之间实现相互提交证书进行认证[5]. 3.2.4 客户机通过HTTPS方式访问服务器 当客户通过浏览器以HTTPS方式访问系统服务器时,要进行客户证书的确认.在系统登录过程中出现要求提交客户证书的对话框时,用户可以选择所使用的客户端数字证书,服务器获取客户证书并提取其证书序列号进行验证.最后,只有当用户名与口令以及客户数字证书的验证都通过之后才能成功登录系统进行访问. 在J2EE Web系统用户登录验证过程中,利用JAAS提供的动态、可插拔认证模型实现了基于用户口令与X509数字证书的双重堆叠认证,使用服务器证书与客户证书实现了双方相互间的SSL双向认证,并通过HTTPS连接实现了数据的安全传输.实践证明,该认证方案相对于基于用户口令或数字证书等其他单方面认证,既可以实现服务器对客户端的双重认证,有效防止非授权用户的非法访问,较好地解决系统用户的安全认证问题,又可以实现客户端对服务器的认证,防止登录到假冒的服务器,同时能保证认证双方所传输数据的安全,进一步增强Web系统的安全性. 参考文献 [1] 朱福喜,傅建明,唐晓军.JAVA项目设计与开发范例[M].北京:电子工业出版社,2005:253. [2] 彭 超 马 丁.新一代JSP网络编程入门与实践[M].北京:清华大学出版社,2007:310. [3] 马臣云,王 彦.精通PKI网络安全认证技术与编程实现[M]. 北京:人民邮电出版社,2008:398-400. [4] 梁 栋.Java加密与解密的艺术[M].北京:机械工业出版社,2010:341-350. [5] 罗 锐,程文青.Java安全体系在Web程序中的研究和应用[J].计算机应用与研究,2006,(7):113-114.4 结 论