WS-Security
WS-Security is a standard for adding security to SOAP Web service message exchanges (see
Resources). It uses a SOAP message-header element to attach the security information to messages, in the form of
tokens conveying different types of claims (which can include names, identities, keys, groups, privileges, capabilities, and so on) along with encryption and digital-signature information. WS-Security supports multiple formats for tokens, multiple trust domains, multiple signature formats, and multiple encryption technologies, so in most cases the header information needs to include specific format and algorithm identification for each component. The added information can result in a complex structure for the header information, as shown in the (heavily edited)
Listing 1 — a sample message with signing and encryption:
Listing 1. Sample message with signing and encryption
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" ...>
<soap:Header>
<wsse:Security soap:mustUnderstand="1">
<wsu:Timestamp wsu:Id="Timestamp-d2e3c4aa-da82-4138-973d-66b596d66b2f">
<wsu:Created>2006-07-11T21:59:32Z</wsu:Created>
<wsu:Expires>2006-07-12T06:19:32Z</wsu:Expires>
</wsu:Timestamp>
<wsse:BinarySecurityToken ValueType="...-x509-token-profile-1.0#X509v3"
EncodingType="...-wss-soap-message-security-1.0#Base64Binary"
xmlns:wsu="...oasis-200401-wss-wssecurity-utility-1.0.xsd"
wsu:Id="SecurityToken-faa295...">MIIEC56MQswCQY...</wsse:BinarySecurityToken>
<xenc:EncryptedKey xmlns:xenc="http://www.w3.org/2001/04/xmlenc#">
<xenc:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#rsa-1_5" />
<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
<wsse:SecurityTokenReference>
<wsse:KeyIdentifier ValueType=
"...#X509SubjectKeyIdentifier">LlYsHyhNnOVA9Aj7...</wsse:KeyIdentifier>
</wsse:SecurityTokenReference>
</KeyInfo>
<xenc:CipherData>
<xenc:CipherValue>g+A2WJhsoGBKUydZ9Za...</xenc:CipherValue>
</xenc:CipherData>
<xenc:ReferenceList>
<xenc:DataReference URI="#EncryptedContent-ba0556c3-d443-4f34-bcd1-14cbc32cd689" />
</xenc:ReferenceList>
</xenc:EncryptedKey>
<Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
<SignedInfo>
<ds:CanonicalizationMethod
Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"
xmlns:ds="http://www.w3.org/2000/09/xmldsig#" />
<SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1" />
<Reference URI="#Id-c80f735c-62e9-4001-8094-702a4605e429">
<Transforms>
<Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" />
</Transforms>
<DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" />
<DigestValue>lKjc5nyLQDZAIu/hZb4B6mLquow=</DigestValue>
</Reference>
...
</SignedInfo>
<SignatureValue>TiLmWvlz3mswinLVQn58BgYS0368...</SignatureValue>
<KeyInfo>
<wsse:SecurityTokenReference>
<wsse:Reference URI="#SecurityToken-faa295..."
ValueType="...-x509-token-profile-1.0#X509v3" />
</wsse:SecurityTokenReference>
</KeyInfo>
</Signature>
</wsse:Security>
</soap:Header>
<soap:Body wsu:Id="Id-8db9ff44-7bef-4737-8091-cdac51a34db8">
<xenc:EncryptedData Id="EncryptedContent-ba05..."
Type="http://www.w3.org/2001/04/xmlenc#Content"
xmlns:xenc="http://www.w3.org/2001/04/xmlenc#">
<xenc:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#aes128-cbc"/>
<xenc:CipherData>
<xenc:CipherValue>mirmi0KuFEEI56eu2U3cICz...</xenc:CipherValue>
</xenc:CipherData>
</xenc:EncryptedData>
</soap:Body>
</soap:Envelope> |
In this article, you'll see some simple examples of WS-Security headers, with only a single token. The next article in the series will go further in discussing the type of complex structure shown in
Listing 1.
WS-Security applies to actual SOAP message exchanges. The service implementation can verify that WS-Security has been properly applied to incoming messages, but clients need to know in advance what they must implement in order to use the service. With the complexity of WS-Security and the number of options supported, it can be difficult just to use a text description for this purpose, and manual configuration of WS-Security handling can cause errors. WS-Policy is a general-purpose structure for specifying extension requirements for Web services, and WS-SecurityPolicy is an extension of WS-Policy specifically for WS-Security support. Together these two standards support describing WS-Security requirements in machine-readable form. The WS-Policy and WS-SecurityPolicy information can be used alone or embedded directly inside Web Services Description Language (WSDL) documents, so that Web service frameworks can configure themselves automatically to a service's requirements.
Introducing Rampart
Rampart is the Axis2 security module, supporting WS-Security, WS-SecurityPolicy, WS-SecureConversation, and WS-Trust. In this article, you'll only see Rampart's WS-Security and WS-SecurityPolicy functions; later articles will give you a look at the other features.
Because Rampart is implemented as a module (actually a pair of modules — rampart.mar and rahas.mar), it plugs into the Axis2 processing framework and does its job by intercepting messages at particular points in the inbound and outbound processing, checking or making changes to the messages as appropriate.
Installing Rampart
Rampart comes with several .jar files (in the distribution's lib directory), along with a pair of .mar module files (in the dist directory). You must add the .jar files to your classpath in order to use Rampart with Axis2, and you must add the .mar files to either your classpath or an Axis2 repository structure.
The easiest way of handling the Rampart .jar and .mar files is to add them into your Axis2 installation. You can just directly copy the .jar files from the Rampart lib directory into your Axis2 lib directory, and the .mar files from the Rampart dist directory into your Axis2 repository/modules directory. (You can also use the Ant build.xml in the Rampart samples directory to copy the files across to your Axis2 installation. Just set the
AXIS2_HOME
environmental variable to your Axis2 installation directory and run
ant
from a console open to the Rampart sample directory.)
For many WS-Security features, you also need to add the Bouncy Castle security provider to your JVM security configuration and the Bouncy Castle .jar to your Axis2 installation. This step — not needed for the
UsernameToken
you'll learn about in this article — is required for other security features to be covered later in the series. Because of patent issues with some of the security algorithms, the Bouncy Castle .jar is a separate download from Rampart (see
Resources). Download the appropriate version of the .jar for your Java runtime, and add the .jar to the Axis2 lib directory. You then need to modify your Java installation's security policies to use the Bouncy Castle code by adding a line to the java.security file found in your Java runtime's lib/security directory. Look for the section of the file with several different
security.provider
lines, and add the following line:
security.provider.99=org.bouncycastle.jce.provider.BouncyCastleProvider |
The ordering of the
security.provider
lines in the file doesn't matter, but it's a good idea to add this after the other lines for predefined security provider implementations.
To use the Rampart code in an Axis2 server installation, you need to create a new axis2.war file, one that includes the added Rampart .jar and .mar files. You can use the Ant build.xml provided in the webapp directory to create axis2.war, provided you make one change: delete the line
<exclude name="axis2-codegen*.jar"/>
near the end of the file. Then open a console to the Axis2 webapp directory and run
ant
. After the build.xml runs, you can find the created axis2.war Web application in the Axis2 installation dist directory.
A sample application
The application provided in the example code (see
Downloads) is based on one I used in "
Axis2 Data Binding" to demonstrate data-binding alternatives for Axis2. For this article and the following ones on Axis2 WS-Security support, I've trimmed it down to just three operations:
getBook
,
addBook
, and
getBooksByType
. To keep things simple, only the Axis Data Binding (ADB) version of the code is provided, but this is not a requirement of working with WS-Security in Axis2 — Rampart implements WS-Security at a level that's independent of the data-binding technique your code uses, so it works with all forms of data binding supported by Axis2.
The root directory of the example code is jws04code. Inside this directory, you'll find Ant build.xml and build.properties files, the library.wsdl file giving the service definition for the example application, a log4j.properties file used to configure client-side logging, and several property-definition XML files (all named
XXX-policy-client.xml or
XXX-policy-server.xml). The build.properties file configures the operation of the example application.
Listing 2 shows the supplied version of this properties file:
Listing 2. Supplied build.properties file
# set axis-home to your Axis2 installation directory
axis-home=PATH_TO_AXIS2_INSTALLATION
# set the connection protocol to be used to access services (http or https)
protocol=http
# set the name of the service host
host-name=localhost
# set the port for accessing the services (change this for monitoring)
host-port=8080
# set the base path for accessing all services on the host
base-path=/axis2/services/
# set the name of the policy file to be used by the client
client-policy=plain-policy-client.xml
# set the name of the policy file to be used by the server
server-policy=plain-policy-server.xml |
Before trying out the examples, you need to edit the build.properties file and set the actual path to your Axis2 installation (with Rampart added, as discussed in the preceding section). If you're using a different host or port number for your server, you also need to modify the
host-name
and
host-port
values. I'll discuss the remaining values later in this article.
Giving WS-Security a try
WS-Security defines several types of security tokens (including tokens that are part of the core specification, and those defined by
profiles as plug-in extensions to the specification), with many options for how the tokens are constructed and used. The point of this article is the configuration and use of Rampart with Axis2, so I'll just use the simplest useful token as an example: the
UsernameToken
, defined by the
UsernameToken
profile.
UsernameToken
WS-SecurityPolicy
The purpose of a
UsernameToken
is just to convey username and password information as part of the WS-Security headers. The most basic form of
UsernameToken
sends both the username and password as plain text. This isn't optimal from a security standpoint (though there's nothing wrong with using this approach over secure connections), but it's easy to see what's being sent, making it a useful starting point.
The WS-SecurityPolicy configuration for a
UsernameToken
sent as text can be as simple as shown in
Listing 3. This policy (shown here with one line split in two to fit the page width — not valid for actual use) consists of a standard WS-Policy wrapper (the elements using the
wsp
prefix) around a WS-SecurityPolicy
UsernameToken
assertion.
Listing 3. WS-SecurityPolicy for plain-text UsernameToken
<wsp:Policy wsu:Id="UsernameToken" xmlns:wsu=
"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy">
<wsp:ExactlyOne>
<wsp:All>
<sp:SupportingTokens
xmlns:sp="http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702">
<wsp:Policy>
<sp:UsernameToken sp:IncludeToken="http://docs.oasis-open.org/
ws-sx/ws-securitypolicy/200702/IncludeToken/AlwaysToRecipient"/>
</wsp:Policy>
</sp:SupportingTokens>
</wsp:All>
</wsp:ExactlyOne>
</wsp:Policy> |
The
UsernameToken
in
Listing 3 uses an
IncludeToken
attribute to specify the type of message flow that is to include the token — in this case, all message flows that go from the initiator of a request (that is, the client) to the recipient of a request (the server). You could use other defined values for the
IncludeToken
attribute to specify other uses of the token, but for a
UsernameToken
this is generally the only value that makes sense.
Applying the policy
WS-Policy and WS-SecurityPolicy are designed to support embedding within WSDL service definitions. References are used to associate a policy to one or more
<wsdl:binding>
,
<wsdl:binding>
/
<wsdl:operation>
, or
<wsdl:message>
definitions. Axis2 1.4.X implements preliminary handling for policies embedded in WSDL, but as of Axis2 1.4.1 the implementation is not yet robust. This article instead attaches the policies directly to the client and server in order to be compatible with the 1.4.1 code.
Server-side policy handling
For the server side, you apply a policy by adding it into the services.xml configuration file included in each Axis2 .aar service archive. The policy can be added directly as the child of a
<service>
element to apply to all operations defined by that service. You also need to add a
<module>
element to services.xml in order to tell Axis2 that the Rampart module must be included in the configuration for the service.
Listing 4 is an edited version of the services.xml used by the example application, with the added module reference and policy information shown in bold:
Listing 4. services.xml with embedded policy
<serviceGroup>
<service name="library-username">
<messageReceivers>
<messageReceiver
class="com.sosnoski.ws.library.adb.LibraryUsernameMessageReceiverInOut"
mep="http://www.w3.org/ns/wsdl/in-out"/>
</messageReceivers>
<parameter
name="ServiceClass">com.sosnoski.ws.library.adb.LibraryUsernameImpl</parameter>
<parameter name="useOriginalwsdl">true</parameter>
<parameter name="modifyUserWSDLPortAddress">true</parameter>
<operation mep="http://www.w3.org/ns/wsdl/in-out" name="getBook"
namespace="http://ws.sosnoski.com/library/wsdl">
<actionMapping>urn:getBook</actionMapping>
<outputActionMapping>http://.../getBookResponse</outputActionMapping>
</operation>
...
<module ref="rampart"/>
<wsp:Policy xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy"
xmlns:wsu="http://.../oasis-200401-wss-wssecurity-utility-1.0.xsd"
wsu:Id="UsernameToken">
<wsp:ExactlyOne>
<wsp:All>
<sp:SupportingTokens
xmlns:sp="http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702">
<wsp:Policy>
<sp:UsernameToken
sp:IncludeToken="http://.../IncludeToken/AlwaysToRecipient">
<wsp:Policy>
<sp:HashPassword/>
</wsp:Policy>
</sp:UsernameToken>
</wsp:Policy>
</sp:SupportingTokens>
<ramp:RampartConfig xmlns:ramp="http://ws.apache.org/rampart/policy">
<ramp:passwordCallbackClass>...PWCBHandler</ramp:passwordCallbackClass>
</ramp:RampartConfig>
</wsp:All>
</wsp:ExactlyOne>
</wsp:Policy>
</service>
</serviceGroup> |
If you compare the embedded policy in
Listing 4 with the basic policy in
Listing 3, you'll see one addition — a
<ramp:RampartConfig>
element. This element provides Rampart-specific extensions to the policy information, in this case giving the name of a class to be used for handling password callbacks. The callback is how your server code can verify the username-and-password combination supplied by the client on a request.
Listing 5 shows the actual implementation of the callback class, as used for the plain-text password. In this case, both the username and password are supplied to the callback, and all the callback needs to do is verify the combination. If the username and password match the expected values, this just returns; otherwise, it throws an exception to indicate the error.
Listing 5. Password callback code
import org.apache.ws.security.WSPasswordCallback;
public class PWCBHandler implements CallbackHandler
{
public void handle(Callback[] callbacks)
throws IOException, UnsupportedCallbackException {
for (int i = 0; i < callbacks.length; i++) {
WSPasswordCallback pwcb = (WSPasswordCallback)callbacks[i];
String id = pwcb.getIdentifer();
if (pwcb.getUsage() == WSPasswordCallback.USERNAME_TOKEN_UNKNOWN) {
// used when plain-text password in message
if (!"libuser".equals(id) || !"books".equals(pwcb.getPassword())) {
throw new UnsupportedCallbackException(callbacks[i], "check failed");
}
}
}
}
} |
For a real application, you'd naturally want to use some other mechanism (such as a database or an external security mechanism) to verify the username and password combination. The callback technique lets you use any verification technique you want as an extension of the Rampart security handling.
Client-side configuration
To use Rampart for your client code, you first need to have the module available for use with Axis2. You can do this by configuring an Axis2 repository structure for the client, but it's generally easier just to include the rampart.mar module file (and any other modules you need to use) in your classpath. The supplied example uses the classpath approach.
You then need to configure the security policy and any related parameters for the client. The easiest way to handle this configuration is to set values directly on the service stub.
Listing 6 shows how the configuration is done in the example code:
Listing 6. Client configuration
/**
* Load policy file from classpath.
*/
private static Policy loadPolicy(String name) throws XMLStreamException {
ClassLoader loader = WebServiceClient.class.getClassLoader();
InputStream resource = loader.getResourceAsStream(name);
StAXOMBuilder builder = new StAXOMBuilder(resource);
return PolicyEngine.getPolicy(builder.getDocumentElement());
}
public static void main(String[] args) throws IOException, XMLStreamException {
// check for required command line parameters
if (args.length < 4) {
System.out.println("Usage:\n java " +
"com.sosnoski.ws.library.adb.WebServiceClient protocol host port path");
System.exit(1);
}
// create the client stub
String target = args[0] + "://" + args[1] + ":" + args[2] + args[3];
System.out.println("Connecting to " + target);
LibraryUsernameStub stub = new LibraryUsernameStub(target);
// configure and engage Rampart
ServiceClient client = stub._getServiceClient();
Options options = client.getOptions();
options.setProperty(RampartMessageData.KEY_RAMPART_POLICY,
loadPolicy("policy.xml"));
options.setUserName("libuser");
options.setPassword("books");
client.engageModule("rampart");
|
The configuration portion is the final code block in Listing 6. This gets the
org.apache.axis2.client.ServiceClient
instance from the created stub and sets the policy information (loaded from the classpath) and username/password in the client options. It then engages the Rampart module in the Axis2 configuration used by the client. Once this is done, you can use the stub to access the service just as you would without WS-Security, and Rampart adds the
UsernameToken
automatically to each request.
Confirming the results
With Ant installed, you can just run
ant
from a console open to the example code directory to build both client and server code. You can then deploy the created library-username.aar file to your Axis2 server installation (one that includes the Rampart .jars and .mars, of course), and try out the client by entering
ant run
at the console. If everything is set up correctly, you should see the output shown in
Figure 1:
Figure 1. Console output when running application
Just running the client with the server doesn't show you what's happening, of course. You can use a tool such as TCPMon to act as an intermediary between the client and server and capture the message exchange to see the WS-Security
UsernameToken
in action (see
Resources). To do this, you'd first need to get TCPMon set up and accepting connections from the client on one port, which it then forwards to the server running on a different port (or a different host). You can then edit the build.properties file and change the
host-port
value to the listening port for TCPMon. If you again enter
ant run
at the console, you should then see the messages being exchanged.
Listing 7 shows a sample client-message capture:
Listing 7. Client message with UsernameToken
<?xml version='1.0' encoding='UTF-8'?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
<soapenv:Header>
<wsse:Security xmlns:wsse="...wss-wssecurity-secext-1.0.xsd"
soapenv:mustUnderstand="1">
<wsse:UsernameToken xmlns:wsu="...wss-wssecurity-utility-1.0.xsd"
wsu:Id="UsernameToken-1815911473">
<wsse:Username>libuser</wsse:Username>
<wsse:Password Type="...wss-username-token-profile-1.0#PasswordText"
>books</wsse:Password>
</wsse:UsernameToken>
</wsse:Security>
</soapenv:Header>
<soapenv:Body>
<ns2:getBooksByType xmlns:ns2="http://ws.sosnoski.com/library/wsdl">
<ns2:type>scifi</ns2:type>
</ns2:getBooksByType>
</soapenv:Body>
</soapenv:Envelope> |
Securing UsernameToken
A basic plain-text
UsernameToken
doesn't provide much security directly, because both the username and the corresponding password are visible to anyone able to monitor a message. If you use an encrypted communication channel, this isn't a real problem — as long as the channel encryption is solid, no outside party can monitor a message. WS-SecurityPolicy conveniently defines a way to require the use of an encrypted channel, as shown in
Listing 8 (again with a line split to fit page width — see the example code package's secure-policy-server.xml file for the real policy):
Listing 8. Policy requiring HTTPS connection
<wsp:Policy wsu:Id="UsernameToken" xmlns:wsu=
"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy">
<wsp:ExactlyOne>
<wsp:All>
<sp:TransportBinding xmlns:sp="http://schemas.xmlsoap.org/ws/2005/07/securitypolicy">
<wsp:Policy>
<sp:TransportToken>
<wsp:Policy>
<sp:HttpsToken RequireClientCertificate="false"/>
</wsp:Policy>
</sp:TransportToken>
</wsp:Policy>
</sp:TransportBinding>
<sp:SupportingTokens
xmlns:sp="http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702">
<wsp:Policy>
<sp:UsernameToken sp:IncludeToken="http://docs.oasis-open.org/
ws-sx/ws-securitypolicy/200702/IncludeToken/AlwaysToRecipient"/>
</wsp:Policy>
</sp:SupportingTokens>
</wsp:All>
</wsp:ExactlyOne>
</wsp:Policy>
|
The portion of
Listing 8 shown in bold is the added part, consisting of a
<sp:TransportBinding>
element and nested
<sp:HttpsToken>
element. The
<sp:HttpsToken>
element says that a secure HTTPS connection must be used in communicating with the service. If you try building the service .aar with this policy (by changing the
server-policy
value in build.properties to
secure-policy-server.xml
, and then running
ant build-server
) and deploying it, you'll see that Rampart enforces this policy requirement, rejecting any normal HTTP connections.
If you want to try out an HTTPS connection to the service you can do so, but you first need to configure your Web server to support HTTPS. (Tomcat has good instructions for this, at /tomcat-docs/ssl-howto.html.) You also need to change the
protocol
value in build.properties to
https
, and if you're using a self-signed certificate for the Web server, you need to pass a trust store to the client when running the Ant
test
target. The supplied build.xml has a commented-out line to do this, so you can just uncomment the line and set the location of the appropriate trust store file on your system.
Another way of making
UsernameToken
more secure works even over unencrypted links. This method uses a
digest value computed over a string made up of two other text values combined with the password. One of the text values, the
nonce, is a random value generated by the sender for each request. The other, the created timestamp, is just the time at which the sender created the
UsernameToken
. Both these values are included in the
UsernameToken
as plain text. When properly used by both client and server, the combination of these values with the password in the digest makes it possible for the server to verify that the correct password was used when generating the digest, while making it difficult for any outside party to fake a valid password.
Listing 9 gives a policy example for using the digest password, followed by an actual capture from a message using the digest password (both reformatted to fit page width — see the hash-policy-client.xml file for the real policy). The differences from the original policy are again shown in bold.
Listing 9. Policy using password digest, and sample message
<wsp:Policy wsu:Id="UsernameToken" xmlns:wsu=
"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy">
<wsp:ExactlyOne>
<wsp:All>
<sp:SupportingTokens
xmlns:sp="http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702">
<wsp:Policy>
<sp:UsernameToken sp:IncludeToken="http://docs.oasis-open.org/
ws-sx/ws-securitypolicy/200702/IncludeToken/AlwaysToRecipient">
<wsp:Policy>
<sp:HashPassword/>
</wsp:Policy>
</sp:UsernameToken>
</wsp:Policy>
</sp:SupportingTokens>
</wsp:All>
</wsp:ExactlyOne>
</wsp:Policy>
<?xml version='1.0' encoding='UTF-8'?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
<soapenv:Header>
<wsse:Security xmlns:wsse=".../oasis-200401-wss-wssecurity-secext-1.0.xsd"
soapenv:mustUnderstand="1">
<wsse:UsernameToken xmlns:wsu="...wss-wssecurity-utility-1.0.xsd"
wsu:Id="UsernameToken-1421876889">
<wsse:Username>libuser</wsse:Username>
<wsse:Password Type="...wss-username-token-profile-1.0#PasswordDigest"
>/Wt/2yDdZwa8a5qd7U70hrp29/w=</wsse:Password>
<wsse:Nonce>4ZQz5ytME/RXfChuKJ03iA==</wsse:Nonce>
<wsu:Created>2009-03-17T11:20:57.467Z</wsu:Created>
</wsse:UsernameToken>
</wsse:Security>
</soapenv:Header>
<soapenv:Body>
<ns2:getBooksByType xmlns:ns2="http://ws.sosnoski.com/library/wsdl">
<ns2:type>scifi</ns2:type>
</ns2:getBooksByType>
</soapenv:Body>
</soapenv:Envelope>