Introduction
When I started working with WSE3, I just wanted to test how to authenticate a web service call with a username and password. I read the Implementing Direct Authentication with UsernameToken in WSE 3.0 article and I found it was very useful will all of the issues regarding to security. Wait a minute. It was so complicated for a newbie like me. I didn't care how to secure the message between client and web services at this time yet. I just wanted to pass a username and password as plain text and the web service to do authentication for each method call. I will leave the message encrypting tasks between client and web service for another article.
Background
Here are some good articles:
I assume you've already installed Web Services Enhancements (WSE) 3.0.
Using the Code
UsernameToken
with username and password will be attached to the SOAP header when the client makes a call to the web service. To do custom authentication at server side, you need to override the AuthenticateToken
method of the UsernameTokenManager
class. We will create a class library project called UsernameAssertionLibrary
and add a class called ServiceUsernameTokenManager
that inherits from UsernameTokenManager
.
using System;
using System.Diagnostics;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using Microsoft.Web.Services3;
using Microsoft.Web.Services3.Design;
using Microsoft.Web.Services3.Security;
using Microsoft.Web.Services3.Security.Tokens;
namespace UsernameAssertionLibrary
{
public class ServiceUsernameTokenManager : UsernameTokenManager
{
public ServiceUsernameTokenManager()
{
}
public ServiceUsernameTokenManager(XmlNodeList nodes)
: base(nodes)
{
}
protected override string AuthenticateToken(UsernameToken token)
{
string username = token.Username;
char[] ch = username.ToCharArray();
Array.Reverse(ch);
return new String(ch);
}
}
}
The AuthenticateToken
method will be called by WSE3 to retrieve the password of token.Username
. You can implement this method to get a password from database, XML file, etc. for the given username. WSE3 will check if the password returned by AuthenticationToken
matches with the password in the SOAP header. If they don't match, an exception will be sent to the client.
Now that we have the UsernameAssertionLibrary
DLL, we need to register it to our web service. Add an ASP.NET web service project to the solution. We need to add reference to the UsernameAssertionLibrary
class library to the web service project or we can copy UsernameAssertionLibrary.dll to the bin directory of the web service folder.
To enable WSE 3.0, right click on the web service project and click WSE Settings 3.0.
- Under the General tab, check both Enable this project for Web Services Enhancements and Enable Microsoft Web Services Enhancement Soap Protocol Factory.
- Under the Security tab, in the Security Tokens Managers area, click Add.
Fill in the fields as follows:
- Type: "UsernameAssertionLibrary.ServiceUsernameTokenManager, UsernameAssertionLibrary"
- Namespace: "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"
- LocalNode: "UsernameToken"
- Click OK. By doing this, you register our custom username token manager with WSE3.0.
- Under the Policy tab, check Enable Policy and click Add. Name your new application policy "ServerPolicy" and then click OK. In the WSE Security Settings Wizard window:
- Select Secure a service application and Username; then click Next
- Check Specify Username Token in code and click Next
- Select None (reply on transport protection), click Next and Finish
Don't worry about the setting for Protection Order. We just make it work first before we can think how we should protect information sent via the network. There are several way you can secure your message -- for example, using SSL or WSE3.0 to encrypt the SOAP message -- but it is not in this very first look at WSE3.0. - Under the Diagnostics tab, select Enable Message Trace. Enter InputTrace.webinfo into the Input text box and OutputTrace.webinfo into the Output text box. These are XML files you can use to trace SOAP messages sent from the client and service.
Now that we have wse3policyCache.config, take a look at this file:
<policies xmlns="http://schemas.microsoft.com/wse/2005/06/policy">
<extensions>
<extension name="usernameOverTransportSecurity" type=
"Microsoft.Web.Services3.Design.UsernameOverTransportAssertion,
Microsoft.Web.Services3, Version=3.0.0.0, Culture=neutral,
PublicKeyToken=31bf3856ad364e35" />
<extension name="requireActionHeader" type=
"Microsoft.Web.Services3.Design.RequireActionHeaderAssertion,
Microsoft.Web.Services3, Version=3.0.0.0, Culture=neutral,
PublicKeyToken=31bf3856ad364e35" />
</extensions>
<policy name="ServerPolicy">
<usernameOverTransportSecurity />
<requireActionHeader />
</policy>
</policies>
Open the web.config file; it looks like this:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<configSections>
<!-- Register WSE config section -->
<section name="microsoft.web.services3" type=
"Microsoft.Web.Services3.Configuration.WebServicesConfiguration,
Microsoft.Web.Services3, Version=3.0.0.0, Culture=neutral,
PublicKeyToken=31bf3856ad364e35" />
</configSections>
<appSettings />
<connectionStrings />
<system.web>
<!-- Compilation settings -->
<compilation debug="true">
<assemblies>
<add assembly="Microsoft.Web.Services3, Version=3.0.0.0,
Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
<add assembly="System.Security, Version=2.0.0.0,
Culture=neutral, PublicKeyToken=B03F5F7F11D50A3A" />
<add assembly="System.Configuration.Install,
Version=2.0.0.0, Culture=neutral,
PublicKeyToken=B03F5F7F11D50A3A" />
<add assembly="System.Design, Version=2.0.0.0,
Culture=neutral, PublicKeyToken=B03F5F7F11D50A3A" />
</assemblies>
</compilation>
<!-- We don't require any authentication, because it will be
implemented on application layer -->
<authentication mode="None" />
<!-- Web Services settings -->
<webServices>
<!-- Enable WSE 3.0 -->
<soapExtensionImporterTypes>
<add type=
"Microsoft.Web.Services3.Description.WseExtensionImporter,
Microsoft.Web.Services3, Version=3.0.0.0, Culture=neutral,
PublicKeyToken=31bf3856ad364e35" />
</soapExtensionImporterTypes>
<soapServerProtocolFactory
type="Microsoft.Web.Services3.WseProtocolFactory,
Microsoft.Web.Services3, Version=3.0.0.0, Culture=neutral,
PublicKeyToken=31bf3856ad364e35" />
</webServices>
</system.web>
<!-- WSE 3.0 config section -->
<microsoft.web.services3>
<diagnostics>
<trace enabled="true" input="InputTrace.webinfo"
output="OutputTrace.webinfo" />
</diagnostics>
<security>
<securityTokenManager>
<add type=
"UsernameAssertionLibrary.ServiceUsernameTokenManager,
UsernameAssertionLibrary" namespace=
"http://docs.oasis-open.org/wss/2004/01/
oasis-200401-wss-wssecurity-secext-1.0.xsd"
localName="UsernameToken" />
</securityTokenManager>
</security>
<policy fileName="wse3policyCache.config" />
</microsoft.web.services3>
</configuration>
Now we need to code for our web service. Add reference to Microsoft.Web.Services3 to the project. Open Service.cs and copy the code:
using System;
using System.Web;
using System.Web.Services;
using System.Web.Services.Protocols;
using Microsoft.Web.Services3;
[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
[Microsoft.Web.Services3.Policy("ServerPolicy")]
public class Service : System.Web.Services.WebService
{
public Service ()
{
}
[WebMethod]
public string HelloMyFriend()
{
return "Hello " +
RequestSoapContext.Current.IdentityToken.Identity.Name;
}
}
The attribute [Microsoft.Web.Services3.Policy("ServerPolicy")]
will register our ServerPolicy
, which is configured in wse3policyCache.config to the service. We have a web method called HelloMyFriend()
that returns a string Hello + the username in the token, which is passed in the SOAP header when the client makes a HelloMyFriend
call. There is no code in the HelloMyFiend
method to authenticate the username, but WSE will call AuthenticateToken()
from our custom Username Token Manager to retrieve the password. It will then compare the password with the one in SOAP header sent from the client.
Now we will see how we can have username and password in the SOAP header when we make a service call. Add a Console Application project to our solution called TestWebService
and add reference to Microsoft.Web.Services3
. Before you can make a call to the web service, you need to have a proxy. You can either use Add Web Preference in Visual Studio or use WseWsdl3.exe to generate a proxy class. I used WseWsdl3.exe; the default is in Program Files\Microsoft WSE\v3.0\Tools. Here is the command to generate the ServiceProxy.cs class.
wsewsdl3 <a href="http://localhost:1515/WebService/Service.asmx?wsdl">http:
/o:c:\ServiceProxy.cs /l:cs /type:webClient
Add ServiceProxy.cs to our TestWebService
project. Like the server side, the client also needs to have a policy. You need to configure the client to enable WSE3.0. To enable WSE 3.0, right click on the TestWebService
project and click WSE Settings 3.0.
- Under the General tab, check Enable this project for Web Services Enhancements.
- Under the Policy tab, check Enable Policy and click Add. Name your new application policy "ClientPolicy" and click OK. In the WSE Security Settings Wizard window:
- Select Secure a client application and Username and click Next
- Check Specify Username Token in code and click Next
- Select None (reply on transport protection), click Next and Finish
- Under the Diagnostics tab, select Enable Message Trace. Enter InputTrace.webinfo into the Input text box and OutputTrace.webinfo into the Output text box.
We have wse3policyCache.config in the project look like this:
<policies xmlns="http://schemas.microsoft.com/wse/2005/06/policy">
<extensions>
<extension name="usernameOverTransportSecurity" type=
"Microsoft.Web.Services3.Design.UsernameOverTransportAssertion,
Microsoft.Web.Services3, Version=3.0.0.0, Culture=neutral,
PublicKeyToken=31bf3856ad364e35" />
<extension name="requireActionHeader" type=
"Microsoft.Web.Services3.Design.RequireActionHeaderAssertion,
Microsoft.Web.Services3, Version=3.0.0.0, Culture=neutral,
PublicKeyToken=31bf3856ad364e35" />
</extensions>
<policy name="ClientPolicy">
<usernameOverTransportSecurity />
<requireActionHeader />
</policy>
</policies>
The app.config file looks like this:
<configuration>
<configsections>
<section name="microsoft.web.services3" type=
"Microsoft.Web.Services3.Configuration.WebServicesConfiguration,
Microsoft.Web.Services3, Version=3.0.0.0, Culture=neutral,
PublicKeyToken=31bf3856ad364e35" />
</configsections>
<microsoft.web.services3>
<diagnostics>
<trace output="OutputTrace.webinfo" input="InputTrace.webinfo"
enabled="true" />
</diagnostics>
<policy filename="wse3policyCache.config" />
</microsoft.web.services3>
</configuration>
Here is the client code:
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Web.Services3;
using Microsoft.Web.Services3.Design;
using Microsoft.Web.Services3.Security.Tokens;
namespace TestWebService
{
class Program
{
static void Main(string[] args)
{
Service serviceProxy = new Service();
UsernameToken token = new UsernameToken("admin", "nimda",
PasswordOption.SendPlainText);
serviceProxy.SetClientCredential(token);
serviceProxy.SetPolicy("ClientPolicy");
try
{
Console.WriteLine(serviceProxy.HelloMyFriend());
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
}
}
Now try to run TestWebService
. If you change the password to "nimda1" you will get an exception. You can open OutputTrace.webinfo to see the SOAP message. You might think that the username and password should not be seen. There are several way to secure messages, for example, using SSL (point to point) or WSE3.0 (end to end).
History
- 6 July, 2007 -- Original version posted
- 12 July, 2007 -- Updated
- 4 September, 2007 -- Article edited and moved to the main CodeProject.com article base