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!