Implementing WS-Security in a SOAP::Lite client
SOAP::Lite does not contain support for WS-Security, especially for the userToken profile as specified in http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0.pdf. The good news is, that there is a quite simple way to integrate it. This will be shown in the next lines.
The userToken profile requires additional header data to be sent. An example:
<soap:Header>
<wsse:Security
xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"
xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
soap:mustUnderstand="1">
<wsse:UsernameToken>
<wsse:Username>aUserName</wsse:Username>
<wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest"
>x6aFVLxxyv10N5P6D/XL4t3578A=</wsse:Password>
<wsse:Nonce>YSBzZXF1ZW5jZSB2YWx1ZSBvZiA4OTA=</wsse:Nonce>
<wsu:Created>2009-05-22T15:36:13Z</wsu:Created>
</wsse:UsernameToken>
</wsse:Security>
</soap:Header>
To create data which get serialized into the soap header, one have to use SOAP::Header instead of SOAP::Data and pass it to the webservice call as an additional parameter, e.g.:
$proxy->getSimpleResult($data,SOAP::Header->new(...))
There is no much magic in creating the wsse:Security element, one issue should be mentioned:
The specification requires the client to provide a countermeasure for replay attacs. Beside a username and a
(hashed) password, the client has to provide a timestamp and a random value called "Nonce". The client does
not hash the password alone, but a concatenated string consisting of passwort, timestamp and nonce.
One easy way to create a nonce is to use a sequence generator, e.g.:
sub create_generator {
my ($name,$start_with) = @_;
my $i = $start_with;
return sub { $name . ++$i; };
}
*default_nonce_generator = create_generator( "a value of ", int(1000*rand()) );
With some supporting subs, the wsse:Security element can be generated as follows:
use Time::Local;
use Digest::SHA1;
use MIME::Base64;
sub xml_quote {
my ($value) = @_;
$value =~ s/&/&/;
$value =~ s/</</;
$value =~ s/>/>/;
$value;
}
sub _complex_type {
my ($name,@childs) = @_;
my $data = SOAP::Data->new( name => $name );
$data->value( \SOAP::Data->value(@v));
$data;
}
sub _typeless {
my ($name,$value) = @_;
my $data = SOAP::Data->new( name => $name );
$value = xml_quote($value);
$data->value( $value );
$data->type( "" );
$data;
}
sub timestamp {
my ($sec,$min,$hour,$mday,$mon,$year,undef,undef,undef) = gmtime(time);
$mon++;
$year = $year + 1900;
return sprintf("%04d-%02d-%02dT%02d:%02d:%02dZ",$year,$mon,$mday,$hour,$min,$sec);
}
sub create_generator {
my ($name,$start_with) = @_;
my $i = $start_with;
return sub { $name . ++$i; };
}
*default_nonce_generator = create_generator( "a value of ", int(1000*rand()) );
sub ws_authen {
my($username,$passwort,$nonce_generator) = @_;
if(!defined($nonce_generator)) {
$nonce_generator = \&default_nonce_generator;
}
my $nonce = $nonce_generator->();
my $timestamp = timestamp();
my $pwDigest = Digest::SHA1::sha1( $nonce . $timestamp . $passwort );
my $passwortHash = MIME::Base64::encode_base64($pwDigest,"");
my $nonceHash = MIME::Base64::encode_base64($nonce,"");
my $auth = SOAP::Header->new( name => "wsse:Security" );
$auth->attr( {
"xmlns:wsse" => "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd",
"xmlns:wsu" => "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd",
});
$auth->mustUnderstand(1);
$auth->value( \SOAP::Data->value(
_complex_type ( "wsse:UsernameToken",
_typeless("wsse:Username",$username),
_typeless("wsse:Password",$passwortHash)->attr({
"Type" => "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest"
}),
_typeless("wsse:Nonce",$nonceHash),
_typeless("wsu:Created",$timestamp),
)
)
);
$auth;
}
Finally, to add the WS-Security data to your call, just the result of a ws_authen() function call to parameter list:
$proxy->getSimpleResult($data,ws_authen->($username,$password))
Enjoy it!
