在Web Service标准规范中,安全性主要通过Soap Header来保证Web Service的授权使用,通过SSL(CA认证方式)来保证传输层数据的加密,防止网络侦听(用SSL而不用WS-Security是因为性能,虽然WS-Security有着很多SSL所无法做到的特性:不基于传输层,保障非点对点的安全传输,部分加密等等)。
但以上两种方法还是比较“重”,对计算资源与网络带宽占用比较高,不太适合轻量级应用,特别是我们的应用运行在客户的安全内网,对于授权使用与数据加密要求不高,因此可以在初期采用比较简单的方式来实现授权使用与数据加密。
在兼顾安全性,同时又不能占用太多系统资源的前提下,决定采用唯一会话ID的方式来解决问题。
首先,所有调用客户端都要在参数中打包一个调用者对象,并根据其中内容得到唯一会话ID。服务端在收到调用者对象后,根据其中的内容,也计算一个唯一会话ID,如果与客户端发来的ID完全相同,则认为调用者是合法,参数也是完整的。
如有可能,则保存此调用者ID到缓存或者数据库中,以后每次调用都要先判断此调用者ID是否已经存在,如果已经存在,则视同重复调用,则服务端抛弃此调用申请,或者返回“重复调用”出错信息。
对于重要的参数,可以在双方约定一个加解密算法单独进行处理,以保证数据安全性。
一、创建web services 工程(XFire),和平时的一样。
二、加入身份验证功能
1、首先编写服务端验证类,继承AbstractHandler类
package test;
importorg.codehaus.xfire.MessageContext;
importorg.codehaus.xfire.handler.AbstractHandler;
import org.jdom.Element;
public classAuthenticationHandler extends AbstractHandler {
public voidinvoke(MessageContext cfx) throws Exception {
if (cfx.getInMessage().getHeader() == null){
throw neworg.codehaus.xfire.fault.XFireFault("请求必须包含验证信息",
org.codehaus.xfire.fault.XFireFault.SENDER);
}
Element token =cfx.getInMessage().getHeader().getChild(
"AuthenticationToken");
if (token == null) {
throw new org.codehaus.xfire.fault.XFireFault("请求必须包含身份验证信息",
org.codehaus.xfire.fault.XFireFault.SENDER);
}
String username =token.getChild("Username").getValue();
String password =token.getChild("Password").getValue();
try {
// 进行身份验证 ,只有abcd@1234的用户为授权用户
if(username.equals("abcd") &&password.equals("1234"))
// 这语句不显示
System.out.println("身份验证通过");
else
throw new Exception();
} catch (Exception e) {
throw neworg.codehaus.xfire.fault.XFireFault("非法的用户名和密码",
org.codehaus.xfire.fault.XFireFault.SENDER);
}
}
}
2、Client构造授权信息
package test;
importorg.codehaus.xfire.MessageContext;
importorg.codehaus.xfire.handler.AbstractHandler;
import org.jdom.Element;
public classClientAuthenticationHandler extends AbstractHandler {
private String username = null;
private String password = null;
public ClientAuthenticationHandler() {
}
public ClientAuthenticationHandler(Stringusername,String password) {
this.username = username;
this.password = password;
}
public void setUsername(String username) {
this.username = username;
}
public void setPassword(String password) {
this.password = password;
}
public void invoke(MessageContext context)throws Exception {
//为SOAP Header构造验证信息
Element el = newElement("header");
context.getOutMessage().setHeader(el);
Element auth = newElement("AuthenticationToken");
Element username_el = newElement("Username");
username_el.addContent(username);
Element password_el = newElement("Password");
password_el.addContent(password);
auth.addContent(username_el);
auth.addContent(password_el);
el.addContent(auth);
}
}
3、修改services.xml为web services绑定Handler
4、新建一个类ClientTest,用来测试
package test;
importjava.lang.reflect.Proxy;
importjava.net.MalformedURLException;
importorg.codehaus.xfire.client.*;
importorg.codehaus.xfire.service.Service;
importorg.codehaus.xfire.service.binding.ObjectServiceFactory;
public class ClientTest {
/**
* @param args
*/
public static voidmain(String[] args) {
// TODO Auto-generated method stub
try {
Service serviceModel = newObjectServiceFactory().create(IHello.class);
IHello service = (IHello) newXFireProxyFactory().create(serviceModel,
"http://dracom-d1514b82:8080/web_services3/services/Hello");
XFireProxy proxy =(XFireProxy)Proxy.getInvocationHandler(service);
Client client = proxy.getClient();
//发送授权信息
client.addOutHandler(newClientAuthenticationHandler("abcd","1234"));
//输出调用web services方法的返回信息
System.out.println(service.getMessage("你好aaa"));
} catch (MalformedURLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
三、这样我们就完成了编码,下面启动web services,运行客户端代码,本文为abcd@1234位授权用户,
使用abcd@1234,可以正常访问web services,如果用错误帐号,则会有以下异常:
Exception in thread"main" org.codehaus.xfire.XFireRuntimeException: Could not invokeservice.. Nested exception is org.codehaus.xfire.fault.XFireFault: 非法的用户名和密码
org.codehaus.xfire.fault.XFireFault:非法的用户名和密码
atorg.codehaus.xfire.fault.Soap11FaultSerializer.readMessage(Soap11FaultSerializer.java:31)
atorg.codehaus.xfire.fault.SoapFaultSerializer.readMessage(SoapFaultSerializer.java:28)
atorg.codehaus.xfire.soap.handler.ReadHeadersHandler.checkForFault(ReadHeadersHandler.java:111)
atorg.codehaus.xfire.soap.handler.ReadHeadersHandler.invoke(ReadHeadersHandler.java:67)
atorg.codehaus.xfire.handler.HandlerPipeline.invoke(HandlerPipeline.java:131)
atorg.codehaus.xfire.client.Client.onReceive(Client.java:406)
atorg.codehaus.xfire.transport.http.HttpChannel.sendViaClient(HttpChannel.java:139)
atorg.codehaus.xfire.transport.http.HttpChannel.send(HttpChannel.java:48)
atorg.codehaus.xfire.handler.OutMessageSender.invoke(OutMessageSender.java:26)
atorg.codehaus.xfire.handler.HandlerPipeline.invoke(HandlerPipeline.java:131)
at org.codehaus.xfire.client.Invocation.invoke(Invocation.java:79)
atorg.codehaus.xfire.client.Invocation.invoke(Invocation.java:114)
atorg.codehaus.xfire.client.Client.invoke(Client.java:336)
atorg.codehaus.xfire.client.XFireProxy.handleRequest(XFireProxy.java:77)
atorg.codehaus.xfire.client.XFireProxy.invoke(XFireProxy.java:57)
at$Proxy0.getMessage(Unknown Source)
attest.ClientTest.main(ClientTest.java:24)
如果不在CientTest加以下Heade则会有以下异常:
XFireProxy proxy =(XFireProxy)Proxy.getInvocationHandler(service);
Client client = proxy.getClient();
//发送授权信息
client.addOutHandler(newClientAuthenticationHandler("abcd1","1234"));
信息: Fault occurred!
org.codehaus.xfire.fault.XFireFault:请求必须包含验证信息
attest.AuthenticationHandler.invoke(AuthenticationHandler.java:11)
atorg.codehaus.xfire.handler.HandlerPipeline.invoke(HandlerPipeline.java:131)
atorg.codehaus.xfire.transport.DefaultEndpoint.onReceive(DefaultEndpoint.java:64)
atorg.codehaus.xfire.transport.AbstractChannel.receive(AbstractChannel.java:38)
atorg.codehaus.xfire.transport.http.XFireServletController.invoke(XFireServletController.java:304)
atorg.codehaus.xfire.transport.http.XFireServletController.doService(XFireServletController.java:129)
atorg.codehaus.xfire.transport.http.XFireServlet.doPost(XFireServlet.java:116)
atjavax.servlet.http.HttpServlet.service(HttpServlet.java:710)
atjavax.servlet.http.HttpServlet.service(HttpServlet.java:803)
atorg.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:290)
atorg.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
atorg.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:230)
atorg.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:175)
atorg.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:128)
atorg.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:104)
atorg.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:109)
atorg.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:261)
atorg.apache.coyote.http11.Http11Processor.process(Http11Processor.java:844)
atorg.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:581)
atorg.apache.tomcat.util.net.JIoEndpoint$Worker.run(JIoEndpoint.java:447)
atjava.lang.Thread.run(Thread.java:619)
SSL(SecureSocket Layer)是一种通信交互协议,由Netscape公司在1994年制定,主要目的就是确保在web 服务器和浏览器之间数据传输安全。通过简单设置应用服务器与客户端即可实现。
Invoker对象唯一标识一个客户端对财政非税征管系统服务端接口方法的一次调用。
名称 |
类型 |
说明 |
是否可空 |
sessionId |
String |
唯一会话ID, MD5(时间戳+年度+区划编码+单位编码+用户编码+用户密码) |
否 |
timestamp |
String |
时间戳,为18位数字组成的字符串,前13位为发起请求时间的13位long类型值(1970年1月1日起直至当前时间的以毫秒为单位的时间间隔的值),后5位为随机数。 |
否 |
sysYear |
String |
年度 |
否 |
districtCode |
String |
区划编码 |
否 |
districtName |
String |
区划名称 |
否 |
unitCode |
String |
单位编码 |
否 |
unitName |
String |
单位名称 |
否 |
userCode |
String |
用户编码 |
否 |
userName |
String |
用户名称 |
否 |
客户端调用Web Service接口时动态生成,只使用一次,用以唯一标识调用者身份,并防止由于网络或者软硬件故障造成的重复调用。
sessionId=MD5(时间戳+年度+区划编码+单位编码+用户编码+用户密码)。
参数说明:
为18位数字组成的字符串,前13位为发起请求或应答时间的13位long类型值(1970年1月1日起直至当前时间的以毫秒为单位的时间间隔的值),后5位为随机数。
为客户端登录时选择的系统年度。
为客户端登录时选择的区划编码值。
为客户端登录时选择的收费单位值。
为客户端登录时选择的用户编码值。
为客户端登录时选择的用户密码值。
如果要进一步加强安全性,双方可以约定在唯一会话ID中加入调用参数完整性、安全性的校验,即在sessionId的MD5中加入此接口要求的其他参数值进行一并计算。
MD5算法如下(包括Java与C#):
//Java MD5算法
private staticString[] HexCode ={ "0", "1", "2", "3","4", "5", "6", "7", "8","9", "A", "B", "C", "D","E", "F" };
public static String MD5(String s){
MessageDigest md = null;
try {
md =MessageDigest.getInstance("MD5");
} catch (Exception e) {
e.printStackTrace();
return null;
}
md.update(s.getBytes());
returnbyteArrayToHexString(md.digest());
}
private static StringbyteToHexString(byte b) {
int n = b;
if (n < 0) {
n = 256 + n;
}
int d1 = n / 16;
int d2 = n % 16;
return HexCode[d1] + HexCode[d2];
}
private static String byteArrayToHexString(byte[] b){
String result = "";
for (int i = 0; i < b.length; i++) {
result = result + byteToHexString(b[i]);
}
return result;
}
//C# MD5算法
private staticstring[] HexCode ={ "0", "1", "2", "3","4", "5", "6", "7", "8","9", "A", "B", "C", "D","E", "F" };
public staticstring MD5(string s)
{
MD5 md = newMD5CryptoServiceProvider();
byte[] ss =md.ComputeHash(UnicodeEncoding.UTF8.GetBytes(s));
return byteArrayToHexString(ss);
}
private static string byteToHexString(byte b) {
int n = b;
if (n < 0) {
n = 256 + n;
}
int d1 = n / 16;
int d2 = n % 16;
return HexCode[d1] + HexCode[d2];
}
private static String byteArrayToHexString(byte[] b){
String result = "";
for (int i = 0; i < b.Length; i++){
result = result +byteToHexString(b[i]);
}
return result;
}