陈亚强
高级软件工程师
2004 年 8 月
本文首先简单介绍Web服务安全性基本概念,然后介绍在axis Web服务开发工具下处理安全性问题的一些常用方法;接下来以安全性实现方法为线索,介绍了怎么在Web应用的基础上配置Web服务的访问控制、怎么使用axis的Handler和Servlet的过滤器进行访问控制,最后讨论怎么在tomcat下配置SSL 以实现Web服务的安全传输。
axis是流行的Web服务开发工具,如何在axis下开发安全的Web服务,这是摆在每个开发者面前的问题。本文是J2EE Web服务开发系列文章的第十二篇,将首先简单介绍Web服务安全性基本概念,然后介绍在axis Web服务开发工具下处理安全性问题的一些常用方法;接下来以安全性实现方法为线索,介绍了怎么在Web应用的基础上配置Web服务的访问控制、怎么使用axis的Handler和Servlet的过滤器进行访问控制,最后讨论怎么在tomcat下配置SSL 以实现Web服务的安全传输。本文下一篇将介绍怎么在axis中实现WS-Security。
阅读本文前您需要以下的知识和工具:
- Apache axis1.1,并且会初步使用;
- Tomcat 4.0以上, 并且会初步使用;
- Servlet的开发经验;
- SOAP消息(SOAP Message)编程知识;
- JAX-RPC编程基础知识;
- Java安全编程基础知识。
本文的参考资料见 参考资料。
本文的全部代码在这里 下载。
Web服务安全概述
安全的Web服务是Web服务成功的必要保证。但众所周知的是,Web服务使用XML来进行数据交换,而XML在默认情况下是明文编码的;同时,大部分Web服务使用HTTP协议作为传输协议,同样,HTTP也是使用明文方式来传输数据的。这就造成了在不加密的传输协议上传输不加密的信息,从而使信息传输的保密性受到威胁。作为企业级的应用,以上的方式不能满足安全性基本要求:
- 数据在因特网上传播时不应该被第三方看到;
- 双方必须能够确定消息的来源;
- 双方必须能够确定被传送的数据没有被篡改。
通过使用SSL协议可以解决第一个问题:"不应该被第三方看到";使用数字签名和数字证书可以解决后面两个问题。当使用数字证书方法时,Web 服务请求者必须有一个由可信认证中心签署的数字证书。请求者使用这个证书来表明它们的身份,并对 SOAP 消息进行数字签名。对方系统接收到消息后,就可对消息做时间戳记并进行日志记录。此时,数字签名会得到验证。验证过程将确保消息来自发送方,并且还要验证消息内容在传输过程中没有被篡改。
IBM、Microsoft 和 Verisign 于2002年十二月份联合发布了一个关于 Web 服务安全性(Web Services Security,WS-Security)的规范,该规范描述如何向 SOAP 消息附加签名和加密报头;另外,它还描述如何向消息附加安全性令牌(包括二进制安全性令牌,如 X.509 证书),提供了一套帮助 Web 服务开发者保护 SOAP 消息交换的机制。
根据应用的对安全要求的级别不同,可以采用不同的方式来实现安全性,以下是目前最常用的一些实现方式(从低到高排列):
- J2EE Web应用默认的访问控制(数据是明文的);
- 使用axis的Handler进行访问控制(数据是明文的);
- 使用Servlet过滤器(Filter)进行访问控制(数据是明文的);
- 使用SSL/HTTPS协议来传输(加密的数据传输协议);
- 使用WS-Security规范对信息进行加密与身份认证(数据被加密传输)。
前三种方式对于安全级别要求不高的应用是可行的,它能够使用Web应用访问认证机制来进行权限验证,从而保护对资源的访问。但需要注意的是,虽然它们进行了身份验证,但信息的传递还是以明文的方式进行的,不能保证信息在传输过程中不被窃取。SSL是一个安全的传输协议,使用它传输Web服务能保证信息不被第三方窃取。但它有个缺点就是对系统资源消耗大。采用最后一种方式,信息被签名后再加密,然后把加密后的信息网络上传播,这样,即使第三方获得加密后的传输信息,也不能解密。对于安全级别要求高的系统,应该采用WS-Security规范来作为Web服务安全性解决方案。
开发、部署示例Web服务
作为一个实例性的教程,我们有必要先开发一个简单的Web服务作为示例。关于如何在Axis下开发并部署一个简单的Web服务你可以参考下面这篇文章: 用JAX-RPC开发Web服务:Servlet作为Web服务端点。
下面的例子提供的是一个提供个人所得税计算的Web服务。客户端传入工资的金额,Web服务将计算个人所得税金额。
例程 1 个人所得税Web服务
package com.hellking.study.webservice;
public class PersonalTaxService
{
final double base=1200;//所得税上缴基数。
public double getTax(double salary)
{
double tax_salary=salary-1200;
double tax=0.0d;//计算后的所得税。
if(0>tax_salary) tax=0;
else if(0<tax_salary&&tax_salary <=500) tax=tax_salary*0.05-0;
else if(500<tax_salary&&tax_salary<=2000) tax=tax_salary*0.10-25;
…
return tax;
}
}
|
编译后,你只要在Axis的部署配置文件(AXIS_HOME\Web-INF\server-config.wsdd)的适当位置增加以下部署代码,Web服务就可以部署成功。
例程2 部署示例Web服务
<service name="PersonalTaxService" provider="java:RPC">
<parameter name="allowedMethods" value="*"/>
<parameter name="className" value="com.hellking.study.webservice.PersonalTaxService"/>
</service>
|
部署后的Web服务URL为: http://localhost:8080/axis/services/PersonalTaxService?wsdl (请先部署好这个Web服务再进行下面的学习。)
从Web应用的基本认证说起
不管客户端通过什么API来调用Web服务,它总是先构造SOAP消息,然后通过HTTP POST方法把消息发送到Web服务的URL(注1:当然,Web服务也可以通过其它基于文本的协议传输,但是由于非常少见,在这里不讨论)。而我们知道,不管axis部署在哪种服务器上(Tomat还是Websphere),它总是以Servlet方式来运行的。所以最简单实现Web服务安全的方式就是通过在web应用的配置文件(web.xml)来实现访问控制。
J2EE web应用中有多种认证方式:BASIC(基本认证方法)、FORM(基于表单)、DIGEST(消息摘要)和CLIENT-CERT(数字证书)。关于J2EE Web应用的其它认证方式的配置请参考其它资料,在此不再介绍。
下面以Tomcat使用服务器为例,使用Web应用模型中基本认证方法来配置Web服务的安全访问。
Web应用的基本认证是建立在J2EE角色和用户的基础之上,首先在Tomcat角色配置文件中增加一个角色和一个帐号。
例程3 在tomcat-users.xml中增加角色(Tomcat_Home\conf\tomcat-users.xml)
<?xml version='1.0' encoding='utf-8'?>
<tomcat-users>
…
<role rolename="department-manager"/>
<user username="hellking" password="simplewebservices" roles="department-manager"/>
…
</tomcat-users>
|
上面配置代码在tomcat配置文件中添加了一个department-manager角色,并且在此角色中添加了一个名为hellking的用户。要使tomcat-users.xml中配置的角色和用户生效,需要配置tomcat使用UserDatabaseRealm。打开Tomcat_Home\conf\server.xml配置文件,在GlobalNamingResources中添加以下描述:
例程4 在tomcat中添加UserDatabaseRealm
<GlobalNamingResources>
...
<Resource name="UserDatabase" auth="Container"
type="org.apache.catalina.UserDatabase"
description="User database that can be updated and saved">
</Resource>
<ResourceParams name="UserDatabase">
<parameter>
<name>factory</name>
<value>org.apache.catalina.users.MemoryUserDatabaseFactory</value>
</parameter>
<parameter>
<name>pathname</name>
<value>conf/tomcat-users.xml</value>
</parameter>
</ResourceParams>
</GlobalNamingResources>
|
然后再web应用的部署描述符中指定Web服务资源的访问控制,如下所示:
例程5 配置对Servlet的访问控制(AXIS_HOME\WEB-INF\web.xml)
<security-constraint>
<web-resource-collection>
<web-resource-name>Tax Web service </web-resource-name>
<url-pattern>/services/PersonalTaxService</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>department-manager</role-name>
</auth-constraint>
</security-constraint>
<login-config>
<auth-method>BASIC</auth-method>
<realm-name>Axis Basic Authentication Area</realm-name>
</login-config>
<security-role>
<role-name>department-manager</role-name>
</security-role>
|
url-pattern指定了需要通过角色验证的URL样式,在这里是"/services/PersonalTaxService";role-name是能够访问制定URL的角色,这里是department-manager。以上配置的意思是只有角色类型是"department-manager"的用户才能访问URL样式为"/services/PersonalTaxService"Web服务。
下面我们看怎么在axis客户端代码中axis客户端,如例程6所示。
例程6 axis客户端代码
package com.hellking.study.webservice;
import org.apache.axis.client.Call;
import org.apache.axis.client.Service;
。。。
/**
*调用需要验证的Web服务
*/
public class AuthClient
{
static final double salary=5000;
public static void main(String [] args)
{
try {
//服务端的url,需要根据情况更改。
String endpointURL = "http://localhost:8080/axis/services/PersonalTaxService?wsdl";//
// Web服务端点地址
Service service = new Service();
Call call = (Call) service.createCall();
call.setTargetEndpointAddress( new java.net.URL(endpointURL) );
call.setOperationName( new QName("PersonalTaxService", "getTax") );//设置操作的名称。
//由于需要认证,故需要设置调用的用户名和密码。
call.getMessageContext().setUsername("hellking");//设置用户名。
call.getMessageContext().setPassword("simplewebservices");//设置密码
call.addParameter( "op1", XMLType.XSD_DOUBLE, ParameterMode.IN );//参数的类型
call.setReturnType( XMLType.XSD_DOUBLE );//返回的数据类型
Double ret = (Double) call.invoke( new Object [] { new Double(salary) });//执行调用
System.out.println("使用HTTP协议来作为Web服务的传输协议!");
System.out.println("已经成功调用。请参看服务端的输出!");
System.out.println("输入工资"+salary+"元,应交个人所得税:"+ret);
。。。
}
}
|
可以看出,通过使用call.getMessageContext().setUsername("hellking")来设置调用的用户名,通过setPassword来设置调用的密码。通过上面的代码可以看出,除了为Call对象设置的用户名和密码外,其它的代码和不使用访问控制时一样。
下面我们看看它的运行结果:
使用HTTP协议来作为Web服务的传输协议!
已经成功调用。请参看服务端的输出!
输入工资5000.0元,应交个人所得税:445.0
|
从输出可以看到,客户端成功通过了验证,并且返回了正确的结果。
使用axis的Handler进行访问控制
axis为Web服务的访问控制提供了相关的配置描述符,并且提供了一个访问控制的简单Handler(关于Handler的详细介绍见" J2EE Web服务开发系列之六: 使用Handler来增强Web服务的功能")。默认情况下,你只要在配置描述符中添加用户,然后在Web服务器的部署描述符中自动允许的角色即可。
首先在axis的配置文件users.lst(位于WEB-INF目录下)中添加一个用户,如"axisuser pass",表示用户名为axisuser,密码为pass。然后把案例的Web服务重新部署,在server-config.wsdd中添加例程7所示的部署代码。
例程7 重新部署PersonalTaxService
<service name="PersonalTaxService2" provider="java:RPC">
<parameter name="allowedMethods" value="*"/>
<parameter name="className" value="com.hellking.study.webservice.PersonalTaxService"/>
<parameter name="allowedRoles" value="axisuser"/>
<requestFlow>
<handler name="authen"
type="java:org.apache.axis.handlers.SimpleAuthenticationHandler"/>
</requestFlow>
</service>
|
在这个部署描述符中,指定PersonalTaxService2服务只能被axisuser访问,要想使访问控制生效,还必须把SimpleAuthenticationHandler添加到请求Handler链中。
你只要修改AuthClient代码的服务端点URL和访问用户名、密码,就可以测试新的Web服务了,如例程8所示。
例程8 HandlerAuthClient
public class HandlerAuthClient
{
。。。
String endpointURL = "http://localhost:8080/axis/services/PersonalTaxService2?wsdl";
。。。
call.getMessageContext().setUsername("axisuser");//axis中的用户名。
call.getMessageContext().setPassword("pass");//密码
。。。
|
执行HandlerAuthClient,能够顺利访问Web服务;如果修改用户名或者密码,那么就不能访问,这说明Axis的Handler对Web服务的访问权限进行了有效的控制。
使用Servlet过滤器(Filter)进行访问控制 Axis的Web服务端本质上是以Servlet方式在运行,所有我们完全可以在Web应用上部署一个Servlet过滤器,通过此过滤器来达到访问控制的效果。
Web应用中的过滤器截取从客户端进来的请求,然后进行一系列处理,最后把请求发送到目标Servlet。过滤器的工作原理如下图所示。
图1 过滤器工作原理
过滤器可以说是外部进入Web服务器的第一道关,它能决定请求是否继续向前转发,也能对请求中的信息进行处理。如果过滤器用于对Web服务进行访问控制,那么它能根据客户端信息决定目标的服务是否能调用成功。
将要开发的过滤器将根据客户端IP地址进行过滤,如果客户端的IP地址在限制范围中,那么就不能访问目标的Web服务。过滤器部分代码如下。
例程9 用过滤器限制Web服务的访问权限
package com.hellking.study.webservice;
import javax.servlet.FilterChain;
。。。
public class WebServicesFilter implements Filter
{
//没有权限访问的IP地址
static final String[] deniedIPList=new String[]{
"123.201",
"192.168",
"25.46",
"124.0.0.1"
};
public boolean isIPDenied(String ipAddr)
{
…
}
//过滤处理的方法
public void doFilter(final ServletRequest req,final ServletResponse res,FilterChain chain)
throws IOException,ServletException
{
HttpServletRequest hreq = (HttpServletRequest)req;
HttpServletResponse hres = (HttpServletResponse)res;
//HttpSession session = hreq.getSession();
String clientIp=req.getRemoteAddr();
System.out.println("开始过滤。。。");
if(isIPDenied(clientIp))
{
//验证不成功,让用户登录。
throw new ServletException("无权限访问此Web服务!");
}
else
{
//验证成功,继续处理
chain.doFilter(req,res);
}
}
…
}
|
WebServicesFilter 过滤器限制了deniedIPList中指定的所有客户端。编写了过滤器后,需要在Web应用的部署描述符中指定使用此过滤器,并且把过滤器映射到目标URL上。当然,你也可以开发其它过滤器进行访问控制,比如Web服务客户端登录时,把登录信息保存在HTTP会话中,当客户端访问受限的资源时,读取HTTP会话中客户端信息以决定客户端是否有权限访问目标资源。
除了编写Servlet过滤器实现类外,还需要在web.xml中对它进行配置,并且把过滤器映射到要过滤的目标URI上。以下是过滤器的部署描述符。
例程10 部署过滤器
<filter>
<filter-name>WebServicesFilter</filter-name>
<filter-class>com.hellking.study.webservice.WebServicesFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>WebServicesFilter</filter-name>
<url-pattern>/services/*</url-pattern>
</filter-mapping>
|
url-pattern指定了过滤器要过滤的范围。"/services/*"表示以"/services"开始的URL将全部被过滤,这正是AxisServlet默认的URL。通过上面的配置,只要客户端调用axis Web服务,就会被WebServicesFilter过滤器过滤。如果客户端IP地址在过滤器的deniedIPList中,那么就不能访问目标服务。
使用SSL作为Web服务的传输协议
Web服务也可以使用SSL作为传输协议。虽然JAX-RPC并没有强制规定是否使用SSL协议,但在tomcat下使用HTTPS协议。
相关知识:
SSL由两个共同工作的协议组成:"SSL 记录协议"(SSL Record Protocol)和"SSL 握手协议"(SSL Handshake Protocol)。SSL 记录协议建立在可靠的传输协议(如TCP)之上,为高层协议提供数据封装、压缩、加密等基本功能的支持;SSL 握手协议建立在SSL记录协议之上,用于在实际的数据传输开始前,通信双方进行身份认证、协商加密算法、交换加密密钥等。
SSL握手协议包含两个阶段,第一个阶段用于建立私密性通信信道,第二个阶段用于客户认证。第一阶段是通信的初始化阶段,在此阶段,首先SSL要求服务器向浏览器出示证书;然后浏览器中的SSL软件发给服务器一个随机产生的传输密钥,此密钥由已验证过的公钥加密,随机产生的传输密钥是核心机密,只有客户的浏览器和此公司的Web服务器知道这个数字序列。第二阶段的主要任务是对客户进行认证,此时服务器已经被认证了。服务器方向客户发出认证请求消息。客户收到服务器方的认证请求消息后,发出自己的证书,并且监听对方回送的认证结果。而当服务器收到客户的证书后,给客户回送认证成功消息,否则返回错误消息。到此为止,握手协议全部结束。
要使用SSL协议,服务器至少有一个私有密匙和一个用于验证身份的证书。私有密匙在密匙交换算法中用到,证书将发送到客户端,以通知服务器端的身份。如果SSL服务器要验证客户端的身份,那么客户端必须也有自己的密匙库(包含私有密匙和证书)。JSSE中引入了信任库(truststore)的概念,它是用来保存证书的数据库。客户端或者服务器通过信任库来验证对方的身份。
在使用SSL前,必须确保系统安装了JSSE。JDK1.4版本默认以及安装了JSSE。如果没有安装,把下载安装好的jar文件拷贝到%JAVA_HOME%\ jre\lib\ext目录下。这样,就安装好了JSSE的运行环境。
下面我们使用JDK自带的工具创建密匙库和信任库。
1)通过使用一下的命令来创建服务器端的密匙库。
keytool -genkey -alias hellking -keystore server.keystore -keyalg RSA
输入keystore密码: changeit
您的名字与姓氏是什么?
[Unknown]: hellking-Server
您的组织单位名称是什么?
[Unknown]: huayuan
您的组织名称是什么?
[Unknown]: huayuan
您所在的城市或区域名称是什么?
[Unknown]: beijing
您所在的州或省份名称是什么?
[Unknown]: beijing
该单位的两字母国家代码是什么
[Unknown]: cn
CN=chen ya qiang, OU=huayuan, O=huayuan, L=beijing, ST=beijing, C=cn 正确吗?
[否]: y
输入<hellking>的主密码
(如果和 keystore 密码相同,按回车):
|
以上命令执行完成后,将获得一个名为server.keystore的密匙库。
2)生成客户端的信任库。首先输出RSA证书:
keytool -export -file test_axis.cer -storepass changeit -keystore server.keystore
|
然后把RSA证书输入到一个新的信任库文件中。这个信任库被客户端使用,被用来验证服务器端的身份。
keytool -import -file test_axis.cer -storepass changeit -keystore client.truststore -alias serverkey -noprompt
|
3)创建客户端密匙库。重复步骤1,创建客户端的密匙库。也可以使用以下命令来完成:
keytool -genkey -dname " CN=hellking-Client, OU=tsinghua, O=tsinghua, L=BEIJING, S=BEIJING, C=CN"
-storepass changeit -keystore client.keystore -keyalg RSA -keypass changeit
|
4)生成服务器端的信任库。
keytool -export -file test_axis.cer -storepass changeit -keystore client.keystore
keytool -import -file test_axis.cer -storepass changeit -keystore server.truststore -alias clientkey -noprompt
|
生成了密匙库和信任库,我们把服务器端的密匙库(server.keystore)和信任库(server.truststore)拷贝到Tomcat的某个目录。
下面需要更改Tomcat的配置文件(server.xml),增加一下部署描述符:
例程11 为Tomcat配置SSL协议。
<Connector port="8443"
maxThreads="150" minSpareThreads="25" maxSpareThreads="75"
enableLookups="false" disableUploadTimeout="true"
acceptCount="100" debug="0" scheme="https" secure="true"
clientAuth="true" keystoreFile="K:\jakarta-tomcat-5.0.16\server.keystore" keystorePass="changeit"
truststoreFile="K:\jakarta-tomcat-5.0.16\server.truststore" truststorePass="changeit"
sslProtocol="TLS" />
|
clientAuth参数制定服务器是否要验证客户端证书,如果指定为true,那么客户端必须拥护服务器端可信任的证书后服务器才能响应客户端;如果指定为false,那么服务器不需要验证客户端的证书。
在此,我们又把PersonalTaxService部署一次,在server-config.wsdd中添加如下部署代码。
<service name="PersonalTaxService3" provider="java:RPC">
<parameter name="allowedMethods" value="*"/>
<parameter name="className" value="com.hellking.study.webservice.PersonalTaxService"/>
</service>
|
最后我们需要修改客户端调用程序,如例程12所示。
例程12 SSL客户端调用程序
package com.hellking.study.webservice;
….
public class SSLAuthClient
{
static final double salary=5000;
public static void main(String [] args)
{
try {
//服务端的url,注意使用了SSL协议后,前缀是https。
String endpointURL = "https://localhost:8443/axis/services/PersonalTaxService3?wsdl";
….
//由于使用了证书数字证书,所以不使用用户名和密码验证。
//call.getMessageContext().setUsername("hellking");。
//call.getMessageContext().setPassword("simplewebservices");
….
Double ret = (Double) call.invoke( new Object [] { new Double(salary) });
System.out.println("使用SSL协议来作为Web服务的传输协议!");
System.out.println("已经成功调用。请参看服务端的输出!");
System.out.println("输入工资"+salary+"元,应交个人所得税:"+ret);
} catch (Exception e) {
e.printStackTrace();
}
}
}
|
最后使用一下的命令来执行客户端程序:
set AXIS_LIB=K:\jakarta-tomcat-5.0.16\webapps\axis\WEB-INF\lib
SET CLASSPATH=.;%CLASSPATH%;%AXIS_LIB%\wsdl4j.jar.jar;%AXIS_LIB%\axis.jar;%AXIS_LIB%\jaxrpc.jar;%AXIS_LIB%
\saaj.jar;%AXIS_LIB%\commons-discovery.jar;%AXIS_LIB%\commons-logging.jar
java -Djavax.net.ssl.keyStore=client.keystore \
-Djavax.net.ssl.keyStorePassword=changeit \
-Djavax.net.ssl.trustStore=client.truststore \
com.hellking.study.webservice.SSLAuthClient
|
参数解释:通过-Djavax.net.ssl.keyStore来指定客户端密匙库,-Djavax.net.ssl.trustStore来指定客户端信任库。
最后的输出结果如下:
使用SSL协议来作为Web服务的传输协议!
已经成功调用。请参看服务端的输出!
输入工资5000.0元,应交个人所得税:445.0
|
总结、下一步
使用Web应用模型中基本认证方法来配置Web服务的安全访问以及使用axis的Handler、Servlet的过滤器来进行访问控制,它们都是一些轻量级控制Web服务安全解决方案,因为都是把未加密的信息在网络上传播。使用了SSL作为传输协议后,信息在网络上是加密传输的,但对资源消耗多,并且对客户端要求严格(必须支持SSL),不能满足Web服务访问的便利性。下一部分将介绍怎么在安全性要求高的环境下在axis开发工具里实现WS-Security。
下载样例代码在此。