Par défaut l’implémentation de WCF dans SilverLight ne permet pas d’utiliser une authentification sur un service WCF. Voila pourquoi après plusieurs tests et en m’inspirant de quelques bribes de code trouvées sur le web j’ai mis en place un dispositif d’authentification entre WCF et SilverLight en faisant passer des informations via les entêtes HTTP des requêtes formulées via WCF.
En gros, pour faire simple:
- Du côté client (SilverLight) : j’insert des informations dans les entêtes HTTP émises avec les requêtes de SilverLight.
- Du côté serveur (service WCF): j’utilise un mécanisme d’interception propre à WCF pour effectuer une vérification de entêtes HTTP. Si mes entêtes sont valide j’autorise l’appel de la méthode, si non je retourne une erreur au client.
Voici donc le code utilisé côté client j’utilise une méthode qui m’instancie mon proxy en ajoutant au passage un variable dans mes entêtes HTTP (ServiceToken dans mon exemple)
using System; using System.ServiceModel; using System.ServiceModel.Channels; public static class MonService { public static TestSilverlight.MonProxy.MonServiceClient Instance { get { // Recherche de l'uri du service String uri = System.Windows.Browser.HtmlPage.Document.DocumentUri.AbsoluteUri .Substring(0, System.Windows.Browser.HtmlPage.Document.DocumentUri.AbsoluteUri.LastIndexOf('/') + 1); BasicHttpBinding binding = new BasicHttpBinding(); TestSilverlight.MonProxy.MonServiceClient service = new TestSilverlight.MonProxy.MonServiceClient( binding, new EndpointAddress( String.Concat( uri, "MonService.svc")) ); OperationContextScope scope = new OperationContextScope(service.InnerChannel); OperationContext.Current.OutgoingMessageHeaders.Add(MessageHeader.CreateHeader( "LeNomDeMaVariable", String.Empty, "LaValeurDeMaVariable")); return service; } } }
Côté service j’ajoute deux classe : un Behavior et un Invoker qui vont servir à intercepter mes appels à mes méthodes et
Voici l’invoker qui vas être appelé par le B
using System; using System.ServiceModel.Dispatcher; using System.ServiceModel.Channels; using System.ServiceModel; using System.Configuration; namespace Authentification { public class AuthentificationInvoker : IOperationInvoker { /// <summary> /// Opération initaliement invoquée /// </summary> private IOperationInvoker _operationInvoker; /// <summary> /// Contrstructeur /// </summary> /// <param name="operationInvoker"></param> public AuthentificationInvoker(IOperationInvoker operationInvoker) { this._operationInvoker = operationInvoker; } /// <summary> /// Test si on autorise la méthode à être executée /// </summary> /// <param name="instance"></param> /// <param name="inputs"></param> /// <param name="outputs"></param> /// <returns></returns> public Object Invoke(Object instance, Object[] inputs, out Object[] outputs) { // Lecture de l'entête de la requete MessageHeaders headers = OperationContext.Current.IncomingMessageHeaders; Int32 i = headers.FindHeader("LeNomDeMaVariable", String.Empty); if (i > -1 & headers.GetHeader<String>("LeNomDeMaVariable", String.Empty) == "LaValeurDeMaVariable") { return this._operationInvoker.Invoke(instance, inputs, out outputs); } else { outputs = new Object[] { "Accès interdit" }; return "Accès interdit"; } } #region Autres opération (non modifiées) public Object[] AllocateInputs() { return this._operationInvoker.AllocateInputs(); } public IAsyncResult InvokeBegin(Object instance, Object[] inputs, AsyncCallback callback, Object state) { return this._operationInvoker.InvokeBegin(instance, inputs, callback, state); } public Object InvokeEnd(Object instance, out Object[] outputs, IAsyncResult result) { return this._operationInvoker.InvokeEnd(instance, out outputs, result); } public Boolean IsSynchronous { get { return this._operationInvoker.IsSynchronous; } } #endregion } }
Et le Behavior qui utilisera l’Invoker:
using System; using System.ServiceModel.Description; namespace Authentification { [AttributeUsage(AttributeTargets.Method)] public class AuthentificationBehavior : Attribute, IOperationBehavior { /// <summary> /// Substitution de l'invocation initiale de la méthode /// </summary> /// <param name="operationDescription"></param> /// <param name="dispatchOperation"></param> public void ApplyDispatchBehavior(OperationDescription operationDescription, System.ServiceModel.Dispatcher.DispatchOperation dispatchOperation) { // Subtitution de l'onvoker initialement utilisé dispatchOperation.Invoker = new AuthentificationInvoker(dispatchOperation.Invoker); } #region Non utilisé public void AddBindingParameters(OperationDescription operationDescription, System.ServiceModel.Channels.BindingParameterCollection bindingParameters) { } public void ApplyClientBehavior(OperationDescription operationDescription, System.ServiceModel.Dispatcher.ClientOperation clientOperation) { } public void Validate(OperationDescription operationDescription) { } #endregion } }
Ensuite pour mettre en place le Behavior sur une méthode il me suffit d’éjouter l’attibut [Authentification.AuthentificationBehavior] à la méthode voulue. Dans mon exemple :
using System; using System.Linq; using System.Runtime.Serialization; using System.ServiceModel; using System.ServiceModel.Activation; using System.Collections.Generic; using System.Text; namespace TestSilverlight.Web { [ServiceContract(Namespace = "")] [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)] public class MonService { [OperationContract] [Authentification.AuthentificationBehavior] public String MaMethode() { // Ajoutez votre implémentation d'opération ici // ... } } }
Et nous voici donc avec une authentification opérationnelle entre SilverLight et un service WCF.
Dans un cadre de mise en production il vas s’en dire qu’il est fortement conseillé de stocker les éléments passer en entête dans un dispositif de stockage sûr et d’employer SSL afin de crypter les échanges.
Mes sources utilisées pour atteindre ce résultat sont le Forum Silverlight et surtout le blog de Kevin Dockx où j’ai découvert l’Invoker.