Introduction
This article is a proof of concept article (POC). It explains how the Kerberos authentication can be implemented to authenticate users when they need to request a web service using WSE 3.0. This goal is very important, especially when using the Service Oriented Architecture (SOA). I decided to write this article after reading one of the Microsoft patterns and practices: Web Service Security Guide, which is a very good reference in this issue.
The following article doesn't describe how the Kerberos authentication works, in detail, and supposes that the reader has a good idea about it. It focuses on how to implement it using Microsoft technologies. A single sign on (SSO) implementation is the main usage of this concept. For example: suppose we have two domain users, a marketing group user and an accounting group user. Each one can login to his Windows account and then open the company website without any need to be authenticated against the company website. Sign on is done once when logging to Windows. And, the company website, for sure, gives each user his group privileges only. Simply put, we have the following participants in our system:
- Web service: that authenticates any client that needs to request it.
- Client: that needs to request the web service; it should provide the credentials for authentication when requesting the web service.
- Kerberos Distribution Center (KDC): it is responsible for authenticating the client and issuing a ticket that has the client credential, and then the client can use it for authentication with the web service. For the Microsoft environment, KDC is available in Windows Server 2003, for example, which is a domain controller.
The above figure shows the relationship between our system participants. Now, let's divide our article into two parts:
- Part I: The basic knowledge that is required to implement our demo. It could be illustrated in the following points:
- Overview of the Kerberos authentication process.
- Preparing web services and IIS configuration.
- Applying the Kerberos authentication on web services.
- Applying the Kerberos authentication on the client application.
- Part II: Describes a very simple demo based on part I.
Part I:
Overview of the Kerberos authentication process
Briefly, when a client needs to request a service, it does five steps, as shown in the following diagram:
I want to explain more about steps 2 and 4.
Preparing web services and IIS configuration
In this section, we will learn more about the master key and how to have a web service with a unique master key. What is a master key that is used to encrypt the service ticket? In a Windows server: each registered object (computer or user) on the KDC has a shared key (also called master key). This shared key is used for encrypting the service ticket and also for decrypting it. The object is registered on the KDC by a unique name called SPN (Service Principal Name), so when requesting a ticket from the KDC, the SPN should be determined. By default, all services running on Windows use the built-in account Network Service, and the default SPN that refers to it ‘host/PCName’. The default application pool on IIS uses the Network Service account to identify itself to Windows. That means, when a service ticket is generated to a web service on IIS, it is encrypted by the shared key that is related to the ‘Network Service’ account, and then any web service that uses the same application pool can decrypt the ticket.
To dedicate a web service to a new master key, you need to do the following:
- Add a new domain account to the Active Directory.
- Create a new SPN that refers to the new domain account.
- Add a new application pool on IIS, and map its identity to the new domain account.
- Configure the web service virtual directory to use the new application pool.
- Grant the new account all permission needed to access the web service virtual directory and to work as an IIS_WPG group.
- Restart IIS.
Details:
Create a new SPN that refers to the new domain account:
The Setspn tool is included in the Windows Support Tools, and is used for managing SPNs.
Type the following command in the command prompt: Setspn –a http/ServiceName DomainName\DomainAccount, where:
- ServiceName: is the name of the web service.
- DomainName: is the name of the domain.
- DomainAccount: is the new domain account name.
Add a new application pool on IIS, and map its identity to the new domain account:
Configure the web service virtual directory to use the new application pool:
Grant the new account all permission needed to access the web service virtual directory and work as an IIS_WPG group:
- Adding a new domain account to the IIS_WPG group.
- Assign this new domain account the following two user rights to start the CGI processes: Adjust memory quotas for a process, and Replace a process level token.
- Grant the new domain account full control on the web service folder.
- Grant the new domain account full control on the temp folder in the Windows directory.
Applying Kerberos authentication on web services
To use Kerberos authentication in the web service:
- Enable WSE 3.0, and enable Policy.
- Add the Policy file and configure the Policy.
- Apply the Policy on the web service.
Details:
- Enable WSE 3.0, and enable Policy: by adding the following tags in the web.config file:
<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>
<system.web>
<compilation debug="true">
<assemblies>
<add assembly="Microsoft.Web.Services3, Version=3.0.0.0,
Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
</assemblies>
</compilation>
<webServices>
<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>
<microsoft.web.services3>
<policy fileName="wse3policyCache.config" />
<tokenIssuer>
<statefulSecurityContextToken enabled="false" />
</tokenIssuer>
</microsoft.web.services3>
- Add the Policy file and configure the Policy: add a config file to your project, name it ‘wse3policyCache.config’, then add the following tags to it:
<policies xmlns="http://schemas.microsoft.com/wse/2005/06/policy">
<policy name="KerberosService">
<authorization>
<allow user="Mawhiba\Akram" />
<deny role="*" />
</authorization>
<kerberosSecurity establishSecurityContext="true"
renewExpiredSecurityContext="true" requireSignatureConfirmation="false"
messageProtectionOrder="SignBeforeEncryptAndEncryptSignature"
requireDerivedKeys="true" ttlInSeconds="300">
<protection>
<request
signatureOptions="IncludeAddressing, IncludeTimestamp,
IncludeSoapBody"
encryptBody="true" />
<response signatureOptions="IncludeAddressing, IncludeTimestamp,
IncludeSoapBody"
encryptBody="true" />
<fault signatureOptions="IncludeAddressing, IncludeTimestamp,
IncludeSoapBody"
encryptBody="false" />
</protection>
</kerberosSecurity>
<requireActionHeader />
</policy>
</policies>
The authorization part may be changed according to the business roles.
- Apply the policy on the web service: by adding the following code before the service class:
[Policy("KerberosService")]
Applying Kerberos authentication on the client application
To use Kerberos authentication in the client:
- Enable WSE 3.0, and enable Policy.
- Add the Policy file and configure the Policy.
- Use the enhanced version of the web service and apply the Policy on the client.
Details:
- Enable WSE 3.0, and enable the Policy: by adding the following tags in the web.config file:
<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>
<system.web>
<compilation debug="true">
<assemblies>
<add assembly="Microsoft.Web.Services3, Version=3.0.0.0,
Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
</assemblies>
</compilation>
<webServices>
<soapExtensionImporterTypes>
<add type="Microsoft.Web.Services3.Description.WseExtensionImporter,
Microsoft.Web.Services3, Version=3.0.0.0, Culture=neutral,
PublicKeyToken=31bf3856ad364e35" />
</soapExtensionImporterTypes>
</webServices>
</system.web>
<microsoft.web.services3>
<policy fileName="wse3policyCache.config" />
</microsoft.web.services3>
- Add the Policy file and configure the Policy: by adding a config file to your project, naming it ‘wse3policyCache.config’, then adding the following tags to it:
<policies xmlns="http://schemas.microsoft.com/wse/2005/06/policy">
<policy name="KerberosClient">
<kerberosSecurity establishSecurityContext="true"
renewExpiredSecurityContext="true" requireSignatureConfirmation="false"
messageProtectionOrder="SignBeforeEncryptAndEncryptSignature"
requireDerivedKeys="true" ttlInSeconds="300">
<token>
<kerberos targetPrincipal="http/ServiceName"
impersonationLevel="Impersonation" />
</token>
<protection>
<request
signatureOptions="IncludeAddressing,
IncludeTimestamp, IncludeSoapBody"
encryptBody="true" />
<response signatureOptions="IncludeAddressing,
IncludeTimestamp, IncludeSoapBody"
encryptBody="true" />
<fault signatureOptions="IncludeAddressing,
IncludeTimestamp, IncludeSoapBody"
encryptBody="false" />
</protection>
</kerberosSecurity>
<requireActionHeader />
</policy>
</policies>
where ‘http/ServiceName
’ is the SPN of the required service
- Use the enhanced version of the web service and apply the policy on the client: using the ‘Wse’ version, like in the following code:
Client.localhost.ServiceWse myService = new ServiceWse();
myService.SetPolicy("KerberosClient");
Part II:
Fine, we can now start our simple project to demonstrate Kerberos authentication:
- Create two web services with different master keys (webservice1 and webservice2).
- Apply Kerberos authentication, and add policies on each web service that allow a user and deny the others.
<authorization>
<allow user="CodeProject\Akram" />
<deny role="*" />
</authorization>
- In the two web services, write a test method like this:
[WebMethod]
public string Test()
{
return "Succeeded ";
}
- Create a console application and add two references to the web services.
- Apply both Kerberos authentication policies on it (one policy to request webservice1 called '
KerberosClient1
', and another to request webservice2 called ' KerberosClient2
'). The policy file will be some thing like this:
<policies xmlns="http://schemas.microsoft.com/wse/2005/06/policy">
<policy name="KerberosClient1">
<kerberosSecurity establishSecurityContext="true"
renewExpiredSecurityContext="true" requireSignatureConfirmation="false"
messageProtectionOrder="SignBeforeEncryptAndEncryptSignature"
requireDerivedKeys="true" ttlInSeconds="300">
<token>
<kerberos targetPrincipal="http/WS1"
impersonationLevel="Impersonation" />
</token>
<protection>
<request signatureOptions="IncludeAddressing,
IncludeTimestamp, IncludeSoapBody"
encryptBody="true" />
<response signatureOptions="IncludeAddressing,
IncludeTimestamp, IncludeSoapBody"
encryptBody="true" />
<fault signatureOptions="IncludeAddressing,
IncludeTimestamp, IncludeSoapBody"
encryptBody="false" />
</protection>
</kerberosSecurity>
<requireActionHeader />
</policy>
<policy name="KerberosClient2">
<kerberosSecurity establishSecurityContext="true"
renewExpiredSecurityContext="true" requireSignatureConfirmation="false"
messageProtectionOrder="SignBeforeEncryptAndEncryptSignature"
requireDerivedKeys="true" ttlInSeconds="300">
<token>
<kerberos targetPrincipal="http/WS2"
impersonationLevel="Impersonation" />
</token>
<protection>
<request signatureOptions="IncludeAddressing,
IncludeTimestamp, IncludeSoapBody"
encryptBody="true" />
<response signatureOptions="IncludeAddressing,
IncludeTimestamp, IncludeSoapBody"
encryptBody="true" />
<fault signatureOptions="IncludeAddressing,
IncludeTimestamp, IncludeSoapBody"
encryptBody="false" />
</protection>
</kerberosSecurity>
<requireActionHeader />
</policy>
</policies>
We don't have a web.config in this console application, so we can use the app.config file to enable WSE. The App.config, after modification and adding the web references, will be some thing like this:
="1.0"="utf-8"
<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" />
<sectionGroup name="applicationSettings"
type="System.Configuration.ApplicationSettingsGroup, System,
Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" >
<section name="Client.Properties.Settings"
type="System.Configuration.ClientSettingsSection, System,
Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
requirePermission="false" />
</sectionGroup>
</configSections>
<microsoft.web.services3>
<policy fileName="wse3policyCache.config" />
</microsoft.web.services3>
<applicationsettings />
<client.properties.settings />
<setting name="Client_localhost_Service" serializeAs="String">
<value />http://localhost/WS_Kerb_Demo/Service.asmx</value>
</setting>
<setting name="Client_localhost2_Service" serializeAs="String">
<value>http://localhost/WS_Kerb_Demo2/Service.asmx</value>
</setting>
</Client.Properties.Settings>
</applicationSettings>
</configuration>
- Write some code trying to request the two web services using the two policies, like that:
class Program
{
static void Main(string[] args)
{
localhost1.ServiceWse service1 = new localhost1.ServiceWse();
localhost2.ServiceWse service2 = new localhost2.ServiceWse();
service1.SetPolicy("KerberosClient1");
Console.WriteLine("");
Console.WriteLine("(1) Token with first SPN " +
"and calling the first service ");
try
{
Console.WriteLine(service1.Test());
}
catch
{
Console.WriteLine("Failed");
}
Console.ReadLine();
Console.WriteLine("");
service2.SetPolicy("KerberosClient1");
Console.WriteLine("(2) Token with first SPN " +
"and calling the secound service ");
try
{
Console.WriteLine(service2.Test());
}
catch
{
Console.WriteLine("Failed ");
}
Console.ReadLine();
Console.WriteLine("");
service1.SetPolicy("KerberosClient2");
Console.WriteLine("(3) Token with secound SPN" +
" and calling the first service ");
try
{
Console.WriteLine(service1.Test());
}
catch
{
Console.WriteLine("Failed ");
}
Console.ReadLine();
Console.WriteLine("");
service2.SetPolicy("KerberosClient2");
Console.WriteLine("(4) Token with secound SPN" +
" and calling the secound service ");
try
{
Console.WriteLine(service2.Test());
}
catch
{
Console.WriteLine("Failed ");
}
Console.ReadLine();
}
}
- Run the application and you will see the following output:
Thanks
I hope this article would be useful for some one. It is my first article on The Code Project, and I hope it is not the last. Finally, I would like to thank Arabian Advanced Systems (AAS) located in Riyadh – KSA, from where I got a lot of the article knowledge, for their support and team cooperation.
Akram Aly Mossa is an intermediate level software developer, gained many experiences in different fields (development is one of them), born in Egypt 1981, graduated from Al-Azhar university 2005,
and looking for a good future...