现在的位置: 首页 > 综合 > 正文

使用gSOAP开发实例(8) Phase 1 完结篇 自定义header实现用户名令牌认证(Usernametoken Authentication)

2013年11月20日 ⁄ 综合 ⁄ 共 17749字 ⁄ 字号 评论关闭

上一节介绍了
怎样实现基本认证
(Basic Authentication
,以下简称
basic
方式
)
,望文生义,也就是最简单的用户验证方式,本节稍微深入一些,介绍用户名令牌认证
(Usernametoken Authentication
,以下简称
usernametoken
方式
)

 

Usernametoken
方式与
basic
方式不同的地方,在于后者会把用户名和密码以摘要
(digest)
的形式,置于
HTTP
信息头,而前者则把用户名以明文的形式、密码以明文或者摘要的形式,嵌入到一段
XML
文本中,再置于
SOAP
消息头当中。

 

如果使用
soapUI
调试客户端程序的话,会发现以下是
basic
方式发出的完整的
SOAP
消息:

POST https://test2.r-secure.com/Services/ECHO HTTP/0.9

Content-Type: text/xml;charset=UTF-8

SOAPAction: ""

User-Agent: Jakarta Commons-HttpClient/3.1

Content-Length: 292

Authorization: Basic
VkYtSEstbVNNST0OdlR42EMZaD1BMyE=

Host: test2.r-secure.com

Cookie: $Version=0; MSP2LB=test2.test2f02; $Path=/

 

<soapenv:Envelope
xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:echo="http://echo.rsecure.com/ECHO">

  
<soapenv:Header/>

  
<soapenv:Body>

     
<echo:echo>

        

<echo:EchoMessage>hello</echo:EchoMessage>

     
</echo:echo>

  
</soapenv:Body>

</soapenv:Envelope>

 

以下是
usernametoken
方式发出的完整的
SOAP
消息:

POST https://test.r-secure.com/4.0/services/SecureEcho HTTP/1.1

Content-Type: text/xml;charset=UTF-8

SOAPAction: ""

User-Agent: Jakarta Commons-HttpClient/3.1

Host: test.r-secure.com

Content-Length: xxx

 

<soapenv:Envelope xmlns:echo="http://echo.ws.rsecure.com"
xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">

  
<soapenv:Header>

     
<wsse:Security soapenv:mustUnderstand="1"
xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">

        
<wsse:UsernameToken
wsu:Id="UsernameToken-32870670"
xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">

           
<wsse:Username>roy</wsse:Username>

           
<wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">liang</wsse:Password>

           

<wsse:Nonce>LX4gh+njbEtCNAtkWkXDYA==</wsse:Nonce>

           

<wsu:Created>2010-08-11T06:02:25.874Z</wsu:Created>

        
</wsse:UsernameToken>

     
</wsse:Security>

     
<echo:customerId>G06164</echo:customerId>

  
</soapenv:Header>

  
<soapenv:Body>

     
<echo:sendEcho>

        

<echo:message>hello</echo:message>

     

</echo:sendEcho>

  
</soapenv:Body>

</soapenv:Envelope>

 

其中,加粗部分表示两者的主要区别,红字部分表示各自必不可少的元素。

 

由此可以看出,
usernametoken
方式的特点,是在
SOAP

header
中加入一个
Security
标签,把
usernametoken
信息放在这个
Security
标签里。至于
header
里的另外一个
customerId
标签,是该应用自身的额外要求。

 

 

gSOAP
实现
basic
方式相对简单,不过实现
usernametoken
就比较复杂。
gSOAP
的用户指南推荐使用其自带的插件,在
samples/wsse
目录下的官方实例也是使用这个插件。但是,我个人认为这种方法比较累赘,自动生成的代码太多,不易看懂,而且似乎非常依赖于
wsdl
本身的写法。比如,
samples/wsse
目录下的官方实例含有下列表示
SOAP
header

的结构体,但是,在我实际开发的应用并没有自动产生,即使强行加上去,编译执行通过,运行的时候也出现了相当多的错误。

 

 

而且,从理论上讲,
gSOAP
不过是一个框架,定义了从
SOAP
对象到
SOAP
消息,以及从
SOAP
消息到
SOAP
对象的序列化过程,并且提供了一套与之相适应的
API
,使用
gSOAP
开发不过是在其框架范围内调用其
API
编程。框架的弊端,可想而知,限制了灵活,也限制了方便,更限制了创新。所以,我们可以使用
gSOAP
编程,但是也许没有必要全部照搬,至少在这个案例中,就没有必要照搬。

 

我们应有的思路是,既然
customerId
可以直接写到
SOAP header
中,那么与之并列的、含有
usernametoken

Security
也可以直接写到
SOAP header
中,完全不需要依赖于
gSOAP

wsse
插件。

 

 

与上节一样,基于保密原则,本节案例采用的
wsdl
同样是经过裁剪和替换的,内容如下:

 

 


gSOAP

wsdl
目录,按以下步骤建立客户端存根程序:

-bash-3.2$
mkdir –p secure_echo

-bash-3.2$
cd secure_echo

-bash-3.2$
../wsdl2h –c –o
secure_echo.h secure_echo.wsdl

-bash-3.2$
../../src/soapcpp2 –C –L
–x secure_echo.h

 

重点来了,此时需要修改
gSOAP
为你自动生成的部分文件,加入
usernametoken
支持,以下是详细步骤,代码中加粗部分即修改的内容:

1.    


soapStub.h
,搜索
SOAP_ENV__Header
结构体,本案例中,
gSOAP
只为我们自动生成了对应
customerId
的指针,因为需要在
SOAP header
中增加用户名和密码,所以要在这里手动添加这些信息。这样,修改后的
SOAP_ENV__Header
变为:

struct SOAP_ENV__Header

{

       
char *wsse__username;  

/* mustUnderstand */

       
char *wsse__password;  

/* mustUnderstand */

       
char
*ns1__customerId; 
/* mustUnderstand */

};

2.    


soapC.c
,搜索
soap_out_SOAP_ENV__Header
函数,这是客户端把
SOAP
对象转化为
SOAP
消息相关的函数,由于
gSOAP
只是自动生成了
customerId
属性的转化,我们还需要加入
Security
属性,按照
soapUI
测试好的结果,
Security
含有一个
UsernameToken
,而
UsernameToken
又含有用户名和密码,因此
soap_out
函数应当这样写:

SOAP_FMAC3 int
SOAP_FMAC4 soap_out_SOAP_ENV__Header(struct soap *soap, const char *tag, int
id, const struct SOAP_ENV__Header *a, const char *type)

{

       
if (soap_element_begin_out(soap, tag,
soap_embedded_id(soap, id, a, SOAP_TYPE_SOAP_ENV__Header), type))

               
return soap->error;

       
if
(soap_element_begin_out(soap, "wsse:Security", -1, "")

               
||
soap_element_begin_out(soap, "wsse:UsernameToken", -1, "")

               
||
soap_out_string(soap, "wsse:Username", -1, &a->wsse__username,
"")

               
||
soap_out_string(soap, "wsse:Password", -1, &a->wsse__password,
"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText")

               
||
soap_element_end_out(soap, "wsse:UsernameToken")

               
||
soap_element_end_out(soap, "wsse:Security")

       
)

               

return soap->error;

       
soap->mustUnderstand = 1;

       
if (soap_out_string(soap,
"ns1:customerId", -1, &a->ns1__customerId, ""))

               
return soap->error;

       
return soap_element_end_out(soap,
tag);

}

3.    


SecureEchoHttpBinding.nsmap
,由于上一步用到了
wsse
这个
namespace
,而它又没有出现在
nsmap
文件中,因此我们需要增加该命名空间的信息,其
URL
同样可以从
soapUI
测试结果中取得:

SOAP_NMAC struct
Namespace namespaces[] =

{

       
{"SOAP-ENV",
"http://schemas.xmlsoap.org/soap/envelope/",
"http://www.w3.org/*/soap-envelope", NULL},

       
{"SOAP-ENC",
"http://schemas.xmlsoap.org/soap/encoding/",
"http://www.w3.org/*/soap-encoding", NULL},

       
{"xsi",
"http://www.w3.org/2001/XMLSchema-instance",
"http://www.w3.org/*/XMLSchema-instance", NULL},

       
{"xsd",
"http://www.w3.org/2001/XMLSchema", "http://www.w3.org/*/XMLSchema",
NULL},

       
{"ns1",
"http://echo.ws.rsecure.com", NULL, NULL},

       

{"wsse",
"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd",
NULL, NULL},

       
{NULL, NULL, NULL, NULL}

};

 

 

存根程序修改完成,就可以开始编写客户端程序代码了。这个
web
service

提供了两个接口,一个是
secure_echo
,也就是客户端送任意信息上来,服务端就返回相同的字符串,代码如下:

 

 

另外一个是显示版本信息,代码如下:

 

 

两个客户端程序都是一样的结构,仅仅是调用的接口不一样。与上一节的
basic
方式的客户端相比,
usernametoken
方式的用户密码不是保存在
soap
结构体的
userid
变量和
passwd
变量中,而是保存在
header
指针指向的
SOAP_ENV__Header
结构体中,这就是刚才我们为什么要修改
SOAP_ENV__Header
结构体的原因。

 

两个客户端程序分别保存为
secure_echo.c

show_version.c
,编译命令分别是:

gcc -DWITH_OPENSSL -O2 -o secure_echo secure_echo.c soapC.c
soapClient.c ../../stdsoap2.c -I../.. -L../.. -lgsoap -lssl

 

gcc -DWITH_OPENSSL -O2 -o show_version show_version.c soapC.c
soapClient.c ../../stdsoap2.c -I../.. -L../.. -lgsoap –lssl

 

由此可见,编译的源代码和链接的库文件都与上一节的没什么区别,没有使用额外的插件。

 

客户端程序搞掂,然后就是服务端了。

 

回到
gSOAP

wsdl
目录,按以下步骤建立服务端存根程序:

-bash-3.2$
mkdir –p
secure_echo_server

-bash-3.2$
cd secure_echo_server

-bash-3.2$
cp –p
../secure_echo/secure_echo.h .

-bash-3.2$
../../src/soapcpp2 –S –L
–x secure_echo.h

 

与客户端程序一样,服务端同样要进行一些修改,步骤如下:

1.    


soapStub.h
,与客户端的修改是一样的

2.    


soapC.c
,搜索
soap_in_SOAP_ENV__Header
函数,注意客户端修改的是
soap_out
函数,这里是
soap_in
函数,是服务端把
SOAP
消息转化为
SOAP
对象的函数。由于客户端送上来的
SOAP header
消息含有
Security
属性,我们需要把它转为
username

password
。修改后的代码如下,加粗部分是修改内容:

SOAP_FMAC3 struct
SOAP_ENV__Header * SOAP_FMAC4 soap_in_SOAP_ENV__Header(struct soap *soap, const
char *tag, struct SOAP_ENV__Header *a, const char *type)

{

       
size_t
soap_flag_wsse__security = 1;

       
size_t soap_flag_ns1__customerId = 1;

       
if (soap_element_begin_in(soap, tag,
0, type))

               
return NULL;

       
a = (struct SOAP_ENV__Header
*)soap_id_enter(soap, soap->id, a, SOAP_TYPE_SOAP_ENV__Header, sizeof(struct
SOAP_ENV__Header), 0, NULL, NULL, NULL);

       
if (!a)

               
return NULL;

       
soap_default_SOAP_ENV__Header(soap, a);

       
if (soap->body &&
!*soap->href)

       
{

               
for (;;)

               
{

                       

if ( soap_flag_wsse__security ) {

                               
if ( soap_element_begin_in(soap,
NULL, 0, NULL) )

                   
                    
return NULL;

                               
if ( soap_element_begin_in(soap,
NULL, 0, NULL) )

                                       
return NULL;

                               
if ( soap_in_string(soap,
"", &a->wsse__username, "")

 
                                      
&&
soap_in_string(soap, "", &a->wsse__password, "") ) {

                                       
soap_flag_wsse__security--;

                                       
soap_element_end_in(soap,
NULL);

                    

                   
soap_element_end_in(soap, NULL);

                               
}

                       

}

                       
soap->error =
SOAP_TAG_MISMATCH;

                       
if
(soap_flag_ns1__customerId && (soap->error == SOAP_TAG_MISMATCH ||
soap->error == SOAP_NO_TAG))

                               
if
(soap_in_string(soap, "ns1:customerId", &a->ns1__customerId,
"xsd:string"))

                               
{      
soap_flag_ns1__customerId--;

                                       
continue;

                               
}

                       
if (soap->error ==
SOAP_TAG_MISMATCH)

                               
soap->error
= soap_ignore_element(soap);

                       
if (soap->error ==
SOAP_NO_TAG)

                    
           
break;

                       
if (soap->error)

                               
return NULL;

               
}

               
if (soap_element_end_in(soap,
tag))

                       
return NULL;

       
}

       
else

       
{      
a = (struct SOAP_ENV__Header *)soap_id_forward(soap,
soap->href, (void*)a, 0, SOAP_TYPE_SOAP_ENV__Header, 0, sizeof(struct
SOAP_ENV__Header), 0, NULL);

               
if (soap->body &&
soap_element_end_in(soap, tag))

                       
return NULL;

      
 
}

       
return a;

}

 

 

服务端程序如下,到这里就没什么难度了,基本上与上一节的服务端结构差不多,注意要把几个
pem
证书拷贝过来(因为
usernametoken
方式通常都是基于
HTTPS
的),
echo
接口和
show_version
接口都要编写:

 

 

服务端程序保存为
secure_echo_server.c
,编译命令是

gcc -DWITH_OPENSSL -O2 -o secure_echo_server secure_echo_server.c
soapC.c soapServer.c ../../stdsoap2.c -I../.. -L../.. -lgsoap -lssl –lcrypto

 

运行测试,在
6883
端口启动服务端,然后执行客户端

-bash-3.2$
./show_version
roy liang G06164


username   
: roy

password   
: liang

customer id : G06164

Username token (text) test server version 1.0

 

-bash-3.2$
./secure_echo
roy liang G06164 hello


username   
: roy

password   
: liang

customer id : G06164

message    
: hello

hello

 

以上就是
gSOAP
实现
Usernametoken
认证的方法,而且是通过自定义
SOAP header
实现的。个人认为,与使用
wsse
插件相比,这种方法更为简单直接。

 

另外,本案例的方法适用于以明文传送密码的情况,如果需要以摘要
(digest)
形式传送密码,请参考
plugin
目录
wsseapi.c
里面的

抱歉!评论已关闭.