On peut dire que j’ai pas mal galéré avant de faire fonctionner mon modèle. Le challenge ne s’annonçait pas si compliqué pourtant ! Faire des services WCF et les raccorder à mes Memberships, c’est une simplicité que Microsoft met volontiers en avant. En pratique, moins simple d’autant plus que la documentation MSDN était incomplète. La remontée d’information auprès de Microsoft via Peter Huang du support MS a permis de corriger cette lacune.
La solution était tellement disséminée sur le web que j’ai décidé de vous faire ce tutoriel qui consiste à :
- Créer un service Web via WCF en wsHttpBinding, tout en autorisant les sessions Http
- Sécuriser ce flux via mes MemberShip et Role Providers
- Installer ce fameux certificat sur le poste serveur et client.
1. Le Service WCF
1.1. Le service
using System;
using System.ServiceModel;
using System.Web;
using System.ServiceModel.Activation;
using System.Threading;
namespace BusinessLayer
{
[ServiceBehaviorAttribute(InstanceContextMode = InstanceContextMode.PerCall)]
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Required)]
public class MyService : ServiceLayer.IService
{
public string MyOperation(string myValue)
{
if (HttpContext.Current.Session["nb"] == null)
HttpContext.Current.Session["nb"] = 0;
HttpContext.Current.Session["nb"] = Convert.ToInt32(HttpContext.Current.Session["nb"].ToString()) + 1;
return String.Format("Hello {0}, I mean {1}, visiting for the {2} time with Session {3}", myValue, Thread.CurrentPrincipal.Identity.Name, HttpContext.Current.Session["nb"].ToString(), HttpContext.Current.Session.SessionID) ;
}
}
}
Mon service est plutôt simpliste. Il s’agit de récupérer le paramètre envoyé, d’ajouter 1 à la session en cours et de retourner l’information avec le nom de l’utilisateur actuellement connecté.
Attention, veillez à bien mettre l’attribut AspNetCompatibilityRequirements pour être en mesure de traiter le HttpContext. Sans cela, HttpContext.Current sera toujours null.
Notre classe implémente l’interface IService. C’est par ce biais que le service sera exposé via WCF.
1.2. L’interface
using System;
using System.ServiceModel;
namespace ServiceLayer
{
[ServiceContract( Namespace = "http://www.elgee.com/1/0/0/0", SessionMode = SessionMode.Required)]
public interface IService
{
[OperationContract(IsInitiating=true)]
string MyOperation(string myValue);
}
}
Mon interface expose la méthode MyOperation, c’est le contrat. Le service lui est définit au niveau de l’attribut de mon Interface
1.3. Le Service en frontal
C’est par le biais d’un service.svc que le service peut s’exposer via IIS.
<%@ ServiceHost Language="C#" Debug="true" Service="BusinessLayer.MyService" CodeBehind="~/App_Code/MyService.cs" %>
Là, encore rien de très compliqué.
2. Le paramétrage du service dans le Web.Config
2.1. Le Membership Provider
Pour l’occasion, j’ai réalisé un Membership provider très simple, avec les identifiants et mots de passe en dur dans le code.
<membership defaultProvider="MyMembershipProvider">
<providers>
<clear/>
<add name="MyMembershipProvider" type="Secure.SimpleMembershipProvider" applicationName="Test" enablePasswordRetrieval="false" enablePasswordReset="true" requiresQuestionAndAnswer="false" requiresUniqueEmail="false" passwordFormat="Hashed" maxInvalidPasswordAttempts="5" minRequiredPasswordLength="4" minRequiredNonalphanumericCharacters="0" passwordAttemptWindow="10" passwordStrengthRegularExpression=""/>
</providers>
</membership>
Mon Membership est dans le namespace Secure.SimpleMembershipProvider.
2.1. Le RoleProvider
Idem pour le RoleProvider, très simplifié aussi.
<roleManager enabled="true" defaultProvider="MyRoleProvider">
<providers>
<clear/>
<add name="MyRoleProvider" type="Secure.SimpleRoleProvider"/>
</providers>
</roleManager>
2.2. Le ServiceModel
C’est une nouvelle zone disponible à partir du framework 3.0.
2.2.1. La compatibilité ASP.Net
<serviceHostingEnvironment aspNetCompatibilityEnabled="true"/>
Cet élément permet de pouvoir gérer les notions de Session dans vos services WCF.
2.2.2. Le Binding
<bindings>
<wsHttpBinding>
<binding name="MyBinding" messageEncoding="Mtom" >
<security mode="Message">
<message clientCredentialType="UserName"/>
</security>
</binding>
</wsHttpBinding>
</bindings>
Le Binding, en fonction du transport choisit choisit la manière dont les données seront transportées. Pour utiliser les Membership provider, il faut absolument utiliser le wsHttpBinding. Le messagEncoding est optionnel et par contre doit être utilisé si vous transmettez des données binaires volumineuses.
L’élément Message permet de spécifier que lors du transport, l’accès au message est sécurisé par un système d’authentification. La sécurité sur le transport peut également être mise en œuvre dans le cadre d’un flux sécurisé sous SSL.
2.2.3. Le behavior
<behavior name="Secured">
<serviceCredentials>
<userNameAuthentication userNamePasswordValidationMode="MembershipProvider" membershipProviderName="MyMembershipProvider"/>
<serviceCertificate storeLocation="LocalMachine" storeName="My" x509FindType="FindBySerialNumber"
findValue="6d 0d 18 03 07 6e fe a0 4b bb 7c fc 76 af 01 48"/>
</serviceCredentials>
<serviceDebug includeExceptionDetailInFaults="true"/>
<serviceMetadata httpGetEnabled="true"/>
<serviceAuthorization impersonateCallerForAllOperations="false" principalPermissionMode="UseAspNetRoles" roleProviderName="MyRoleProvider"/>
</behavior>
Le behavior est le comportement de votre service. Ici, on précise que la sécurité est gérée grâce au MembershipProvider en indiquant son nom et pas son namespace.
Un certificat est nécessaire pour ce mode de fonctionneemnt. J’ai décidé de le rechercher via le SerialNumber, je vous explique plus loin comment le retrouver.
Enfin, ne pas oublier également les autorisations via le RoleProvider. Si vous l’oubliez, il ne sera pas possible de récupérer coté service le nom de l’utilisateur connecté.
2.2.4. Le Service
<service behaviorConfiguration="Secured"
name="BusinessLayer.MyService">
<endpoint binding="wsHttpBinding"
bindingConfiguration="MyBinding"
name="MyServiceEndPoint"
contract="ServiceLayer.IService">
</endpoint>
</service>
Le service doit utiliser notre behavior « Secured ». Son name correspond au nom de la classe avec son namespace. Le service s’expose via un ou plusieurs EndPoints. Notre endpoint retenu est le wsHttpBinding qui doit utiliser notre configuration de binding. Le contrat n’est rien d’autre que le nom de l’interface avec son namespace.
3. Les certificats X.509
3.1. Création des certificats
Il est nécessaire d’installer 2 certificats pour utiliser les membership provider dans WCF. Afin d’éviter les mauvaises surprises, je vous conseille de vérifier le fuseau horaire de votre serveur sur lequel vous allez créer les certificats. Checkez que vous êtes sur le bon et que votre machine est à l’heure.
Sur la machine qui hostera les services, lancez une fenètre MSDOS et créez le premier, qui est un certificat Racine :
makecert -n "CN=demoElGeeCA" -r -sv demoElGeeCA.pvk demoElGeeCA.cer
Le certificat est copié dans un .cer.
Exécutez maintenant cette ligne de commande pour le second certificat, qui est signé par le précédent :
makecert -sk TMMCA -iv TMMCA.pvk -n "CN=demoElGeeCA " -ic demoElGeeCA.cer demoElGee.cer -sr LocalMachine -ss My -sky exchange –pe
Il faut également maintenant prépare le certificat pour son installation coté client. Lancez la commande suivante :
pvk2pfx.exe -pvk demoElGeeCA.pvk -spc demoElGeeCA.cer
Une boite de dialogue vous demandera de saisir le mot de passe de la clé. Saisissez-le et accédez aux écrans suivants. Cochez le bouton Oui, Exporter la clé privée.

Cochez les cases comme ci-dessous :

Fournissez un nouveau mot de passe lié au certificat que vous donnerez à votre client :

Donnez un nom à votre fichier pfx :

Générez alors votre fichier. Vous devrez fournir à votre client :
- Le .pfx et son mot de passe
- Le demoElGee.cer
3.1. Déploiement du certificat coté Serveur
Il faut maintenant les mettre dans le bon magasin sur votre serveur
Double cliquez sur le .pfx. A la saisie du mot de passe, cochez les cases comme dans la copie d’écran ci-dessous.

Placez le dans le magasin de certificats « Autorités de certification racines de confiance »
Sur demoElGee.cer, double cliquez et placez le dans le magasin de certificats « Personnes autorisées ».
3.2. Vérification de l’installation des certificats
Pour vérifier leurs installations, lancez la commande certmgr.msc pour atteindre le gestionnaire de certificats

Dans Action, Cliquez sur Recherche des certificats :

Nos deux certificats apparaissent maintenant. Cliquez sur demoElGee pour trouver le SerialNumber dans l’onglet Détails. Son Serial est 6d 0d 18 03 07 6e fe a0 4b bb 7c fc 76 af 01 48. C’est bien celui déployé dans le Web.Config.
4. Les autorisations sur les certificats
Il faut maintenant autoriser le compte qui exécute le site qui héberge les services à accéder à mon certificat. Pour cela, il faut utiliser les outils de WSE 3.0, installé avec les composants WCF de Visual Studio.
Tout d’abord, il vous faut créer un utilisateur local, nous le nommerons IIS. Il devra :
- Etre membre de Network Configuration Operators
- Etre membre de IIS_WPG
- Avoir les droits en écriture sur le répertoire temporaire de Windows (par défaut c:\windows\temp). Ce répertoire est utilisé par WCF pour créer ces fichiers temporaires.
Créez ensuite un pool d’application dans IIS. Ce pool devra être exécuté sous l’utilisateur IIS que nous venons de paramétrer.
Dans C:\Program Files\Microsoft WSE\v3.0\Samples, lancez la ligne de commande suivante :
winhttpcertcfg -g -c LOCAL_MACHINE\My -s demoElGee -a IIS
5. Le Client
J’ai développé comme d’habitude une petite application Console qui utilise mon WebService.
Ajoutez une Référence de Service sur l’url http://monserveur:2174/WebWCF/service.svc. Cette référence est nommée myWS.
5.1. Le Web.Config
Visual Studio, en fonction des paramètres du service aura paramétré votre Web.Config. Attention néanmoins, il est possible que la référence au ClientBehavior ne soit pas implémentée. Voici ce que vous devez avoir :
<system.serviceModel>
<client>
<endpoint address="http:// monserveur:2174/WebWCF/service.svc"
binding="wsHttpBinding" bindingConfiguration="MyServiceEndPoint" behaviorConfiguration="ClientBehavior"
contract="WCFClient.myWs.IService" name="MyServiceEndPoint">
<identity>
<certificate encodedValue="AwAAAAEAAAAUAAAAxbZa1hFs3b1
JAfYwpE9Il/zo6aggAAAAAQAAAP4BAAAwggH6MIIBY6ADAgECAhBt
DRgDB27+oEu7fPx2rwFIMA0GCSqGSIb3DQEBBAUAMBYxFDASBgNVBAMTC2RlbW9FbEdl
ZUNBMB4XDTA3MTAxOTExMDkwOVoXDTM5MTIzMTIzNTk1OVowFDESMBAGA
1UEAxMJZGVtb0VsR2VlMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDT+M9mMWez9Rws
PdmeEl2MPbyNbbpuGH1L1nWOfZrdpSw9oleMH9Lj+wsvbypH7kpb/+qdej7IL3PAI1
ILaBh/KJSJinSNwl31ogwc1ajf+4FKuL81JxmYGyNXNP1i+1Lqi1nDFrHE8gstWPIRG0vqJgF
ZKmD32dds9pjPOqU9GwIDAQABo0swSTBHBgNVHQEEQDA+gBD6aWNw7LnwnUlJxU9bL3NIoRg
wFjEUMBIGA1UEAxMLZGVtb0VsR2VlQ0GCEC4wB2m1DKisScmh4S808NMwDQYJKoZIhvcNAQE
EBQADgYEAwkvKxeN6NiCvucmoK7cajGiKCNVOmI5biPmOt6yoFRvlX9iVVKPgUaG6o3t2iej
ljFxdQsR5DFIN+Rkrk6rV1SyGrGkUvJOhEZtdAfSjoCdiTbf1c6YAnU4a12t433si3R
Mz8XcGiFg4oWiHHj+MyYgmR4trDnCTfqQTYDBGppg=" />
</identity>
</endpoint>
</client>
Le service est bien référencé sur mon URL en wsHttpBinding. Il utilise un behavior et une identité qui correspond au certificat installé sur le serveur. Je n’ai pas tester l’application en client / Serveur. Il est peut être possible de devoir déployer le certificat sur le poste client.
<bindings>
<wsHttpBinding>
<binding name="MyServiceEndPoint" closeTimeout="00:01:00" openTimeout="00:01:00"
receiveTimeout="00:10:00" sendTimeout="00:01:00" bypassProxyOnLocal="false"
transactionFlow="false" hostNameComparisonMode="StrongWildcard"
maxBufferPoolSize="524288" maxReceivedMessageSize="65536" messageEncoding="Mtom"
textEncoding="utf-8" useDefaultWebProxy="true" allowCookies="true">
<readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384"
maxBytesPerRead="4096" maxNameTableCharCount="16384" />
<reliableSession ordered="true" inactivityTimeout="00:10:00"
enabled="false" />
<security mode="Message">
<transport clientCredentialType="Windows" proxyCredentialType="None"
realm="" />
<message clientCredentialType="UserName" negotiateServiceCredential="true"
algorithmSuite="Default" establishSecurityContext="true" />
</security>
</binding>
</wsHttpBinding>
</bindings>
Le paramétrage de mon EndPoint. J’ai mis allowCookies a true. Attention, à chaque update du ServiceReference, cette valeur se remet à false.
<behaviors>
<endpointBehaviors>
<behavior name="ClientBehavior">
<clientCredentials>
<serviceCertificate>
<authentication certificateValidationMode="PeerOrChainTrust" trustedStoreLocation="LocalMachine" revocationMode="NoCheck"/>
</serviceCertificate>
</clientCredentials>
</behavior>
</endpointBehaviors>
</behaviors>
</system.serviceModel>
Mon behavior qui est important. Sans lui, la requête échoue.
5.2. Le code coté client
myWs.ServiceClient ws = null;
try
{
ws = new WCFClient.myWs.ServiceClient();
ws.ClientCredentials.UserName.UserName = "elgee";
ws.ClientCredentials.UserName.Password = "elgee";
Console.WriteLine(ws.MyOperation("toto1"));
Console.WriteLine(ws.MyOperation("toto2"));
ws = new WCFClient.myWs.ServiceClient();
ws.ClientCredentials.UserName.UserName = "elgee";
ws.ClientCredentials.UserName.Password = "elgee";
Console.WriteLine(ws.MyOperation("toto3"));
Console.WriteLine(ws.MyOperation("toto4"));
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
if (ex.InnerException != null)
Console.WriteLine(ex.InnerException.Message);
}
finally
{
ws = null;
}
Console.ReadLine();
Ne pas oublié donc de passer le credential, c'est-à-dire l’identifiant et le mot de passe qu’attendent le Membership Provider. Mon service est lancé deux fois pour voir comment les Sessions se comportent.
5.3. L’installation du certificat
Comme pour le serveur, les deux certificats sont nécessaires pour faire fonctionner votre service. C’est contraignant, pas il n’y a pas le choix. Suivez la même procédure que le chapitre 3.2 avec le fichier .pfx et le DemoElGee.cer.

Entrez le mot de passe qui a été défini à la création du fichier PFX :

Déployez le certificat dans le magasin « Autorités de certification racines de confiance » :

Prenez maintenant fichier .CER (DemoElGee.cer) et double cliquez dessus pour l’installer (bouton installer le certificat). Installez le dans le magasin « Personnes autorisées ».

5.4. Le résultat final
Cela marche nickel !!! Mon utilisateur est bien reconnu et son nom affiché ! Mes sessions fonctionnent parfaitement, ou presque, puisqu’à la connexion suivante, je ne récupère pas ma session.

6. Conclusion
WCF apporte une grande souplesse sur la création de vos services au niveau du métier. Par contre, les couches de transport et le paramétrage s’est grandement complexifié. Le moindre paramètre de travers, vos services ne fonctionnent plus.
Pour aider à tracer l’exécution de vos services, je ne serai vous recommander ces quelques lignes dans votre Web.Config :
<configuration>
<system.diagnostics>
<sources>
<source name="System.ServiceModel"
switchValue="Information, ActivityTracing"
propagateActivity="true">
<listeners>
<add name="traceListener"
type="System.Diagnostics.XmlWriterTraceListener"
initializeData= "c:\log\Traces.svclog" />
</listeners>
</source>
</sources>
</system.diagnostics>
</configuration>