You are currently browsing the category archive for the 'web service' category.

一、NCSA Server
二、CERN httpd
三、Netscape Server
四、IIS
一、设置NCSA Server或Apache Server以使用CGI

NCSA Server的CGI1.1只允许用下列两种方式激活用户服务器上的脚本:ScriptAlias指令和AddType指令。这两条指令都放在srm.conf文件中,该文件一般在用户的服务器根目录的conf目录中。

ScriptAlias指令告诉服务器该目录中的所有文件都是脚本或者是服务器作为CGI文件执行的程序。该方法能保证用户的CGI程序在特定位置。AddType指令允许用户告诉服务器任何具有指定前缀的文件都是可执行文件。如果希望将CGI程序放在服务器中任何地方的话该指令即很有用。

1、ScriptAlias指令

ScriptAlias指令位于Server Resource Map文件(srm.conf)中,程序内容例如下:

DocumentRoot /usr/local/etc/httpd/htdocs
UserDir public_html
REdirect /HTTPD/ http://www.server.com/
Alias /icons/ /usr/local/etc/httpd/icons/
ScriptAlias /cgi-bin/ /usr/local/etc/httpd/cgi-bin/
DirectoryIndex index.html index.shtml index.cgi
IndexOptions FancyIndexing
AddIcon /icons/movie.gif .mpg .qt
AddIcon /icons/menu.gif
AddIcon /icons/blank.xbm
DefaultIcon /icons/unknown.xbm

IndexIgnore */.??* *~ *# */HEADER* */README*
DefaultType text/plain
AccessFileName .htaccess
srm.conf文件允许用户根据自己系统需要设置HTTP Server。它允许用户告诉服务器用户的主页在什么地方,目录中的哪个文档是索引文档,如果不存在索引文件的话将装载什么图片文件以显示文件的类型,等等。srm.conf及其他配置文件的说明可查阅http://www.nease.net/tppmsgs/msgs0.htm#34

2、AddType指令
AddType指令是执行CGI程序的另一种方式,它是在srm.conf文件中加入下列行:

AddType application/x-httpd-cgi.cgi

在自己的系统中设置了该指令后,任何在服务器控制范围内的扩展名为.cgi的文件都会被作为CGI程序执行而不是作为文本文件阅读。这意味着用户可以在他的个人目录中创建脚本并能执行它。但是如果脚本写得不正确,就可能导致对文件系统、口令文件等的不同类型伤害。

AddType指令可以扩展为允许扩展名不是.cgi的程序同样被执行。大家经常会看见以.pl(Perl脚本的常见扩展名)或.sh(Bourne Shell脚本的常见扩展名)结尾的脚本。如果想支持其他扩展名的程序,只需简单地将它们加入AddType指令中,如下所示:

AddType application/x-httpd-cgi .cgi .pl .sh

3、访问配置文件
为了支持CGI程序的执行必须多加入一条指令。在Server Root/conf目录中是一个名为access.conf的配置文件。该文件允许用户设置ServerRoot下的哪个目录能够访问的全局限制,甚至允许用户控制哪些站点可以访问这些目录。下面是access.conf文件的一个例子:

<Directory /usr/local/etc/httpd/cgi-bin>
Options INdexes Exec CGI
</Directory>

<Directory /usr/local/etc/httpd/htdocs>
Options Indexes FollowSymLinks
AllowOverride All
<Limit GET>
order allow,deny
allow from all
</Limit>
</Directory>
Exec CGI表示允许执行该目录中的CGI脚本。Options Indexes FollowSymLinks表示允许索引(显示某文件夹中的内容)并能够遵循符号链(这就意味着在ServerRoot之外的文件也能被访问)。AllowOverride指令允许用户决定哪个指令可以被目录的.htaccess文件覆盖。中设置了对该目录中允许使用GET方式的限制。在HTTPD中,部分指令的选项是GET、POST和PUT(目前PUT尚未实现)。order allow,deny一行告诉服务器先找allow行再找deny行。下一行则是告诉服务器允许所有站点访问该目录中的页面。

二、设置CERN HTTP服务器以使用CGI
CERN HTTP服务器(也称为W3C HTTP服务器)仅需要编辑/etc/httpd.conf文件即可支持在服务器内使用CGI程序。这个指令类似于NCSA Server使用的指令:

Exec /url-prefix/* /physical-path/*

其中/url-prefix/定义了客户能看见的路径,而/physical-path/则是包含脚本的目录的实际路径。

三、设置Netscape以使用CGI
首先要启动管理服务器。以root身份登录,并运行/ServerRoot/admserv/start-admin,缺省端口为81。然后启动浏览器连接。在Netscape Admin页面中,单击Select URL Mapping,从弹出窗口中选择Map a URL to a Local Directory。然后单击Select CGI and Server Parsed HTML,从弹出窗口中选择Activate CGI as a File Type。现在即可单击Browse Files并选择欲激活的目录。选择完目录后,单击I’d Like to Activate CGI as a File Type。在ServerRoot中即会看到Conf目录中的obj.conf配置文件中已加入了下列行:

NameTrans form=”/cgi-bin” fn=”pfx2dir” dir=”/usr/local/web/cgi-bin” name=”cgi”

name=cgi调用了下列行:

<Object name=”cgi”>
ObjectType fn=”force-type” type=”magnus-internal/cgi”
Service fn=”send-cgi”
</Object>
它告诉服务器此为一个CGI目录,其中的所有文件都将用Netscape内部提供的CGI执行。


四、设置IIS以使用CGI

在IIS上运行CGI有十个简单的步骤:

1)安装Internet Service Manager。
2)从列表中选择WWW Servive。
3)选择Properties/Service Properties命令。
4)单击Directories标签。
5)单击Add按钮。
6)指定自己的cgi-bin目录的完整路径(例如,c:\webfiles\scripts)。
7)使用/scripts作为目录别名。
8)选中Execute检查框。
9)单击OK保存修改。
10)将自己的CGI程序放在c:\webfiles\scripts中并在HTML中作为/scripts/someprogram.exe引用。
在使用IIS时经常出现的问题与设置IIS没太大关系而是和基本的操作系统功能有很大关系。IIS与底层的操作系统联系很紧密,即使已经设置为服务,Web服务器基本上是作为应用程序来运行的,通常只有一个用户安全环境,Web服务器能访问到的与Web服务器下的CGI程序能访问到的内容几乎没什么不同(这类似于UNIX环境,在UNIX环境下,很重要的一点就是不要将Web服务器作为root来运行)。IIS的工作很像一个扩展的文件系统。每个用户有自己的权限。CGI程序在执行该程序的访问者的用户安全环境中运行。对于未验证的页面,这就是缺省提供的“无名的”用户,而对验证的页面,安全环境就像用户位于服务器控制台前手工运行该程序一样。使大部分初学者犯错误的正是这种额外的安全层次。

IIS管理员最常抱怨的一个错误信息是”The Application misbehaved by not returning a complete set of headers”。错误消息接下来列出服务器接收到的头标–一般是个空的清单。这种讨厌的不明确的错误有一个直接的原因,不过这个原因与CGI脚本的错误操作没有一点关系。如果因为某种原因某个CGI脚本不能运行,它就不能产生任何头标。IIS将错误的责任推在脚本身上,实际上却几乎总是服务器管理员的错。CGI脚本需要访问系统DLLs、系统的临时目录以及它们使用的任何其他资源。如果该脚本是按静态约束进行编译的,那么除非所有组件均可用,否则操作系统不会装载该程序的。如果系统管理员锁紧了安全级使得脚本不能装载它的DLLs,那么脚本就不能运行。当脚本不能运行时,它也就不产生任何头标了(或者其他的输入),从而导致出现本段开头引用的错误消息。

如果管理员是在一个安全目录中运行脚本的(安全目录即是一个需要单独用户验证才能访问的目录),那么每个可能访问系统的用户都必须有下列安全权限。如果是无名地运行脚本,那么只有无名用户需要这些权限:

.对%systemroot%system(一般为c:\winnt\system)的读权限
.对%systemroot%system32(一般为c:\winnt\system32)的读权限
.对临时目录(一般为c:\temp)的修改权限
.对Web根的读权限
.对CGI目录的修改权限
如果在有了这些访问权限之后仍然出问题,可以进一步临时给特殊的用户帐号Everyone赋予这些目录的修改权限。如果问题解决了,就可以认定是少了一个步骤(或一个用户)。纠正问题然后慢慢回收权限直至服务器重新安全。







一.系统环境
linux操作系统kernel2.4.2,安装gsoap2.6到目录/usr/local/gsoap
二.gSOAP的简要使用例子
下面是一个简单的例子,实现一个加法运算的WebService,具体功能是cli端输入num1和num2,server端返回一个num1和num2相加的结果sum。


1. 首先,我们需要做的是写一个函数声明文件,来定义接口函数ns__add,文件名字为add.h,内容如下:


//gsoap ns service name: add
//gsoap ns service namespace: http://mail.263.net/add.wsdl
//gsoap ns service location: http://mail.263.net
//gsoap ns service executable: add.cgi
//gsoap ns service encoding: encoded
//gsoap ns schema namespace: urn:add


int ns__add( int num1, int num2, int* sum );


2. 然后我们需要创建文件Makefile,从而利用gsoapcpp2工具由add.h生成一些.xml文件、.c文件和.h文件,这些文件均为自动生成,Makefile的内容如下:


GSOAP_ROOT=/usr/local/gsoap
WSNAME=add
CC=g++ -g -DWITH_NONAMESPACES
INCLUDE=-I $(GSOAP_ROOT)
SERVER_OBJS=$(WSNAME)C.o $(WSNAME)Server.o stdsoap2.o
CLIENT_OBJS=$(GSOAP_ROOT)/env/envC.o $(WSNAME)ClientLib.o stdsoap2.o
ALL_OBJS=${WSNAME}server.o $(WSNAME)C.o $(WSNAME)Server.o ${WSNAME}test.o ${WSNAME}client.o $(WSNAME)ClientLib.o


#总的目标
all:server


${WSNAME}.wsdl{WSNAME}.h
$(GSOAP_ROOT)/soapcpp2 -p$(WSNAME) -i -n -c ${WSNAME}.h


stdsoap2.o(GSOAP_ROOT)/stdsoap2.c
$(CC) -c $?


#编译一样生成规则的.o文件
$(ALL_OBJS):%.o:%.c
$(CC) -c $? $(INCLUDE)


#编译服务器端
server:Makefile ${WSNAME}.wsdl ${WSNAME}server.o $(SERVER_OBJS)
$(CC) ${WSNAME}server.o $(SERVER_OBJS) -o ${WSNAME}server


#编译客户端
client:Makefile ${WSNAME}.wsdl ${WSNAME}client.c ${WSNAME}test.c $(ALL_OBJS) stdsoap2.o
$(CC) ${WSNAME}test.o ${WSNAME}client.o $(CLIENT_OBJS) -o ${WSNAME}test


cl:
rm -f *.o *.xml *.a *.wsdl *.nsmap $(WSNAME)H.h $(WSNAME)C.c $(WSNAME)Server.c $(WSNAME)Client.c $(WSNAME)Stub.* $(WSNAME)$(WSNAME)Proxy.* $(WSNAME)$(WSNAME)Object.* $(WSNAME)ServerLib.c $(WSNAME)ClientLib.c $(WSNAME)server ns.xsd $(WSNAME)test


3.我们先来做一个server端,创建文件addserver.c文件,内容如下:


#include “addH.h”
#include “add.nsmap”


int main(int argc, char **argv)
{
int m, s; /* master and slave sockets */
struct soap add_soap;
soap_init(&add_soap);
soap_set_namespaces(&add_soap, add_namespaces);
if (argc < 2)
{
printf(”usage: %s <server_port> \n”, argv[0]);
exit(1);
}
else
{
m = soap_bind(&add_soap, NULL, atoi(argv[1]), 100);
if (m < 0)
{
soap_print_fault(&add_soap, stderr);
exit(-1);
}
fprintf(stderr, “Socket connection successful: master socket = %d\n”, m);
for ( ; ; )
{
s = soap_accept(&add_soap);
if (s < 0)
{
soap_print_fault(&add_soap, stderr);
exit(-1);
}
fprintf(stderr, “Socket connection successful: slave socket = %d\n”, s);
add_serve(&add_soap);//该句说明该server的服务
soap_end(&add_soap);
}
}
return 0;
}
//server端的实现函数与add.h中声明的函数相同,但是多了一个当前的soap连接的参数
int ns__add(struct soap *add_soap, int num1, int num2, int *sum)
{
*sum = num1 + num2;
return 0;
}


4.让我们的server跑起来吧:
shell>make
shell>./addserver 8888
如果终端打印出“Socket connection successful: master socket = 3”,那么你的server已经在前台run起来了,应该是值得高兴的?。
打开IE,键入http://本机IP:8888,显示XML,服务已经启动,终端打印出“Socket connection successful: slave socket = 4”,表示服务接收到了一次soap的连接。


5.让我们再来写个客户端(这个只是将soap的客户端函数封装一下,具体的调用参见下面的addtest.c),创建文件addclient.c,内容如下:


#include “addStub.h”
#include “add.nsmap”
/**
* 传入参数:server:server的地址
* num1,num2:需要相加的数
* 传出参数:sum:num1和num2相加的结果
* 返回值:0为成功,其他为失败
*/
int add( const char* server, int num1, int num2, int *sum )
{
struct soap add_soap;
int result = 0;
soap_init(&add_soap);
soap_set_namespaces(&add_soap, add_namespaces);


//该函数是客户端调用的主要函数,后面几个参数和add.h中声明的一样,前面多了3个参数,函数名是接口函数名ns__add前面加上soap_call_
soap_call_ns__add( &add_soap, server, “”, num1, num2, sum );
if(add_soap.error)
{
printf(”soap error:%d,%s,%s\n”, add_soap.error, *soap_faultcode(&add_soap), *soap_faultstring(&add_soap) );
result = add_soap.error;
}
soap_end(&add_soap);
soap_done(&add_soap);
return result;
}


6.我们最终写一个可以运行的客户端调用程序,创建文件addtest.c,内容如下:


#include <stdio.h>
#include <stdlib.h>


int add(const char* server, int num1, int num2, int *sum);


int main(int argc, char **argv)
{
int result = -1;
char* server=”http://localhost:8888“;
int num1 = 0;
int num2 = 0;
int sum = 0;
if( argc < 3 )
{
printf(”usage: %s num1 num2 \n”, argv[0]);
exit(0);
}


num1 = atoi(argv[1]);
num2 = atoi(argv[2]);


result = add(server, num1, num2, &sum);
if (result != 0)
{
printf(”soap err,errcode = %d\n”, result);
}
else
{
printf(”%d+%d=%d\n”, num1, num2, sum );
}
return 0;
}


7.让我们的client端和server端通讯
shell>make client
shell>./addtest 7 8
当然,你的server应该还在run,这样得到输出结果7+8=15,好了,你成功完成了你的第一个C写的WebService,恭喜。
三.图示说明


四.要注意的问题
1. add.h文件前面的几句注释不能删除,为soapcpp2需要识别的标志
2. 接口函数的返回值只能是int,是soap调用的结果,一般通过soap.error来判断soap的连接情况,这个返回值没有用到。
3. 接口函数的最后一个参数为传出参数,如果需要传出多个参数,需要自己定义一个结构将返回项封装。
4. 在.h文件中不能include别的.h文件,可能不能生效,需要用到某些结构的时候需要在该文件中直接声明。
5. 如果客户端的调用不需要返回值,那么最后一个参数
五.参考文档
1.gsoap主页
http://gsoap2.sourceforge.net


2.跟我一起写Makefile
http://dev.csdn.net/develop/article/20/20025.shtm


3.Web Services: A Technical Introduction(机械工业出版社)
六.备注
192.168.18.233和192.168.18.234的/usr/local/gsoap目录下的3个需要的文件及一个env目录,不是编译安装的,是在别的地方编译好了直接copy过来的(实际编译结果中还有wsdl2h工具及其他一些文件,但是我们的实际开发中只是用到了这3个文件及env目录)。因为时间仓促,本人还没有时间研究编译的问题,相关细节可以查看参考文档1。
在192.168.18.233的/home/weiqiong/soap/sample目录下及192.168.18.234的/tmp/soap/sample目录下有本文讲到的加法运算的例子。

http://www.infoq.com/cn/news/2007/12/soapui-20


EviWare是一家专注于开源Web Service测试套件的瑞典公司,最近,他们在JavaPolis 2007会仪上发布了他们的2.0版soapUI产品。对发布产品的完整介绍可以在这里找到,用户手册在这里


这个工具提供了包含操作层面和模式层面的完整的WSDL覆盖程度分析到达每个元素的所有路径都被测试过了。 


它提供了测试重构能力,这可以:


……使[用户]可以随着WSDLs的更新重构[他们]所有的测试。测试重构甚至还考虑到了更新过的WSDLs会如何影响到您的XPath表达式。

soapUI的需求管理功能可以将测试用例和需求有机的结合起来,其中需求可以被直接键入或者导入。


在这次发布中,从一个数据源得到测试数据已经成为可能。EviWare增加了一个DataSink功能,从测试响应中得到的数据可以被添加到数据接收器(例如数据库,Excel或者csv文件等)中。


EviWare还引入了对基于用户名或X.509令牌的WS-Security 1.0 (2004年3月发布)规范的支持。这个发布可以测试身份认证、数字签名以及加密。 


该产品可以捕捉SOAP请求和响应来创建测试请求,测试用例和仿真的服务。 


当测试用例互相依赖时,该产品支持创建测试链来管理参数的输入输出。


soapUI 有多种版本,特别还提供了eclipse插件


Mike Jennings,一个北卡罗来纳大学教堂山分校的企业应用开发者,同时也是soapUI的用户,评价道



这个工具在我对代码的问题(如我的客户端软件可能设置不正确)进行调试时有帮助,并且有助于我识别其他用户的软件问题。这个客户端是免费使用的,并且是一个java webstart应用程序,所以它可以在任何系统中运行。


Rick Grehan上个月在InfoWorld已经发表了一篇5个Web Services测试工具的分析文章。上周,Mindreef发表了一篇易懂的手册,介绍了一个SOA测试方法论,这个手册他是与David Linthicum合著的。


查看英文原文:EviWare Releases Web Services Test Suite soapUI 2.0

http://www.cnblogs.com/wayfarer/category/95646.html

http://dev2dev.bea.com.cn/techdoc/2005032301.html


Web services测试对于开发和测试团队来说提出了一系列崭新的问题。JUnits可以创建用来测试Web service的一部分功能,但是确保充分验证所需的总体功能性,它却不能提供,同时它也会使更新数据值变得困难。还有许多需要被测试的Web service组件仍未找到合适的、更加通用的测试工具。由于Web service测试是一个在开发过程中很早就涉及到外部合作伙伴的领域,所以格外危险,可能很快就会变成一场恶梦。
  SOAPtest提供了一套完整的针对Web service的测试工具,使开发团队和(或)测试团队能够利用一个一致的工具,对从Web Service定义语言(WSDL)确认到无需自己构建和更新测试客户端的性能测试,来证明Web services是否合适。
  SOAPtest的一个主要特征是该工具支持大量数据源。生成测试数据可能是耗时的,并易于出错,但是SOAPtest工具有效地减轻了所有这些问题。SOAPtest支持来自任何可通过ODBC/JDBC连接的数据库的测试数据,例如.csv文件、内部表、Excel电子表格,或者任何上述类型文件的集合都支持。增加一个数据源是非常简单的,您只需要选择数据源的类型和位置。我使用的是指南中提供的Excel电子表格。
  首先,我运行一系列WSDL确认。WSDL通过描述服务和标识位置来控制对Web service的访问。屏幕界面相当直观,我能很快就验证一组WSDL文件。
  您也可以检查任何URL链接在WSDL中是否依然有效。最有趣的是该工具能够执行Web Services互操作性组织确认。该工具能够生成一份印象深刻的报告,报告可由开发和测试团队进行分析,并酌情分发给您的合作伙伴。
  该工具还支持WSDL比较和回归测试,使开发团队能够在其他测试之前轻松地获得并确认WSDL变化。
  一旦WSDL得到确认并生效,下一个关键步骤就是确保Web service操作在单元测试层次上的有用性。这是通过创建一组单元测试来完成的,按照逻辑划分每组单元测试都属于一组测试套件。SOAPtest支持正面测试(测试期望的场景)、反面测试(错误条件下的测试)和回归测试(执行改变确认)。利用该工具,测试数据可以全部或部分来自各种数据源,或者由工具快速生成。在数据生成方面,该工具具有大量的功能,并有能力根据用户定义的一系列规则来生成数据。但有一个缺点就是当我试图仅仅保存某个独立测试本身时,却不知道如何完成。
  当开发人员完成了单元测试,SOAPtest工具才能够提供创建实际场景测试的功能。在测试过程中,场景测试表现了业务场景的再现。场景测试可以从头创建,也可以通过一系列已经生成的单元测试与测试团队定义的附加测试相融合的联合体得到。
  SOAPtest提供了数据确认能力的范围。数据可以由元素确认,或者仅由那些您想比较的元素来确认。
  Web services的另一个关键方面是其异步特性。那些期望使用其他应用程序的传统异步响应的测试工具在处理Web services的这个典型方面时遇到了困难。SOAPtest提供了对那些使用Parlay、SCP和WS-Addressing通信协议的异步调用进行测试的功能。这很关键,因为大多数这些服务的性质不能直接得到确认或者响应。SOAPtest包含一台Tomcat服务器,当部署了结果回调异步模式时,Tomcat服务器使SOAPtest能够测试期望的“回调”响应。当使用了回调测试时,一个建议的更新是让Tomcat服务器自动启动。一组异步测试的结果如图1所示:



  当开发Web services时,另一个关键细节与安全的重要性有关。揭示Web services的本质特性需要安全考虑;因此,测试安全特性对任何Web service的确认测试都是不可缺少的。SOAPtest提供了一个加密工具,能够支持对消息的全部或者部分加密。该工具提供的加密标准包括三重DES、AES 128、AES 192和AES 256。这些数字代表密钥的位数。当使用加密测试时, 一定要安装JCE(Java密码术扩展)Unlimited Strength Jurisdiction Policy File,因为没有它测试就无法工作。
  SOAPtest所提供的其他关键安全特性包括XML签字工具、XML 验证工具和对用户名和SAML标志的支持。XML签字工具提供数字签名功能。如果您的Web service需要某种数字签名,该工具能用来验证其功能性。同样,XML验证工具允许用户进行加密/解密/验证数字签名消息,方法是利用公钥/私钥存储文件。支持的密钥存储文件格式包括JKS、PKCS12、BKS和UBER。
  安全声明标注语言(SAML)为交换安全信息提供了一个标准。SAML为Web services提供了一个交换认证和授权声明的方法。这赋予团队在整个组织中提供身份管理服务的机会。在出现一个失败之前,SOAPtest 支持SAML验证。
  如果您的Web service测试不具备上述测试的特征,SOAPtest将通过提供自定义脚本功能来提供最高的灵活性。脚本可使用Java、JavaScript或者Jython,以及支持文件导入的工具来编写。为了完成测试,脚本代码能够完成任意所需的其他测试逻辑。
在测试Web services时,另一个关键方面是确认性能需求和识别性能的界线。在Web service测试的工具中,SOAPtest相当独特,这是因为它在负载测试领域所提供的强大功能。
  要访问SOAPtest的负载测试工具包,从左侧导航面板中选择Load Test选项卡。现有的场景可以利用以前的功能测试,也可以生成新的测试场景。 该工具提供了通过约束相关机器来在整个网络中运行这些测试的能力。当您能够避免本地机器局限性的时候,这也使得负载水平显著地增加。
  通过创建用户配置文件,可以将功能测试和负载测试合为一体。每个配置文件由一个或者多个功能测试组成,并且能被加入到每个负载测试或者从每个负载测试中删除。我使用的配置文件是基于前面创建的正面和负面测试场景的。
  也可以延迟每个配置文件,以便更好地监视每个功能测试对负载的影响。配置文件比率可以通过使用与该场景相关的Profiles选项卡来调整。在整个计划的测试周期中,它以图形显示信息,允许对用户数量和每秒钟的点击数进行调整。很简单,在图的任何地方点击右键,创建一个点,然后把那个点移到合适的位置。每秒钟的点击数和用户的数量也可以动态地调整。
  我创建的配置文件以用户数量的缓慢增加为初始,随后每分钟都有显著增加,当测试进行到一半时,再缓缓减少。我也改变了我的用户配置文件比率,随着时间的变化来减少正面测试的数量。测试的持续时间也完全由用户决定,且它可以持续数天提供数据。我选择3分钟作为精确度。运行测试,结果如图2 所示。



  我们也能看到一个基于文本的图形摘要(参见图3)。



  很多操作系统统计数据都可以添加到这个测试工具中并用其进行监视。SOAPtest 支持添加SMNP、Windows Perfmon和JMS监视数据包。我使用的是默认的windows系统。
SOAPtest的报告根据所有测试结果生成。报告可以生成HTML、XML或者.csv 格式。

结束语
总之,SOAPtest 提供了一个跨越整个开发生命周期的有用的Web service测试资源。它的界面非常简单,对于开发者和测试者来说很容易掌握。它鼓励整个团队来共享测试,并且提供了能支持最小单元测试工作的测试套件,以扩充Web service负载测试。它支持多种数据源、复杂的测试场景创建和自动化测试。
异步的Web service确认和复杂的安全验证是把SOAPtest和其他测试产品彻底区别开的其他特征。

原文出处
http://www.sys-con.com/story/?storyid=47101&DE=1








 作者简介
Jason Snyder是架构专家,供职于Boston的CSC Consulting公司;作为首席架构师,他已经参与了多个J2EE开发项目。在软件开发、面向对象的设计和应用架构方面,他拥有10多年的丰富经验。


 http://www.cnblogs.com/oscarxie/archive/2007/09/21/760827.html


最近一直在做WebService的测试,考虑到手工测试的困难,所以特意去寻找好的测试工具,现在做一个整理。


1、.NET WebService Studio


这款工具出自微软内部,最大的优点是可视化很好,不用去看那些XML文件,WebService的基础内容就有XML,但是测试中Case过多,每次测试结果都去看XML文件,看一轮下来对个人的视力是个很大的损害。



从上图可以看到,操作上也很方便,只需要把Service部署到IIS后,在WSDL EndPoint中输入这个要测的Service的URL,点击Get按钮,就能把Service要输入的参数列表取出来,测试的时候只需要在输入参数的值,点击Invoke按钮,就可以得到结果,结果也是一样,一边为参数,一边为值,检查起来很方便。同时参数的类型也能显示出来。


但是缺点就来了,每一个Case都需要输入一次,不能做到测试驱动。这样如果有1000个Case,要输入1000个,效率比较低。


2、WebTest From VSTS


这个在VSTS For Testers读书笔记中介绍过,具体可以参见Mango的文章,http://blog.joycode.com/mango/archive/2007/02/28/94002.aspx,很好地实现了数据绑定,不过结果还是需要一条一条Check,不过WebTest已经提供了很好的验证规则,可以将预期的结果与测试结果作比较。不过顺便提下就是测试结果居然不能被拷贝出来,这个让我很郁闷。


3、新的问题


不过到这里还没有结束,前面说的Service都是可以部署到IIS里的,接口是Public的,



但是现在做的项目使用WCF Service,不是Public,不部署到IIS了,都是Host到系统的服务中。



这样上面的两款工具都不支持,那么考虑首先去找是否有适合的工具,不行的话就只能自己团队内部开发了。


4、SOATest


SOATest是由Parasoft出品的,原来叫作SOAPTest,它是使用WSDL通过描述服务和标识位置来控制对Web service的访问。它提供了WSDL验证、单元测试、功能测试和性能测试,支持多个数据源,是一款专业的Web Service测试工具。具体介绍可以到官方网站访问http://www.parasoft.com ,官方网站提供试用版下载,目前已经是5.0的版本了;另外在《SOAPtest–一个有用的Web service测试资源》这篇文章中有具体的介绍。



但是看了SOATest的教程文档,似乎输入都需要为WSDL的URL,对这个工具的使用还不是充分了解,希望熟悉此工具的人士不吝指点一二。


4、自主开发工具


 主要思想是,先从Dll文件中取出各个Service的XML Schema文件作为模板,



 


 


将测试数据传入模板测试Service。


5、小结


现在很多系统都开始使用面向服务的架构,很多业务功能都通过Service实现,测试的时候通过UI往往不能发现深层次的问题,通过测试Servce可以增加覆盖率,不过以往工作中接触的较少,希望通过大家的讨论来形成一种比较合适的测试方法。

安全断言标记语言(SAML)是XML标准的数据交换和授权认证安全领域的关系,也就是说,在服务和身份提供者. SAML是产品的安全服务技术委员会 SAML 1.1 FAQ




1. 什么是SAML


SAML(Security Assertion Markup Language)是一个XML框架,也就是一组协议,可以用来传输安全申明。比如,两台远程机器之前要通讯,为了保证安全,我们可以采用加密等措施,也可以采用SAML来传输,传输的数据以XML形式,符合SAML规范,这样我们就可以不要求两台机器采用什么样的系统,只要求能理解SAML规范即可,显然比传统的方式更好。SAML 规范是一组Schema 定义。


可以这么说,在Web Service 领域,schema就是规范,在Java领域,API就是规范。(我自己总结的)


2.SAML 作用?


SAML 主要包括三个方面:

1.认证申明。表明用户是否已经认证,通常用于单点登录。

2.属性申明。表明 某个Sujbect 的属性。

3.授权申明。表明 某个资源的权限。


3. SAML框架是什么?


SAML就是客户向服务器发送SAML 请求,然后服务器返回SAML响应。数据的传输以符合SAML规范的XML格式表示。


SAML 可以建立在SOAP上传输,也可以建立在其他协议上传输。

因为SAML的规范由几个部分构成:SAML Assertion,SAML Prototol,SAML binding等。


4. SAML 安全吗,因为传输都是XML明文?


当然,SAML就是为了解决安全问题提出的。SAML 基于 XML 签名规范,所以整个XML传输虽然是明文,但也法被修改。显然,也可以将XML加密后在传输了。

取自”http://wiki.ccw.com.cn/SAML

本备忘录的状态
本文档讲述了一种Internet社区的Internet标准跟踪协议,它需要进一步进行讨论和建
议以得到改进。请参考最新版的“Internet正式协议标准”(STD1)来获得本协议的标准化程
度和状态。本备忘录的发布不受任何限制。
版权声明
Copyright(C)TheInternetSociety(1998).AllRightsReserved.

摘要:
本文档阐述了一种使用散列函数加密的消息验证机制——散列消息鉴别码
HMAC。HMAC通过捆绑一个共享密钥可以使用任何迭代的可用于加密的散列
函数。例如:MD5,SHA—1。这种加密机制的强度取决于所用散列函数的特性。

1.简介 2
2.HMAC的定义。 2
3.密钥。 3
4.注意事项 4
5.删节输出结果 4
6.安全 4
注释: 5
附录: 5
致谢: 8
参考书目: 8

1.简介
在开放的计算与通讯世界中,提供一种途径去检测通过不可靠媒介传输或
存储的信息完整性是非常重要的。提供这种完整性检测的机制基于一种通常
被称作消息鉴别码的密钥MAC。一般的,消息鉴别码用于验证传输于两个共
同享有一个密钥的单位之间的消息。在本文档中,我们将描述一种基于散列函数
的消息鉴别码机制。这种机制被称为散列消息鉴别码HMAC。它是基于[BCK1]
的作者所做的工作,他说明并就加密性能分析了HMAC的结构。我们在讲述H
MAC的基本原理和安全性分析时会提到这些结果,并且还要与其他键入式散列方
法做对比。
HMAC可以与任何迭代散列函数捆绑使用。MD5和SHA—1就是这种散列函数
HMAC还可以使用一个用于计算和确认消息鉴别值的密钥。
这种结构的主要作用是:

? 不用修改就可以使用适合的散列函数。而且散列函数在软件方面表现的很好。
并且源码是公开和通用的。

? 可以保持散列函数原有的性能而不致使其退化。

? 可以使得基于合理的关于底层散列函数假设的消息鉴别机制的加密强度分析
便于理解。

? 当发现或需要运算速度更快或更安全的散列函数时,可以很容易的实现底层
散列函数的替换。

本文档详细描述了HMAC使用一种抽象的散列函数(用H表示)。具体的HMAC
需要定义一个具体的散列函数。目前,可供选择的散列函数有SHA—1[SHA],MD5,
RIPEMD—128/160[RIPEMD]。这些不同的HMAC实现被表示为,HMAC—SHA1,
HMAC—MD5,HMAC—RIPEMD,等等。

注释:
在写本文档时,MD5和SHA—1是使用最广泛的加密用散列函数。MD5最近已被
证明对于[Dobb]的攻击存在缺陷。这种攻击方法和其他目前已知的MD5的缺陷不允许
在HMAC中按本文的方法使用MD5(细节请见[Dobb])。然而,SHA—1似乎是一种
更强壮的函数。目前,MD5可以被考虑用于HMAC来为应用程序提供出众的执行效
率的观点受到批评。无论如何,执行者与用户都需要了解关于加密用散列函数被破解
可能性的最新进展,并可能会需要替换底层的散列函数。(关于HMAC安全性的更多
信息参见第六部分)

2.HMAC的定义。
定义HMAC需要一个加密用散列函数(表示为H)和一个密钥K。我们假设H是
一个将数据块用一个基本的迭代压缩函数来加密的散列函数。我们用B来表示数据块
的字长。(以上说提到的散列函数的分割数据块字长B=64),用L来表示散列函数的
输出数据字长(MD5中L=16,SHA—1中L=20)。鉴别密钥的长度可以是小于等于数
据块字长的任何正整数值。应用程序中使用的密钥长度若是比B大,则首先用使用散列
函数H作用于它,然后用H输出的L长度字符串作为在HMAC中实际使用的密钥。
一般情况下,推荐的最小密钥K长度是L个字长。(与H的输出数据长度相等)。更详
细的信息参见第三部分。
我们将定义两个固定且不同的字符串ipad,opad:
(‘i’,'o’标志内部与外部)
ipad=thebyte0×36repeatedBTimes
opad=thebyte0×5CrepeatedBtimes.
计算‘text’的HMAC:
H(KXORopad,H(KXORipad,text))
即为以下步骤:

(1) 在密钥K后面添加0来创建一个子长为B的字符串。(例如,如果K的字长是20
字节,B=60字节,则K后会加入44个零字节0×00)

(2) 将上一步生成的B字长的字符串与ipad做异或运算。

(3) 将数据流text填充至第二步的结果字符串中。

(4) 用H作用于第三步生成的数据流。

(5) 将第一步生成的B字长字符串与opad做异或运算。

(6) 再将第四步的结果填充进第五步的结果中。

(7) 用H作用于第六步生成的数据流,输出最终结果

基于MD5的相关代码将作为附录提供

3.密钥。
用于HMAC的密钥可以是任意长度(比B长的密钥将首先被H处理)。但当密钥
长度小于L时的情况时非常令人失望的,因为这样将降低函数的安全强度。长度大于
L的密钥是可以接受的,但是额外的长度并不能显著的提高函数的安全强度。(如果一
个随机的密钥被认为是不可靠的,那么选择一个较长的密钥是明智的)。
密钥必须随机选取(或使用强大的基于随机种子的伪随机生成方法),并且要周期
性的更新。(目前的攻击没有指出一个有效的更换密钥的频率,因为那些攻击实际上并
不可行。然而,周期性更新密钥是一个对付函数和密钥所存在的潜在缺陷的基本
的安全措施,并可以降低泄漏密钥带来的危害。)

4.注意事项
HMAC是按底层散列函数可以不修改源码就可使用这种方式定义的。尤其是它在使用
H函数时还要依赖于预定义的初始化值IV(一个定值,由每个迭代散列函数在初始化它
的压缩函数时指定).然而,如果你愿意的话,可以修改H函数的源码来支持可变的初始
化值Ivs.
这个想法是这样的:压缩函数作用于B字长数据块(KXORopad)和(KXORipad)
所产生的中间结果可以在密钥刚刚生成时就预先计算好的。先将这些中间结果存储,然
后在每次有消息需要验证时来生成H函数的初始化值IV。这种方法为每个要鉴别的消息
保存了H的压缩函数对于两个B字长数据块(KXORopad)和(KXORipad)的应用。
当鉴别短数据流,保存这样的信息是重要的。我们要强调的是:对待这些中间结果要象
对待密钥一样,并且要同样的进行保密。
上述的选择实现HMAC的方法是本地执行的结果,对内部操作性没有影响。

5.删节输出结果
一个著名的消息鉴别方法是删节消息鉴别码的输出,而只输出部分结果。Preneel
与VanOorschot[pv]给出了一些散列消息鉴别码删节后的输出结果的优势分析。在这
一领域的成果并不是绝对的说删节输出结果有全面的安全优势。它有优势的一面(对一
个攻击者来说可用的散列函数结果信息将更少),也有劣势的一面(攻击者要预测的字长
更短)。基于HMAC的应用程序可以只输出HMAC计算结果的最左的t个字节(也就是说
,计算将按第二部分定义的方式执行,但输出结果将删节至t个字节)。我们推荐的输
出长度t不小于散列函数输出长度的一半(匹配生日攻击的限度)且不能少于80字节
(一个适合的速度限制的字节数使得攻击者难以去预测)。我们建议使用HMAC-H-t来表
示基于输出长度为t的散列函数的HMAC的实现。例如,HMAC-SHA1-80表示HMAC使用
SHA-1函数并且输出被删节至80字节。(如果没有声明这项参数,则假定不删节输出结
果。)
6.安全
这里将说明消息鉴别机制的安全性取决于所采用的散列函数的加密特性:1。抗冲突
攻击能力(只限于初始化值是随机且秘密的,且函数的输出对攻击者来说是不可用的情
况)2。当作用于单数据块时H的压缩函数的的消息鉴别属性(在HMAC中这些数据块是
部分未知得,当攻击者自制内部H函数计算结果,并且攻击者是不能充分的选择得)
HMAC中使用的散列函数一般都具有以上或更强的属性。实际上,如果一个散列函数
不具有以上的属性那么它对于大多数的加密应用程序是不适用的,包括基于该函数的选
择消息鉴别方案。(对HMAC函数原理详细阐述和完整的分析参见[BCK1])
只要得到关于候选散列函数的加密强度有限的信任,那么观察它用于消息鉴别的安
全性及以下HMAC结构的两种属性是很重要的。
1. 这种结构是独立于具体所使用的散列函数并且后者是可以被任何其它安全加
密散列函数替代
2. 消息鉴别相对于加密来说是一种“瞬时”影响。公开的对一种消息鉴别方案
的破坏会导致该方案被替换,但是其对已鉴别过的信息却无能为力,。这就与
加密形成鲜明对比。如果其加密算法被破解的话。今天加密的的数据,在未
来都会受到被破解的威胁,
对HMAC已知最有力的攻击是基于散列函数的冲突频率。(“生日攻击法”)[PV,BCK2],
但完全不适用于最小有理散列函数。
例如:如果我们考虑一个类似MD5的散列函数,其输出结果长度为L=16字节(128
比特),攻击者需要获得正确的消息鉴别标志(使用相同的密钥K!!)计算大约2**64已
知明文。这样至少要使用H处理2**64数据块,这是一个不可能完成的任务(一个数据
块的长度为64字节,在连续的1Gbpslink的条件下需要250,000年,并且在整个过程
中不能更换密钥!)这样的攻击只有在函数H的冲突行为的严重缺陷被发现才有可能成为
现实(冲突在处理2**30后会存在)。这样的发现会导致立即更换现有的函数H(这种故
障产生的影响远远大于传统的在上下文环境数字签名与公开密钥中使用的散列函数故
障。)

注释:
这种攻击与在无相关密钥、2**64离线并行计算可以发现冲突的环境中针对加密散
列函数的规则冲突攻击形成鲜明对比。在现在的条件下生日攻击已基本不可行,而
后者可行性却很高。(在以上的例子中,如果使用的散列函数的输出是160字节,则
2**64应改为2**80)


正确的实施以上的结构时需要注意:选择随机(或加密的伪随机)密钥、一个安全
的密钥交换机制,频繁的更新密钥,对密钥的良好的安全防护。以上这些都是维护HMAC
完整的鉴别机制安全的基本要素。


附录:
例程源码
为了更好的说明该机制,我们提供了实现HMAC-MD5的源码,并且还提供了
一些相应的测试向量。(代码是基于[MD5]中的MD5的源码)


/*
**Function:hmac_md5
*/

void
hmac_md5(text,text_len,key,key_len,digest)
unsignedchar*text;/*pointertodatastream*/
inttext_len;/*lengthofdatastream*/
unsignedchar*key;/*pointertoauthenticationkey*/
intkey_len;/*lengthofauthenticationkey*/
caddr_tdigest;/*callerdigesttobefilledin*/

{
MD5_CTXcontext;
unsignedchark_ipad[65];/*innerpadding-
*keyXORdwithipad
*/
unsignedchark_opad[65];/*outerpadding-
*keyXORdwithopad
*/
unsignedchartk[16];
inti;
/*ifkeyislongerthan64bytesresetittokey=MD5(key)*/
if(key_len>64){

MD5_CTXtctx;

MD5Init(&tctx);
MD5Update(&tctx,key,key_len);
MD5Final(tk,&tctx);

key=tk;
key_len=16;
}

/*
*theHMAC_MD5transformlookslike:
*
*MD5(KXORopad,MD5(KXORipad,text))
*
*whereKisannbytekey
*ipadisthebyte0×36repeated64times
*opadisthebyte0×5crepeated64times
*andtextisthedatabeingprotected
*/

/*startoutbystoringkeyinpads*/
bzero(k_ipad,sizeofk_ipad);
bzero(k_opad,sizeofk_opad);
bcopy(key,k_ipad,key_len);
bcopy(key,k_opad,key_len);

/*XORkeywithipadandopadvalues*/
for(i=0;i<64;i++){
k_ipad[i]^=0×36;
k_opad[i]^=0×5c;
}
/*
*performinnerMD5
*/
MD5Init(&context);/*initcontextfor1st
*pass*/
MD5Update(&context,k_ipad,64)/*startwithinnerpad*/
MD5Update(&context,text,text_len);/*thentextofdatagram*/
MD5Final(digest,&context);/*finishup1stpass*/
/*
*performouterMD5
*/
MD5Init(&context);/*initcontextfor2nd
*pass*/
MD5Update(&context,k_opad,64);/*startwithouterpad*/
MD5Update(&context,digest,16);/*thenresultsof1st
*hash*/
MD5Final(digest,&context);/*finishup2ndpass*/
}

TestVectors(Trailing”ofacharacterstringnotincludedintest):

key=0×0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b
key_len=16bytes
data=”HiThere”
data_len=8bytes
digest=0×9294727a3638bb1c13f48ef8158bfc9d

key=”Jefe”
data=”whatdoyawantfornothing?”
data_len=28bytes
digest=0×750c783e6ab0b503eaa86e310a5db738

key=0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

key_len16bytes
data=0xDDDDDDDDDDDDDDDDDDDD…
..DDDDDDDDDDDDDDDDDDDD…
..DDDDDDDDDDDDDDDDDDDD…
..DDDDDDDDDDDDDDDDDDDD…
..DDDDDDDDDDDDDDDDDDDD
data_len=50bytes

digest=0×56be34521d144c88dbb8c733f0e8b3f6


致谢:
Pau-ChenCheng,JeffKraemer,andMichaelOehler,haveprovided
usefulcommentsonearlydrafts,andranthefirstinteroperability
testsofthisspecification.JeffandPau-Chenkindlyprovidedthe
samplecodeandtestvectorsthatappearintheappendix.Burt
Kaliski,BartPreneel,MattRobshaw,AdiShamir,andPaulvan
Oorschothaveprovidedusefulcommentsandsuggestionsduringthe
investigationoftheHMACconstrUCtion.

参考书目:

[ANSI]ANSIX9.9,”AmericanNationalStandardforFinancial
InstitutionMessageAuthentication(Wholesale),”American
BankersAssociation,1981.Revised1986.

[Atk]Atkinson,R.,”IPAuthenticationHeader”,RFC1826,August
1995.

[BCK1]M.Bellare,R.Canetti,andH.Krawczyk,
“KeyedHashFunctionsandMessageAuthentication”,
ProceedingsofCrypto’96,LNCS1109,pp.1-15.
(http://www.research.ibm.com/security/keyed-md5.html)

[BCK2]M.Bellare,R.Canetti,andH.Krawczyk,
“PseudorandomFunctionsRevisited:TheCascadeConstruction”,
ProceedingsofFOCS’96.

[Dobb]H.Dobbertin,”TheStatusofMD5AfteraRecentAttack”,
RSALabs’CryptoBytes,Vol.2No.2,Summer1996.
http://www.rsa.com/rsalabs/pubs/cryptobytes.html

[PV]B.PreneelandP.vanOorschot,”BuildingfastMACsfromhash
functions”,AdvancesinCryptology–CRYPTO’95Proceedings,
LectureNotesinComputerScience,Springer-VerlagVol.963,

[MD5]Rivest,R.,”TheMD5Message-DigestAlgorithm”,
RFC1321,April1992.

[MM]Meyer,S.andMatyas,S.M.,Cryptography,NewYorkWiley,
1982.

[RIPEMD]H.Dobbertin,A.Bosselaers,andB.Preneel,”RIPEMD-160:A
strengthenedversionofRIPEMD”,FastSoftwareEncryption,
LNCSVol1039,pp.71-82.
FTP://ftp.esat.kuleuven.ac.be/pub/COSIC/bosselae/ripemd/.

[SHA]NIST,FIPSPUB180-1:SecureHashStandard,April1995.

[Tsu]G.Tsudik,”Messageauthenticationwithone-wayhash
functions”,InProceedingsofInfocom’92,May1992.
(Alsoin”AccessControlandPolicyEnforcementin
Internetworks”,Ph.D.Dissertation,ComputerScience
Department,UniversityofSouthernCalifornia,April1991.)

[VW]P.vanOorschotandM.Wiener,”ParallelCollision
SearchwithApplicationstoHashFunctionsandDiscrete
Logarithms”,Proceedingsofthe2ndACMConf.Computerand
CommunicationsSecurity,Fairfax,VA,November1994.

作者地址:
HugoKrawczyk
IBMT.J.WatsonResearchCenter
P.O.Box704
YorktownHeights,NY10598


EMail:hugo@watson.ibm.com

MihirBellare
DeptofComputerScienceandEngineering
MailCode0114
UniversityofCaliforniaatSanDiego
9500GilmanDrive
LaJolla,CA92093

EMail:mihir@cs.ucsd.edu

RanCanetti
IBMT.J.WatsonResearchCenter
P.O.Box704
YorktownHeights,NY10598

EMail:canetti@watson.ibm.com

http://www.microsoft.com/china/MSDN/library/WebServices/WebServices/HowASP.NETWebServicesWork.mspx?mfr=true


按照RFC2045的定义, Base64被定义为:Base64内容传送编码被设计用来把任意序列的8位字节描述为一种不易被人直接识别的形式。(The Base64 Content-Transfer-Encoding is designed to represent arbitrary sequences of octets in a form that need not be humanly readable.)


图标base64编码在线转换器 :
http://www.motobit.com/util/base64-decoder-encoder.asp


Base64是网络上最常见的用于传输8Bit字节代码的编码方式之一,在发送电子邮件时,服务器认证的用户名和密码需要用Base64编码,附件也需要用Base64编码。
下面简单介绍Base64算法的原理,由于代码太长就不在此贴出
Base64是网络上最常见的用于传输8Bit字节代码的编码方式之一,大家可以查看RFC2045~RFC2049,上面有MIME的详细规范。


Base64要求把每三个8Bit的字节转换为四个6Bit的字节(3*8 = 4*6 = 24),然后把6Bit再添两位高位0,组成四个8Bit的字节,也就是说,转换后的字符串理论上将要比原来的长1/3。


这样说会不会太抽象了?不怕,我们来看一个例子:


转换前 aaaaaabb ccccdddd eeffffff
转换后 00aaaaaa 00bbcccc 00ddddee 00ffffff


应该很清楚了吧?上面的三个字节是原文,下面的四个字节是转换后的Base64编码,其前两位均为0。


转换后,我们用一个码表来得到我们想要的字符串(也就是最终的Base64编码),这个表是这样的:(摘自RFC2045)
Table 1: The Base64 Alphabet


Value Encoding Value Encoding Value Encoding Value Encoding
0 A 17 R 34 i 51 z
1 B 18 S 35 j 52 0
2 C 19 T 36 k 53 1
3 D 20 U 37 l 54 2
4 E 21 V 38 m 55 3
5 F 22 W 39 n 56 4
6 G 23 X 40 o 57 5
7 H 24 Y 41 p 58 6
8 I 25 Z 42 q 59 7
9 J 26 a 43 r 60 8
10 K 27 b 44 s 61 9
11 L 28 c 45 t 62 +
12 M 29 d 46 u 63 /
13 N 30 e 47 v
14 O 31 f 48 w (pad) =
15 P 32 g 49 x
16 Q 33 h 50 y
让我们再来看一个实际的例子,加深印象!


转换前 10101101 10111010 01110110
转换后 00101011 00011011 00101001 00110110
十进制 43 27 41 54
对应码表中的值 r b p 2



所以上面的24位编码,编码后的Base64值为 rbp2
解码同理,把 rbq2 的二进制位连接上再重组得到三个8位值,得出原码。
(解码只是编码的逆过程,在此我就不多说了,另外有关MIME的RFC还是有很多的,如果需要详细情况请自行查找。)


用更接近于编程的思维来说,编码的过程是这样的:


第一个字符通过右移2位获得第一个目标字符的Base64表位置,根据这个数值取到表上相应的字符,就是第一个目标字符。
然后将第一个字符左移4位加上第二个字符右移4位,即获得第二个目标字符。
再将第二个字符左移2位加上第三个字符右移6位,获得第三个目标字符。
最后取第三个字符的右6位即获得第四个目标字符。


在以上的每一个步骤之后,再把结果与 0×3F 进行 AND 位操作,就可以得到编码后的字符了。


C++ Base64编码/解码源代码



inline int Base64Encode(char * base64code, const char * src, int src_len = 0);
inline int Base64Decode(char * buf, const char * base64code, int src_len = 0);


以上两个函数内联定义在base64.h中,使用时include “base64.h” 即可,编码后的长度一般比原文多占1/3的存储空间,为了效率,程序并没有检查目标存储区是否溢出,请保证有足够的存储空间。



源码下载:http://www.yanghan.net/codes/base64src.rar


示例代码输出如下:
[Base64]:
xOO6w6OsU25haVgNCg0KoaGhodXiysfSu7j2QmFzZTY0tcSy4srU08q8/qOhDQoNCkJlc3QgV2lzaGVz
IQ0KDQqhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhICAgICAgICAgICAgICAgZVNYPyENCqGhoaGh
oaGhoaGhoaGhoaGhoaGhoaGhoaGhoaEgICAgICAgICAgICAgICBzbmFpeEB5ZWFoLm5ldA0KoaGhoaGh
oaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoSAgICAgICAgIDIwMDMtMTItMjU=


[源文]:
你好,SnaiX


  这是一个Base64的测试邮件!


Best Wishes!


最近在用加密后的二进制写cookie时碰到问题,cookie内容必须时可见字符,就必须将二进制文件转换成对应的可见字符,Base64编码是不错的解决方案。
1. 定义
Base64是用可见字符传输任意字节的编码方式。RFC2045的section 6.8有描述(The Base64 Content-Transfer-Encoding is designed to represent arbitrary sequences of octets in a form that need not be humanly readable)。电子邮件传输的编码就是采用Base64.

2. 如何编码
ASCII码表示范围0X00-0XFF,共255个字符,用一个字节(八位组)表示。Base64编码是用6位组来表示所有字符。-> 每三个8Bit的字节转换为四个6Bit的字节(3*8 = 4*6 = 24),然后把6Bit再添两位高位0,组成四个8Bit的字节,也就是说,转换后的字符串理论上将要比原来的长1/3。
来看一个转换的例子;
转换前 12345678 12345678 12345678
转换后 00123456 00781234 00567812 00345678


转换成Base64后,编码值范围0-63,共64个字符表示。如下:


Sequence Characters
0 … 25 ‘A’ … ’Z’
26 … 51 ‘a’ … ‘z’
52 … 61 ‘0’ … ‘9’
62 ‘+’
63 ‘/’


完整的映射如下:


The Base64 Alphabet


Value Encoding Value Encoding Value Encoding Value Encoding
0 A 17 R 34 i 51 z
1 B 18 S 35 j 52 0
2 C 19 T 36 k 53 1
3 D 20 U 37 l 54 2
4 E 21 V 38 m 55 3
5 F 22 W 39 n 56 4
6 G 23 X 40 o 57 5
7 H 24 Y 41 p 58 6
8 I 25 Z 42 q 59 7
9 J 26 a 43 r 60 8
10 K 27 b 44 s 61 9
11 L 28 c 45 t 62 +
12 M 29 d 46 u 63 /
13 N 30 e 47 v
14 O 31 f 48 w (pad) =
15 P 32 g 49 x
16 Q 33 h 50 y


填充符=。当需要编码的字符不是3的倍数,就会有余数1或2。这个时候就需要填充2位或1位来补齐,使转换后是4个字节,最多填充两个‘=’如:字符串“张”
11010101 HEX:D5 11000101 HEX:C5


00110101 00011100 00010100
十进制53 十进制34 十进制20 pad
字符’1’ 字符’i’ 字符’U’ 字符’=’


“张”转换成Base64就是”1iU=”,”张3″转换成Base64的结果是”1iU3″。


编码过程,在网上一篇文章大概是如下描述:


第一个字符通过右移2位获得第一个目标字符的Base64表位置,根据这个数值取到表上相应的字符,就是第一个目标字符。
然后将第一个字符左移4位加上第二个字符右移4位,即获得第二个目标字符。
再将第二个字符左移2位加上第三个字符右移6位,获得第三个目标字符。
最后取第三个字符的右6位即获得第四个目标字符。
在以上的每一个步骤之后,再把结果与 0×3F 进行 AND 位操作,就可以得到编码后的字符了。


如果原文字节数不是3的倍数,原文的字节不够的地方可以用全0来补足,转换时Base64编码用=号来代替。这就是为什么有些Base64编码会以一个或两个等号结束的原因,但等号最多只有两个。因为:


余数 = 原文字节数 MOD 3


所以余数任何情况下都只可能是0,1,2这三个数中的一个。如果余数是0的话,就表示原文字节数正好是3的倍数(最理想的情况啦)。如果是1的话,为了让Base64编码是4的倍数,就要补2个等号;同理,如果是2的话,就要补1个等号。


3. 源码实现


实现 1)


base.h


#include <string>


std::string base64_encode(unsigned char const* , unsigned int len);
std::string base64_decode(std::string const& s);



base.cpp


#include “base64.h”
#include <iostream>


static const std::string base64_chars =
“ABCDEFGHIJKLMNOPQRSTUVWXYZ”
“abcdefghijklmnopqrstuvwxyz”
“0123456789+/”;



static inline bool is_base64(unsigned char c) {
return (isalnum(c) || (c == ‘+’) || (c == ‘/’));
}


std::string base64_encode(unsigned char const* bytes_to_encode, unsigned int in_len) {
std::string ret;
int i = 0;
int j = 0;
unsigned char char_array_3[3];
unsigned char char_array_4[4];


while (in_len–) {
char_array_3[i++] = *(bytes_to_encode++);
if (i == 3) {
char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
char_array_4[1] = ((char_array_3[0] & 0×03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
char_array_4[2] = ((char_array_3[1] & 0×0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
char_array_4[3] = char_array_3[2] & 0×3f;


for(i = 0; (i <4) ; i++)
ret += base64_chars[char_array_4[i]];
i = 0;
}
}


if (i)
{
for(j = i; j < 3; j++)
char_array_3[j] = ”;


char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
char_array_4[1] = ((char_array_3[0] & 0×03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
char_array_4[2] = ((char_array_3[1] & 0×0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
char_array_4[3] = char_array_3[2] & 0×3f;


for (j = 0; (j < i + 1); j++)
ret += base64_chars[char_array_4[j]];


while((i++ < 3))
ret += ‘=’;


}


return ret;


}


std::string base64_decode(std::string const& encoded_string) {
int in_len = encoded_string.size();
int i = 0;
int j = 0;
int in_ = 0;
unsigned char char_array_4[4], char_array_3[3];
std::string ret;


while (in_len– && ( encoded_string[in_] != ‘=’) && is_base64(encoded_string[in_])) {
char_array_4[i++] = encoded_string[in_]; in_++;
if (i ==4) {
for (i = 0; i <4; i++)
char_array_4[i] = base64_chars.find(char_array_4[i]);


char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0×30) >> 4);
char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0×3c) >> 2);
char_array_3[2] = ((char_array_4[2] & 0×3) << 6) + char_array_4[3];


for (i = 0; (i < 3); i++)
ret += char_array_3[i];
i = 0;
}
}


if (i) {
for (j = i; j <4; j++)
char_array_4[j] = 0;


for (j = 0; j <4; j++)
char_array_4[j] = base64_chars.find(char_array_4[j]);


char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0×30) >> 4);
char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0×3c) >> 2);
char_array_3[2] = ((char_array_4[2] & 0×3) << 6) + char_array_4[3];


for (j = 0; (j < i - 1); j++) ret += char_array_3[j];
}


return ret;
}


测试代码



#include “base64.h”
#include <iostream>


int main() {
const std::string s = “ADP GmbH\nAnalyse Design & Programmierung\nGesellschaft mit beschränkter Haftung” ;


std::string encoded = base64_encode(reinterpret_cast<const unsigned char*>(s.c_str()), s.length());
std::string decoded = base64_decode(encoded);


std::cout << “encoded: ” << encoded << std::endl;
std::cout << “decoded: ” << decoded << std::endl;


return 0;
}



实现 2)


类定义


class Base64
{
static char Encode(unsigned char uc);
static unsigned char Decode(char c);
static bool IsBase64(char c);
public:
static std::string Encode(
const std::vector<unsigned char> & vby);
static std::vector<unsigned char> Decode(
const std::string & str);
};


类实现



inline char Base64::Encode(unsigned char uc)
{
if (uc < 26)
{
return ‘A’+uc;
}
if (uc < 52)
{
return ‘a’+(uc-26);
}
if (uc < 62)
{
return ‘0′+(uc-52);
}
if (uc == 62)
{
return ‘+’;
}
return ‘/’;
};
inline unsigned char Base64::Decode(char c)
{
if (c >= ‘A’ && c <= ‘Z’)
{
return c - ‘A’;
}
if (c >= ‘a’ && c <= ‘z’)
{
return c - ‘a’ + 26;
}
if (c >= ‘0′ && c <= ‘9′)
{
return c - ‘0′ + 52;
}
if (c == ‘+’)
{
return 62;
};
return 63;
};
inline bool Base64::IsBase64(char c)
{
if (c >= ‘A’ && c <= ‘Z’)
{
return true;
}
if (c >= ‘a’ && c <= ‘z’)
{
return true;
}
if (c >= ‘0′ && c <= ‘9′)
{
return true;
}
if (c == ‘+’)
{
return true;
};
if (c == ‘/’)
{
return true;
};
if (c == ‘=’)
{
return true;
};
return false;
};
inline std::string Base64::Encode(const std::vector<unsigned char> & vby)
{
std::string retval;
if (vby.size() == 0)
{
return retval;
};
for (int i=0;i<vby.size();i+=3)
{
unsigned char by1=0,by2=0,by3=0;
by1 = vby[i];
if (i+1<vby.size())
{
by2 = vby[i+1];
};
if (i+2<vby.size())
{
by3 = vby[i+2];
}
unsigned char by4=0,by5=0,by6=0,by7=0;
by4 = by1>>2;
by5 = ((by1&0×3)<<4)|(by2>>4);
by6 = ((by2&0xf)<<2)|(by3>>6);
by7 = by3&0×3f;
retval += Encode(by4);
retval += Encode(by5);
if (i+1<vby.size())
{
retval += Encode(by6);
}
else
{
retval += “=”;
};
if (i+2<vby.size())
{
retval += Encode(by7);
}
else
{
retval += “=”;
};
if (i % (76/4*3) == 0)
{
retval += “\r\n”;
}
};
return retval;
};
inline std::vector<unsigned char> Base64::Decode(const std::string & _str)
{
std::string str;
for (int j=0;j<_str.length();j++)
{
if (IsBase64(_str[j]))
{
str += _str[j];
}
}
std::vector<unsigned char> retval;
if (str.length() == 0)
{
return retval;
}
for (int i=0;i<str.length();i+=4)
{
char c1=’A',c2=’A',c3=’A',c4=’A';
c1 = str[i];
if (i+1<str.length())
{
c2 = str[i+1];
};
if (i+2<str.length())
{
c3 = str[i+2];
};
if (i+3<str.length())
{
c4 = str[i+3];
};
unsigned char by1=0,by2=0,by3=0,by4=0;
by1 = Decode(c1);
by2 = Decode(c2);
by3 = Decode(c3);
by4 = Decode(c4);
retval.push_back( (by1<<2)|(by2>>4) );
if (c3 != ‘=’)
{
retval.push_back( ((by2&0xf)<<4)|(by3>>2) );
}
if (c4 != ‘=’)
{
retval.push_back( ((by3&0×3)<<6)|by4 );
};
};
return retval;
};

XML Signature规范是将数字签名和XML组合而成的产物,不要以为XML Signature仅仅是将数字签名技术应用于XML文件。


XML Signature
包括以下的功能:


       1XML Signature可以对任何能够以URI形式(uniform resource identifier)定位的资源做签名。既包括与签名同在一个XML文件中的元素,也包括其他XML文件中的元素,甚至可以是非XML形式的资源(比如一个图形文件),只要能被URI定位到的资源都可以应用XML Signature. 这也代表了XML签名的对象可以是动态的变化。


       2XML Signature可以对XML文件中的任一元素做签名,也可以对整个文件做签名。


       3XML Signature 既可以用非对称密钥做签名(Digital Signature),也可以用对称密钥做签名(HMAC)



XML Signature


            CanonicalizationMethod代表了将XML元素标准化的方法,在XML的签名中与通常的签名有一个不同的地方在于XML元素的特殊性,比如同样一份XML文件在Windows下的表示方法和Linux下就会有所不同,因为同样一个换行符号在不同的操作系统代表的字符是不同的,但你不可能因为这个就认为这份XML文件被修改了;一个XML元素的两个属性的位置调换了,也不代表这份XML文件的完整性被破坏,所以在XML元素被签名前需要做一个标准化的处理。该元素就是指定其处理方法。


            SignatureMethod指定了用于签名的方法,其中包括了摘要所用的方法以及是使用非对称密钥还是对称密钥加密(或混合)


             Reference表示真正想要签名的对象,通过URI定位到要签名的对象(一个xml元素或其他),如果没有指定URI就表示要签名的整个XML文件。


                       Transforms表示了在签名前可能对被签名对象所要做的转换,比如当待签名的对象是一个二进制资源时,为了避免该对象中可能出现非法的XML格式,就需要用Base-64将其转换一下。这里还可以使用一些其他的转化方法如XPATHXSLT


                       DigestMethod指定了对引用对象做摘要的方法,一般使用SHA1


                       DigestValue这里面存放做完摘要后的结果,这样当后面对做SignedInfo签名的时候就间接的对引用对象做了签名,从而保证其完整性。


             


        SignatureValue这里存放的是对SignedInfo元素标准化(CanonicalizationMethod)后签名(SignatureMethod)的结果。


        KeyInfo是一个可选项,它用于消息接受方查找验证签名时所需的密钥,比如用某个特定的名称标识(Identity)来表示密钥,这样信息的接受方就可以根据这个标识来查找验证签名时所需要的密钥。如果消息交换双方之前已经约定好了签名的密钥就可以省略该元素。


        Object用于定义一些扩展信息,也是可选项。

的结构如下:

<Signature>
    <SignedInfo>
        <CanonicalizationMethod>
        <SignatureMethod>
        (<Reference (URI=)? >
            (Transforms)?
            (DigestMethod)
            (DigestValue)
        </Reference>)+
    </SignedInfo>
    <SignatureValue>
    (KeyInfo)?
    (Object)*
</Signature>

(x)? 代表x出现0-1次 (x)+ 代表x出现1-n次 (x)* 代表x出现0-n次


下面对
Signature中出现的各个元素做简要介绍:  
      SignedInfo
表示最终要被签名的对象,签名主要包括两个过程先对要签名的对象做摘要,然后加密摘要(DSA不是加密摘要而是将摘要和密钥混合,效果类似)。XML Signature签名的对象并不仅仅是你引用的对象,而是包含了一些其他元素,比如CanonicalizationMethod, SignatureMethod元素,它是对整个SignedInfo元素做的签名。


其中根据被签名对象
(ReferenceURI所指向的对象)Signature元素所处的位置关系,将XML Signature做了一个分类。


如果被签名对象是Signature的子元素那么这个签名属于Enveloping Signatures类型。如果Signature是被签名对象的子元素,那么该签名属于Enveloped Signatures类型。如果以上两种都不是则属于Detached Signatures类型。



<Signature xmlns=”http://www.w3.org/2000/09/xmldsig#”>
<SignedInfo>
    <Reference URI=”#111″ />
</SignedInfo>
<SignatureValue></SignatureValue>
<KeyInfo></KeyInfo>
<Object>
     <SignedItem id=”111″>Stuff to be signed</SignedItem>
</Object>
</Signature>
Enveloping Signatures


<PurchaseOrder id=”po1″>
<SKU>125356</SKU>
<Quantity>17</Quantity>
<Signature xmlns=”http://www.w3.org/2000/09/xmldsig#”>
    <SignedInfo>
      <Reference URI=”#po1″ />
    </SignedInfo>
    <SignatureValue></SignatureValue>
    <KeyInfo></KeyInfo>
</Signature>
</PurchaseOrder>
Enveloped Signatures

<PurchaseOrderDocument>
<PurchaseOrder id=”po1″>
    <SKU>12366</SKU>
    <Quantity>17</SKU>
</PurchaseOrder>
<Signature xmlns=”http://www.w3.org/2000/09/xmldsig#”>
    <SignedInfo>
      <Reference URI=”#po1″ />
   </SignedInfo>
    <SignatureValue></SignatureValue>
    <KeyInfo></KeyInfo>
</Signature>
</PurchaseOrderDocument>
Detached Signatures 1

<Signature xmlns=”http://www.w3.org/2000/09/xmldsig#”>
<SignedInfo>
    <Reference URI=”http://www.foo.com/picture.jpg” />
</SignedInfo>
<SignatureValue></SignatureValue>
<KeyInfo></KeyInfo>
</Signature>
Detached Signatures 2


在了解了XML Signature的结构的基础上,再来看看XML Signature的处理过程,其中包括了Signature的创建以及验证过程。
在创建Signature的时候分为两个步骤:
    1. 引用(Reference)的创建
       1.1 利用URI定位到引用的资源,并获得其数据。
        1.2 利用Transforms元素中指定的方法对引用的资源做转换。
       1.3 利用DigestMethod元素中指定的方法对转换后的资源做摘要,获得的结果用于创建DigestValue元素。
        1.4 将以上元素组合成Reference元素。

<Reference URI=”http://www.foo.com/securePage.html”>
<DigestMethod Algorithm=”http://www.w3.org/2000/09/xmldsig#sha1″ />
<DigestValue>60NvZvtdTB+7UnlLp/H24p7h4bs=</DigestValue>
</Reference>

    2. 签名(Signature)的创建
        2.1 创建SignedInfo元素,并指定CanonicalizationMethod 和SignatureMethod的方法,然后将之前创建的Reference元素包含进来。
       2.2 根据指定的CanonicalizationMethod将刚创建的SignedInfo元素标准化。
       2.3 根据指定的SignatureMethod对标准化后的SignedInfo元素做签名,所得到的结果用于创建SignatureValue元素。
       2.4 将SignedInfo,SignatureValue以及可选的KeyInfo和Object元素组合成Signature元素。

经过以上步骤,Signature创建完毕。最后介绍消息的接受方如何对Signature进行验证,从而确认消息的完整性。
Signature的验证过程和创建过程一样也分为两个步骤,只有当Reference, Signature都通过验证才能保证消息的完整性。首先在验证前利用CanonicalizationMethod元素中指定的方法将整个Signature元素标准化,然后开始验证各个部分:

     1. 引用(Reference)的验证
       1.1 利用URI定位到引用的资源,并获得其数据。
       1.2 利用Transforms元素中指定的方法对引用的资源做转换。
     1.3 利用DigestMethod元素中指定的方法对转换后的资源做摘要。
        1.4 将上一步的结果与DigestValue中的内容做比较,如果完全一致,则通过验证。
     2. 签名(Signature)的验证
        2.1 获得用于验证的密钥。(可能事先已经约定好,也可能通过KeyInfo中的信息取得)
       2.2 对经过标准化的SignedInfo元素做摘要。
       2.3 利用在第一步中获得的密钥解密SignatureValue元素中的内容,将其与上一步创建的摘要做比较,如果完全一致,则通过验证。如果使用了数字签名(非对称密钥),那么消息还将具有抗否认性。

当以上的两个验证都通过时,可以得到以下的结论:
    1. 消息在传送以及保存的过程中都没有被别人算改。
    2. 消息的发送方与签名方一致(身份鉴别)。 因为密钥只有消息的发送方和消息的接受方知道。
    3. 如果使用了数字签名(非对称密钥加密摘要), 那么还可以防止消息的发送方否认它曾经发送过此消息。

然而以上的消息都是以明文形式传递的,为了保证消息的机密性,就引出了下面将要介绍的内容— XML Encryption。

参考资料:
<<Securing Web Services with WS-Security>>