c# - webapi - Comment sécuriser une API Web ASP.NET




webapi get token (4)

Je souhaite créer un service Web RESTful à l'aide de l'API Web ASP.NET que les développeurs tiers utiliseront pour accéder aux données de mon application.

J'ai beaucoup lu sur OAuth et cela semble être la norme, mais trouver un bon échantillon avec une documentation expliquant comment cela fonctionne (et qui fonctionne vraiment!) Semble être incroyablement difficile (surtout pour un débutant à OAuth).

Y a-t-il un échantillon qui construit et fonctionne réellement et montre comment implémenter ceci?

J'ai téléchargé de nombreux échantillons:

  • DotNetOAuth - la documentation est désespérée du point de vue des débutants
  • Thinktecture - ne peut pas l'obtenir pour construire

J'ai aussi regardé des blogs suggérant un schéma simple basé sur des jetons (comme this ) - cela semble réinventer la roue mais il a l'avantage d'être conceptuellement assez simple.

Il semble qu'il y ait beaucoup de questions comme celle-là sur SO mais pas de bonnes réponses.

Que fait tout le monde dans cet espace?


Avez-vous essayé DevDefined.OAuth?

Je l'ai utilisé pour sécuriser mon WebApi avec OAuth à 2 pattes. Je l'ai également testé avec succès avec des clients PHP.

Il est assez facile d'ajouter le support pour OAuth en utilisant cette bibliothèque. Voici comment vous pouvez implémenter le fournisseur pour l'API Web ASP.NET MVC:

1) Obtenez le code source de DevDefined.OAuth: https://github.com/bittercoder/DevDefined.OAuth - la nouvelle version permet l'extensibilité OAuthContextBuilder .

2) Construisez la bibliothèque et référencez-la dans votre projet API Web.

3) Créez un générateur de contexte personnalisé pour prendre en charge la construction d'un contexte à partir de HttpRequestMessage :

using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Net.Http;
using System.Web;

using DevDefined.OAuth.Framework;

public class WebApiOAuthContextBuilder : OAuthContextBuilder
{
    public WebApiOAuthContextBuilder()
        : base(UriAdjuster)
    {
    }

    public IOAuthContext FromHttpRequest(HttpRequestMessage request)
    {
        var context = new OAuthContext
            {
                RawUri = this.CleanUri(request.RequestUri), 
                Cookies = this.CollectCookies(request), 
                Headers = ExtractHeaders(request), 
                RequestMethod = request.Method.ToString(), 
                QueryParameters = request.GetQueryNameValuePairs()
                    .ToNameValueCollection(), 
            };

        if (request.Content != null)
        {
            var contentResult = request.Content.ReadAsByteArrayAsync();
            context.RawContent = contentResult.Result;

            try
            {
                // the following line can result in a NullReferenceException
                var contentType = 
                    request.Content.Headers.ContentType.MediaType;
                context.RawContentType = contentType;

                if (contentType.ToLower()
                    .Contains("application/x-www-form-urlencoded"))
                {
                    var stringContentResult = request.Content
                        .ReadAsStringAsync();
                    context.FormEncodedParameters = 
                        HttpUtility.ParseQueryString(stringContentResult.Result);
                }
            }
            catch (NullReferenceException)
            {
            }
        }

        this.ParseAuthorizationHeader(context.Headers, context);

        return context;
    }

    protected static NameValueCollection ExtractHeaders(
        HttpRequestMessage request)
    {
        var result = new NameValueCollection();

        foreach (var header in request.Headers)
        {
            var values = header.Value.ToArray();
            var value = string.Empty;

            if (values.Length > 0)
            {
                value = values[0];
            }

            result.Add(header.Key, value);
        }

        return result;
    }

    protected NameValueCollection CollectCookies(
        HttpRequestMessage request)
    {
        IEnumerable<string> values;

        if (!request.Headers.TryGetValues("Set-Cookie", out values))
        {
            return new NameValueCollection();
        }

        var header = values.FirstOrDefault();

        return this.CollectCookiesFromHeaderString(header);
    }

    /// <summary>
    /// Adjust the URI to match the RFC specification (no query string!!).
    /// </summary>
    /// <param name="uri">
    /// The original URI. 
    /// </param>
    /// <returns>
    /// The adjusted URI. 
    /// </returns>
    private static Uri UriAdjuster(Uri uri)
    {
        return
            new Uri(
                string.Format(
                    "{0}://{1}{2}{3}", 
                    uri.Scheme, 
                    uri.Host, 
                    uri.IsDefaultPort ?
                        string.Empty :
                        string.Format(":{0}", uri.Port), 
                    uri.AbsolutePath));
    }
}

4) Utilisez ce tutoriel pour créer un fournisseur OAuth: http://code.google.com/p/devdefined-tools/wiki/OAuthProvider . Dans la dernière étape (Accès à l'exemple de ressource protégée), vous pouvez utiliser ce code dans votre attribut AuthorizationFilterAttribute :

public override void OnAuthorization(HttpActionContext actionContext)
{
    // the only change I made is use the custom context builder from step 3:
    OAuthContext context = 
        new WebApiOAuthContextBuilder().FromHttpRequest(actionContext.Request);

    try
    {
        provider.AccessProtectedResourceRequest(context);

        // do nothing here
    }
    catch (OAuthException authEx)
    {
        // the OAuthException's Report property is of the type "OAuthProblemReport", it's ToString()
        // implementation is overloaded to return a problem report string as per
        // the error reporting OAuth extension: http://wiki.oauth.net/ProblemReporting
        actionContext.Response = new HttpResponseMessage(HttpStatusCode.Unauthorized)
            {
               RequestMessage = request, ReasonPhrase = authEx.Report.ToString()
            };
    }
}

J'ai implémenté mon propre fournisseur donc je n'ai pas testé le code ci-dessus (sauf bien sûr le WebApiOAuthContextBuilder que j'utilise dans mon fournisseur) mais cela devrait fonctionner correctement.


En prolongement de la réponse de @ Cuong Le, mon approche pour empêcher les attaques de rejeu serait

// Cryptage de l'heure Unix côté client à l'aide de la clé privée partagée (ou du mot de passe de l'utilisateur)

// Envoi en tant que partie de l'en-tête de requête au serveur (API WEB)

// Décrypte l'heure Unix sur le serveur (API WEB) en utilisant la clé privée partagée (ou le mot de passe de l'utilisateur)

// Vérifier la différence de temps entre l'heure Unix du client et le temps Unix du serveur, ne doit pas être supérieure à x sec

// si ID utilisateur / mot de passe de hachage sont corrects et que le délai UnixTime décrypté est inférieur à x seconde de l'heure du serveur, alors il s'agit d'une demande valide


L'API Web a introduit un attribut [Authorize] pour assurer la sécurité. Cela peut être défini globalement (global.asx)

public static void Register(HttpConfiguration config)
{
    config.Filters.Add(new AuthorizeAttribute());
}

Ou par contrôleur:

[Authorize]
public class ValuesController : ApiController{
...

Bien sûr, votre type d'authentification peut varier et vous pouvez vouloir effectuer votre propre authentification, lorsque cela se produit, vous pouvez trouver utile d'hériter de l'attribut Authorize et l'étendre pour répondre à vos besoins:

public class DemoAuthorizeAttribute : AuthorizeAttribute
{
    public override void OnAuthorization(System.Web.Http.Controllers.HttpActionContext actionContext)
    {
        if (Authorize(actionContext))
        {
            return;
        }
        HandleUnauthorizedRequest(actionContext);
    }

    protected override void HandleUnauthorizedRequest(System.Web.Http.Controllers.HttpActionContext actionContext)
    {
        var challengeMessage = new System.Net.Http.HttpResponseMessage(System.Net.HttpStatusCode.Unauthorized);
        challengeMessage.Headers.Add("WWW-Authenticate", "Basic");
        throw new HttpResponseException(challengeMessage);
    }

    private bool Authorize(System.Web.Http.Controllers.HttpActionContext actionContext)
    {
        try
        {
            var someCode = (from h in actionContext.Request.Headers where h.Key == "demo" select h.Value.First()).FirstOrDefault();
            return someCode == "myCode";
        }
        catch (Exception)
        {
            return false;
        }
    }
}

Et dans votre contrôleur:

[DemoAuthorize]
public class ValuesController : ApiController{

Voici un lien sur d'autres implémentations personnalisées pour les Autorisations WebApi:

http://www.piotrwalat.net/basic-http-authentication-in-asp-net-web-api-using-membership-provider/


Si vous voulez sécuriser votre API dans un serveur à la mode du serveur (pas de redirection vers le site Web pour l'authentification à 2 pattes). Vous pouvez consulter le protocole OAuth2 Client Credentials Grant.

https://dev.twitter.com/docs/auth/application-only-auth

J'ai développé une bibliothèque qui peut vous aider à ajouter facilement ce type de support à votre WebAPI. Vous pouvez l'installer en tant que paquet NuGet:

https://nuget.org/packages/OAuth2ClientCredentialsGrant/1.0.0.0

La bibliothèque cible .NET Framework 4.5.

Une fois que vous avez ajouté le package à votre projet, il crée un fichier readme à la racine de votre projet. Vous pouvez regarder ce fichier readme pour voir comment configurer / utiliser ce paquet.

À votre santé!





asp.net-web-api