If you want to use an XmlSerializer with your SoapEnvelope, you can. The following code sheds some light on what is really happening under the covers of the Web services stacks:.
Since your SoapEnvelope is an XmlDocument, you can construct any number of XmlReader or XmlWriter objects on top of it and pass them to the Serialize or Deserialize methods, respectively. Now that you have a better idea about how XML messages and objects are related, let's take a look at some more complex messages and see where custom serialization becomes necessary.
The default behavior of XML serialization through XmlSerializer can take you through many cases, but sometimes you need to take more control over the way in which your objects are serialized. The class provided here illustrates two potential problems commonly encountered when working with XML serialization of objects:. In the first case, you have a StringCollection that represent names or abbreviations of the states for example, North Carolina.
There are a number of ways that you could serialize such a list but which one should the XmlSerializer choose?
In this case, you would need to provide more information. The second issue is a bit trickier because one of the members of the class is of the System.
Uri type, and the System. Uri class has no default constructor a public constructor that accepts no parameters. Classes that have no such constructor cannot be deserialized from XML because the XmlSerializer class does not know how to instantiate one, and if it can't be deserialized, XmlSerializer will also refuse to serialize it.
Because of this, the XmlSerializer would also fail to serialize the MyService class because ServiceUri happens to be a public member. The default behavior of XML serialization can take you through most cases, but sometimes you need to take more control over the way in which objects are serialized. In addition to handling messages as raw XML, as you saw previously, there are three other methods at your disposal that you can use to shape the content of your Web services messages: attributes, IXmlSerializable, and IXmlElement.
Implementing IXmlElement. If you want, you can implement IXmlElement in your own classes and achieve much the same results as if you had derived from OpenElement. If you do choose to implement IXmlElement yourself instead of deriving from a WSE base class, you must remember to provide a special constructor for your object.
This special constructor must take as a parameter an instance of the XmlElement class, the same parameter that you would pass to the IXmlElement. LoadXml method. This constructor can be nothing more than a pass-through to the LoadXml method, as shown here:.
Without this constructor, your message objects will appear to be serialized to XML successfully, but they will fail when WSE attempts to deserialize them. Instead of creating the object with a default empty constructor and calling LoadXml, it simply constructs the object using the XmlElement.
There are times, of course, when you need to step in and control the behavior of the serialization process.
The first method that a developer can use to control the XML serialization process is through attributes. Serialization namespace provides a number of attributes that can be used to change normal serialization behavior. Figure 3 lists a few of the more common attributes and the effect that each of them has on the serialization process. The full list is available in the documentation for the System.
Serialization namespace. Whenever a class that uses one or more of these attributes is serialized, the XmlSerializer class uses reflection to determine what attributes are being used and how they should be interpreted.
In the class shown here, I have added attributes to the previously defined class in order to declaratively shape the XML serialization, as shown in Figure 4. The XmlRootAttribute describes the MyService class as a whole, the XmlArrayAttribute describes how the States collection should be serialized as a list of items, and the XmlIgnoreAttribute states that the Uri member ServiceUri should be skipped completely during serialization.
With those changes, the class can now be successfully passed to XmlSerializer. The XML that is created when this object is serialized looks like this:. Although using the attributes to shape XML messages is straightforward, it cannot fix every problem. Most obviously, there is still no serialized information about the Uri member, which may or may not be a problem depending on the class design. On the other hand, suppose you did not like how the States list was serialized.
You may still be unable to get what you want from attributes. For example, there are no attributes available that can tell the XmlSerializer to write an array of strings as an XML type of xsd:list, as shown in the following:. This is a common enough construct in XML and one that is well suited to representing a list of items, but that does not require the overhead of an element for each item.
For that reason, elements of the xsd:list type appear in many of the messages used for the latest Web services specifications, like WS-Discovery.
In order to format the list of strings as an xsd:list, you would need to take more control over the XML serialization process. Another way to take more control is by implementing the IXmlSerializable interface, which is a mechanism for overriding the XML serialization process:. The IXmlSerializable interface has three methods: two for serialization and one for schema generation. The WriteXml and ReadXml methods are invoked whenever the object in question is being serialized or deserialized.
These methods take an XmlWriter instance and XmlReader instance respectively, types which are probably familiar to developers who have been working with XML in the. NET Framework. Just as you could use familiar XmlDocument methods when working at the SoapEnvelope level, you can rely on methods like XmlWriter.
WriteStartElement in order to create your message. The GetSchema method returns an instance of System. XmlSchema, the. This method is called whenever schema information is needed about the object, such as during the construction of WSDL for the Web service.
Using this method takes care of some of the issues you might encounter with XML serialization. For example, if your object implements IXmlSerializable, you can provide public properties and members that are non-serializable data types without generating exceptions from XmlSerializer.
This would allow you to take care of the Uri member that you could not serialize earlier. Figure 5 shows an example of how you can serialize a collection of strings as an xsd:list type.
This code is written to serialize a generic list, but the SerializableList class can serve as a base class, and by initializing the ListName and ListNamespace members in the constructor, you can further customize your serialization. It would seem that implementing the three methods of IXmlSerializable would take care of every issue developers might have with their messages, but there are a couple of problems that prevent that from being the case.
Originally, the IXmlSerializable interface was not recommended for use outside of the. NET Framework classes. The documentation stated that it was meant for internal use only, and so developers who chose to use it knew that it was a technical risk. Since then, it has become clear that its use will be supported in the. NET Framework 2. However, you will likely encounter problems with its use in the current version of. NET, especially when it comes to working with Web services.
In order for a message to be used by the service, it must be described as part of the description of the service itself, namely its WSDL document. The WSDL documents contain embedded schema information about messages, and this is where the problem appears. Through IXmlSerializable, an object can only return a complete schema not a portion through the GetSchema method, and the Framework has problems with combining schemas when generating the service description.
If your message object is of a different XML namespace than the service, duplicate schema information is generated in the subsequent WSDL file one for the correct XML namespace, and one for the service. If your message object is of the same XML namespace as the service, then the WSDL generation fails because the document can't contain the same namespace twice. In either of these cases, you do not get the behavior that you are looking for. It is worth mentioning that in the.
For more information on this change and on IXmlSerializable in the. This interface is defined in the Microsoft. Xml namespace, and by implementing this interface, an object can override the XML serialization process. A definition of the IXmlElement interface is shown in the following code snippet:.
Objects that have a complex structure and need to be represented in a message implement IXmlElement in order to handle the serialization process in a custom manner. The best example of one of these objects is the EndpointReference class, which I discussed earlier in the article, and is used throughout the entire object model.
If I try to define a Web service method that returns an EndpointReference, or even create my own message object that contains a member of the EndpointReference type, the service will fail. The reason for this is that EndpointReference does not have a constructor that takes no parameters.
If a class has no empty constructor, then it cannot be serialized by the Framework because it does not know how to create one. It first checks to see if an object implements IXmlElement before passing it to an instance of XmlSerializer.
If it does implement the interface, then it uses the LoadXml and GetXml methods to handle serialization; if it doesn't, then the object goes the same route as all of the methods, which is through XmlSerializer. It may look as though IXmlElement is the answer to handling complex messages within WSE, but there are still two problems. First, if you read the WSE 2. NET Framework infrastructure and is not intended to be used directly from your code. Second, there is no method in IXmlElement for providing schema information about your custom object.
In some cases, you could probably live without describing your object in schema, but remember that you are dealing with Web services in this case. I've added a new class to the Web service project called WseHelpers, which contains a single method called GetUsernameToken. This method expects the caller to supply it with a SoapContext object that it will inspect for a UsernameToken element, which it returns to the caller when found.
The method throws an exception if it doesn't find a UsernameToken see Figure 1. You can now use this method to require a UsernameToken in all messages entering CalcMortage by simply calling it at the beginning of the method, as shown here:. After making this change, you won't be able to invoke the operation without supplying a UsernameToken.
Now when you run the client you'll get the "Missing security token" exception. Now that the CalcMortgage operation requires a UsernameToken, you need to update the client application to provide one. WSE 2. You only have to instantiate a UsernameToken object and add it to the proxy's SoapContext object, as illustrated here:.
Then, when you invoke any operations through the Web service proxy class, the WSE runtime will automatically add the appropriate security tokens to the SOAP message. In this case, the password is sent in plain text. You can specify how you want the password to be sent when you instantiate the UsernameToken object. I used the PasswordOptions. SendPlainText option in this example. The other two options allow you to send a hashed password or no password at all.
It's obviously not a good idea to send the password in plain text unless you plan to send the SOAP message over a secure channel or to encrypt the UsernameToken element more on this shortly.
Now when you run the client application supplying a UsernameToken, you won't get the "Missing security token" exception anymore.
You must, however, supply the credentials of a valid user account or you'll get the following exception: "The security token could not be authenticated or authorized.
If the call fails, WSE 2. On the other hand, if the call is successful, WSE 2. The Principal object can be used to authorize access to the operation. This is accomplished through the IsInRole method, as illustrated in Figure 2. Here we're verifying that the authenticated user is a member of the Broker group on the machine hosting the service.
With this code in place, the CalcMortage endpoint knows that only authenticated users belonging to the Broker role will be allowed to access the service. Although this is certainly a step in the right direction, the endpoint cannot completely rest since it still has the plain text password to deal with. Not addressing this issue could severely compromise the system since someone could sniff the messages and steal the password. Also, since we're not supplying a signature, someone could modify the credentials en route to the endpoint.
There are a few ways to avoid sending the password in plain text, but they all require you to implement a custom token manager. A custom token manager is a class you write and configure on the service to process incoming UsernameToken elements.
You implement a token manager by deriving a new class from UsernameTokenManager and overriding the AuthenticateToken method.
Within AuthenticateToken, your job is to look up the password for the supplied user name and return it to the caller, which in this case is the WSE infrastructure. Before returning from AuthenticateToken, you can also create and associate a Principal object with the UsernameToken object that specifies role membership. The Principal object can be used later in the processing pipeline for authorization purposes, as illustrated in the previous section.
Figure 3 shows a sample UsernameTokenManager implementation. For pedagogical reasons, I've hardcoded the passwords for each user, but this is obviously not something you'd want to do in practice. The implementation associates a GenericPrincipal object with each token specifying membership in the Broker role.
With the UsernameTokenManager class configured in Web. And since you're looking up the password manually and providing it to WSE, you no longer need to have Windows accounts set up on the machine hosting the service. The other main benefit of using a UsernameTokenManager is that the sender no longer has to supply the password in plain text.
Instead, the sender can send a hashed version of the password. When WSE receives the message and retrieves the password from our UsernameTokenManager, it can use the same algorithm to generate a hash of the value returned by Authenticatetoken and verify that it matches the one sent in the message.
When they match, the sender indeed sent the correct password for the user. When they don't match, the sender sent an incorrect password or the token hash has been tampered, which means authentication fails. WSE takes care of all of this for you if you've provided a UsernameTokenManager implementation to supply a correct password. The following example illustrates how to send a hashed password from the client again, I've hardcoded the password but only for ease of explanation :.
This solved the password problem, but you still don't have protection against message tampering. This is what signatures are designed to prevent.
You can require that incoming messages contain signatures by adding more code to inspect the SoapContext object, very much like you did to require UsernameToken elements. This method illustrates how to traverse the SoapContext looking for MessageSignature objects.
The presence of a MessageSignature object indicates that at least part of the message was signed. You'd have to extend this method to require signatures for specific elements. Adding a call to CheckForSignature at the beginning of CalcMortgage requires signatures on the incoming messages.
If you run the client application without adding a signature, you'll get a "Missing signature" exception. Signing messages from the sending applications is very straightforward with WSE. You can sign messages with a variety of different token types, including UsernameTokens. You sign a message by instantiating a MessageSignature object based on a particular security token and then adding it to the SoapContext, as illustrated in the following lines of code:.
By default, WSE signs the body and most of the headers, although you can control which elements you want to sign. When WSE receives this message on the server, it will recalculate the signature value and verify that it matches the one supplied in the message.
As such, the message signature can be used as proof that the sender knows the password known as proof of possession. This makes is possible for the sender to stop sending the password altogether, as illustrated here:.
In this case, WSE will automatically verify the signature and, by so doing, verify the password. Supplying an invalid password causes an exception "The signature or decryption was invalid" even though the password was not supplied in the message. However, signing the response using the same username token from the request is not commonly used since only the user should use its token to sign a message. A service should not use it to sign anything. Instead, a service should have its own token, normally an X.
One of the problems with using a raw UsernameToken to sign messages is that it becomes less secure with each message sent. Signing repeatedly with the same key might expose the system to ciphertext-only attacks that could result in exposing the secret data you're encrypting. To overcome this, by default the WSE runtime adds some randomness to the username token before it is sent so that the keys for different messages are actually different. A DerivedKeyToken is a security token that's derived from another token and then used to sign or encrypt the message.
A new DerivedKeyToken can be generated from the original shared secret for each message, making it less likely for ciphertext-only attacks to succeed. The following sample illustrates how to modify the sending code to generate a DerivedKeyToken from the original UsernameToken, which it then uses to sign the message:. Encryption is the last feature you may want the service to require. Encryption prevents eavesdropping and guarantees the privacy of sensitive data. Figure 5 shows how to write a method to check for EncryptedData elements in the SoapContext object.
Like CheckSignature, the CheckforEncryption method will return true as long as at least one element was encrypted in the message.
0コメント