Module d'authentification pour les webservices ajax

Description

Microsoft ASP.net Ajax Extensions (CodeName Atlas) nous pousse de plus en plus à construire des WebServices pour des parties de notre application. Par exemple pour pouvoir utiliser le contrôle AutoComplete des toolkits il nous faut faire un WebService (.asmx) retournant une liste de suggestion en fonction d'un début de mot. Cela peut poser des problèmes de sécurité, car n'importe qui peut très facilement utiliser ce WebService.

Cet HttpModule résoud ce problème, en effet avant d'executer le WebService il vérifie si l'appel vient d'un utilisateur de notre site. Si le client demande le proxy JavaScript (monWebService.asmx/js), je l'enregistre dans une variable session. Lors d'un appel vers une méthode du WebService je vérifie si la variable Session est positionné avant de continuer. On peut également enregistrer l'utilisateur pour le WebService en utilisant la méthode static RegisterSessionForWebServiceCall de l'HttpModule. Puisque j'enregistre l'état au niveau de la session il ne faut pas oublier d'activer les session au niveau des méthodes du WebService.

Pour l'utiliser il faut d'abord enregistrer ce module dans le Web.config :
<httpModules>
<add name="WebServiceAuthenticationModule" type="Cyril.Web.HttpModules.WebServiceAuthenticationModule"/>
</httpModules>

Puis implémenter l'interface IWebServiceRequireAuthentication sur le WebService, il s'agit juste d'une interface "marqueur" il n'y aucune méthode à implémenter, mais c'est à partir de cette interface que le module sait s'il doit vérifier la requete en cours ou non.

[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
[ScriptService]
public class WebService : System.Web.Services.WebService, IWebServiceRequireAuthentication
{
[WebMethod(EnableSession=true)]
public string HelloWorld()
{
return "Hello World";
}
}

Source / Exemple :


using System;
using System.Data;
using System.Configuration;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using System.Web.SessionState;
using System.Web.Compilation;
using System.Security;
using System.Web.Hosting;
using System.Net;
using System.Reflection;

namespace Cyril.Web.HttpModules
{

    public class WebServiceAuthenticationModule : IHttpModule
    {

        private const string _isAuthenticatedKey = "__Cyril.Web.HttpModules.WebServiceAuthenticationModule.isAuthenticated";

        public void Init(HttpApplication context)
        {
            context.PostAcquireRequestState += new EventHandler(context_PostAcquireRequestState);
        }
        public void Dispose()
        {

        }

        private void context_PostAcquireRequestState(object sender, EventArgs e)
        {
            HttpApplication application = (HttpApplication)sender;

            String virtualPath = VirtualPathUtility.ToAbsolute(application.Context.Request.FilePath);
            if (virtualPath.EndsWith(".asmx") && HostingEnvironment.VirtualPathProvider.FileExists(virtualPath))
            {
                // Récuperation du type de la page demandé 
                Type compiledType = BuildManager.GetCompiledType(virtualPath);

                // on check si le type implémente l'interface IToto
                if (compiledType != null && compiledType.GetInterface(typeof(IWebServiceRequireAuthentication).FullName) == typeof(IWebServiceRequireAuthentication))
                {

                    // ici on enregistre automatiquement l'utilisateur s'il demande le proxy client
                    String pathInfo = application.Context.Request.PathInfo;
                    if (String.Equals(pathInfo, "/js", StringComparison.OrdinalIgnoreCase) || String.Equals(pathInfo, "/jsdebug", StringComparison.OrdinalIgnoreCase))
                    {
                        RegisterSessionForWebServiceCall();
                    }
                    else
                    {
                        // Si l'utilisateur n'est pas authentifié pour utiliser le WS on lance une erreur. 
                        Object isAuthenticated = application.Context.Session[_isAuthenticatedKey];
                        if (isAuthenticated == null || !(Boolean)isAuthenticated)
                        {
                            throw new WebException("DTC", WebExceptionStatus.ProtocolError);
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Register the current session to be available to call WebService.
        /// </summary>
        public static void RegisterSessionForWebServiceCall()
        {
            // on check si on a déjà enregistré la requete
            if (HttpContext.Current.Items[_isAuthenticatedKey] != null && (Boolean)HttpContext.Current.Items[_isAuthenticatedKey])
                return;

            // Certaines requêtes n'ont pas besoin de la Session, c'est le cas pour le proxy js
            // Ces deux lignes forcent la création des sessions
            if (HttpContext.Current.Session == null)
            {
                SessionStateModule sessionStateModule = ((System.Web.SessionState.SessionStateModule)(HttpContext.Current.ApplicationInstance.Modules["Session"]));
                typeof(SessionStateModule).InvokeMember("InitStateStoreItem", System.Reflection.BindingFlags.InvokeMethod | System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic, null, sessionStateModule, new Object[1] { true });
            }

            // On enregistre l'état au niveau de la session et de la requête
            HttpContext.Current.Session[_isAuthenticatedKey] = true;
            HttpContext.Current.Items[_isAuthenticatedKey] = true;
        }

    }

    /// <summary>
    /// Cette classe est un marqueur a rajouté aux WebService qui nécessite une authentication
    /// </summary>
    public interface IWebServiceRequireAuthentication : IReadOnlySessionState { }
}

Codes Sources

A voir également

Vous n'êtes pas encore membre ?

inscrivez-vous, c'est gratuit et ça prend moins d'une minute !

Les membres obtiennent plus de réponses que les utilisateurs anonymes.

Le fait d'être membre vous permet d'avoir un suivi détaillé de vos demandes et codes sources.

Le fait d'être membre vous permet d'avoir des options supplémentaires.