it-swarm.com.de

So sichern Sie eine ASP.NET-Web-API

Ich möchte einen RESTful Webdienst mit der ASP.NET-Web-API erstellen, mit der Drittanbieter auf die Daten meiner Anwendung zugreifen.

Ich habe ziemlich viel über OAuth gelesen und es scheint der Standard zu sein, aber ich finde ein gutes Beispiel mit einer Dokumentation, die erklärt, wie es funktioniert (und das tatsächlich) funktioniert!) scheint unglaublich schwierig zu sein (besonders für einen Neuling bei OAuth).

Gibt es ein Beispiel, das tatsächlich erstellt und funktioniert und zeigt, wie dies implementiert wird?

Ich habe zahlreiche Beispiele heruntergeladen:

  • DotNetOAuth - Dokumentation ist aus der Perspektive eines Neulings hoffnungslos
  • Thinktecture - kann es nicht zum Bauen bringen

Ich habe mir auch Blogs angesehen, die ein einfaches tokenbasiertes Schema vorschlagen (wie this ) - dies scheint das Rad neu zu erfinden, hat aber den Vorteil, dass es konzeptionell ziemlich einfach ist.

Es scheint, dass es viele Fragen wie diese zu SO gibt, aber keine guten Antworten.

Was macht jeder in diesem Raum?

384
Craig Shearer

Update:

Ich habe diesen Link zu meiner anderen Antwort hinzugefügt Verwendung der JWT-Authentifizierung für die ASP.NET-Web-API hier für alle, die sich für JWT interessieren.


Es ist uns gelungen, die HMAC-Authentifizierung auf die sichere Web-API anzuwenden, und es hat einwandfrei funktioniert. Bei der HMAC-Authentifizierung wird für jeden Verbraucher ein geheimer Schlüssel verwendet, von dem sowohl der Verbraucher als auch der Server wissen, dass er eine Nachricht hasht. HMAC256 sollte verwendet werden. In den meisten Fällen wird das Hash-Passwort des Verbrauchers als geheimer Schlüssel verwendet.

Die Nachricht wird normalerweise aus Daten in der HTTP-Anforderung oder sogar aus benutzerdefinierten Daten erstellt, die zum HTTP-Header hinzugefügt werden. Die Nachricht kann Folgendes enthalten:

  1. Zeitstempel: Uhrzeit, zu der die Anfrage gesendet wird (UTC oder GMT)
  2. HTTP-Verb: GET, POST, PUT, DELETE.
  3. post-Daten und Abfragezeichenfolge,
  4. URL

Unter der Haube wäre die HMAC-Authentifizierung:

Der Verbraucher sendet eine HTTP-Anfrage an den Webserver, nachdem er die Signatur (Ausgabe von hmac hash) erstellt hat, die Vorlage der HTTP-Anfrage:

User-Agent: {agent}   
Host: {Host}   
Timestamp: {timestamp}
Authentication: {username}:{signature}

Beispiel für eine GET-Anfrage:

GET /webapi.hmac/api/values

User-Agent: Fiddler    
Host: localhost    
Timestamp: Thursday, August 02, 2012 3:30:32 PM 
Authentication: cuongle:LohrhqqoDy6PhLrHAXi7dUVACyJZilQtlDzNbLqzXlw=

Die Nachricht an den Hash, um die Signatur zu erhalten:

GET\n
Thursday, August 02, 2012 3:30:32 PM\n
/webapi.hmac/api/values\n

Beispiel für POST -Anforderung mit Abfragezeichenfolge (Signatur unten ist nicht korrekt, nur ein Beispiel)

POST /webapi.hmac/api/values?key2=value2

User-Agent: Fiddler    
Host: localhost    
Content-Type: application/x-www-form-urlencoded
Timestamp: Thursday, August 02, 2012 3:30:32 PM 
Authentication: cuongle:LohrhqqoDy6PhLrHAXi7dUVACyJZilQtlDzNbLqzXlw=

key1=value1&key3=value3

Die Nachricht an den Hash, um die Signatur zu erhalten

GET\n
Thursday, August 02, 2012 3:30:32 PM\n
/webapi.hmac/api/values\n
key1=value1&key2=value2&key3=value3

Beachten Sie, dass die Formulardaten und die Abfragezeichenfolge in der richtigen Reihenfolge sein sollten, damit der Code auf dem Server die Abfragezeichenfolge und die Formulardaten erhält, um die richtige Nachricht zu erstellen.

Wenn eine HTTP-Anforderung an den Server gesendet wird, wird ein Authentifizierungsaktionsfilter implementiert, um die Anforderung zum Abrufen von Informationen zu analysieren: HTTP-Verb, Zeitstempel, URI, Formulardaten und Abfragezeichenfolge. Auf dieser Grundlage wird dann eine Signatur erstellt (hmac-Hash verwendet) Schlüssel (Hash-Passwort) auf dem Server.

Der geheime Schlüssel wird mit dem Benutzernamen auf der Anfrage aus der Datenbank abgerufen.

Anschließend vergleicht der Servercode die Signatur in der Anforderung mit der erstellten Signatur. Wenn gleich, wird die Authentifizierung bestanden, ansonsten ist sie fehlgeschlagen.

Der Code zum Erstellen der Signatur:

private static string ComputeHash(string hashedPassword, string message)
{
    var key = Encoding.UTF8.GetBytes(hashedPassword.ToUpper());
    string hashString;

    using (var hmac = new HMACSHA256(key))
    {
        var hash = hmac.ComputeHash(Encoding.UTF8.GetBytes(message));
        hashString = Convert.ToBase64String(hash);
    }

    return hashString;
}

Wie kann man also einen Wiederholungsangriff verhindern?

Fügen Sie eine Einschränkung für den Zeitstempel hinzu, etwa:

servertime - X minutes|seconds  <= timestamp <= servertime + X minutes|seconds 

(Serverzeit: Uhrzeit der Anforderung, die zum Server kommt)

Und speichern Sie die Signatur der Anforderung im Speicher (verwenden Sie MemoryCache, sollte das Zeitlimit einhalten). Wenn die nächste Anforderung dieselbe Signatur wie die vorherige Anforderung aufweist, wird sie abgelehnt.

Der Demo-Code lautet wie folgt: https://github.com/cuongle/Hmac.WebApi

285
cuongle

Ich würde vorschlagen, zuerst mit den einfachsten Lösungen zu beginnen - vielleicht reicht in Ihrem Szenario die einfache HTTP-Basisauthentifizierung + HTTPS aus.

Wenn dies nicht der Fall ist (z. B. können Sie https nicht verwenden oder benötigen eine komplexere Schlüsselverwaltung), sehen Sie sich möglicherweise die von anderen vorgeschlagenen HMAC-basierten Lösungen an. Ein gutes Beispiel für eine solche API wäre Amazon S3 ( http://s3.amazonaws.com/doc/s3-developer-guide/RESTAuthentication.html ).

Ich habe einen Blogbeitrag über die HMAC-basierte Authentifizierung in der ASP.NET-Web-API geschrieben. Es werden sowohl der Web-API-Dienst als auch der Web-API-Client erläutert, und der Code ist auf bitbucket verfügbar. http://www.piotrwalat.net/hmac-authentication-in-asp-net-web-api/

Hier ist ein Beitrag zur Standardauthentifizierung in der Web-API: http://www.piotrwalat.net/basic-http-authentication-in-asp-net-web-api-using-message-handlers/

Denken Sie daran, dass Sie höchstwahrscheinlich auch für die Bereitstellung von Client-Bibliotheken verantwortlich sind, wenn Sie eine API für Dritte bereitstellen. Die Standardauthentifizierung hat hier einen erheblichen Vorteil, da sie von Haus aus auf den meisten Programmierplattformen unterstützt wird. HMAC hingegen ist nicht so standardisiert und erfordert eine benutzerdefinierte Implementierung. Diese sollten relativ einfach sein, erfordern aber noch Arbeit.

PS. Es besteht auch die Möglichkeit, HTTPS + -Zertifikate zu verwenden. http://www.piotrwalat.net/client-certificate-authentication-in-asp-net-web-api-and-windows-store-apps/

30
Piotr Walat

Haben Sie versucht, DevDefined.OAuth?

Ich habe es verwendet, um mein WebApi mit zweibeinigem OAuth zu sichern. Ich habe es auch erfolgreich mit PHP Clients getestet.

Mit dieser Bibliothek können Sie ganz einfach Unterstützung für OAuth hinzufügen. So können Sie den Anbieter für die ASP.NET MVC-Web-API implementieren:

1) Holen Sie sich den Quellcode von DevDefined.OAuth: https://github.com/bittercoder/DevDefined.OAuth - Die neueste Version ermöglicht OAuthContextBuilder Erweiterbarkeit.

2) Erstellen Sie die Bibliothek und referenzieren Sie sie in Ihrem Web-API-Projekt.

3) Erstellen Sie einen benutzerdefinierten Context Builder, um das Erstellen eines Kontexts aus HttpRequestMessage zu unterstützen:

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) Verwenden Sie dieses Tutorial zum Erstellen eines OAuth Providers: http://code.google.com/p/devdefined-tools/wiki/OAuthProvider . Im letzten Schritt (Beispiel für den Zugriff auf geschützte Ressourcen) können Sie diesen Code in Ihrem Attribut AuthorizationFilterAttribute verwenden:

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()
            };
    }
}

Ich habe meinen eigenen Provider implementiert, daher habe ich den obigen Code nicht getestet (außer natürlich den WebApiOAuthContextBuilder, den ich in meinem Provider verwende), aber er sollte funktionieren.

23

Die Web-API hat ein Attribut [Authorize] eingeführt, um die Sicherheit zu gewährleisten. Dies kann global gesetzt werden (global.asx)

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

Oder per Controller:

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

Natürlich kann Ihre Art der Authentifizierung variieren und Sie möchten möglicherweise eine eigene Authentifizierung durchführen. In diesem Fall kann es hilfreich sein, das Berechtigungsattribut zu erben und es zu erweitern, um Ihre Anforderungen zu erfüllen:

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;
        }
    }
}

Und in Ihrem Controller:

[DemoAuthorize]
public class ValuesController : ApiController{

Hier ist ein Link zu anderen benutzerdefinierten Implementierungen für WebApi-Berechtigungen:

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

21
Dalorzo

Wenn Sie Ihre API Server für Server sichern möchten (keine Umleitung zur Website für zweibeinige Authentifizierung). Sie können sich das OAuth2 Client Credentials Grant-Protokoll ansehen.

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

Ich habe eine Bibliothek entwickelt, mit deren Hilfe Sie Ihre WebAPI auf einfache Weise um diese Art von Unterstützung erweitern können. Sie können es als NuGet-Paket installieren:

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

Die Bibliothek zielt auf .NET Framework 4.5 ab.

Sobald Sie das Paket zu Ihrem Projekt hinzugefügt haben, wird im Stammverzeichnis Ihres Projekts eine Readme-Datei erstellt. In dieser Readme-Datei können Sie nachlesen, wie Sie dieses Paket konfigurieren/verwenden.

Prost!

6
Varun Chatterji

in Fortsetzung der Antwort von @ Cuong Le wäre mein Ansatz, Wiederholungsangriffe zu verhindern, der folgende

// Verschlüsseln Sie die Unix-Zeit auf der Client-Seite mit dem gemeinsam genutzten privaten Schlüssel (oder dem Kennwort des Benutzers).

// Sende es als Teil des Request Headers an den Server (WEB API)

// Entschlüsseln Sie die Unix-Zeit auf dem Server (WEB-API) mit dem gemeinsam genutzten privaten Schlüssel (oder dem Kennwort des Benutzers).

// Überprüfen Sie die Zeitdifferenz zwischen der Unix-Zeit des Clients und der Unix-Zeit des Servers. Sie sollte nicht größer als x Sek. Sein

// Wenn die Benutzer-ID/das Hash-Passwort korrekt sind und die entschlüsselte UnixTime innerhalb von x Sekunden der Serverzeit liegt, handelt es sich um eine gültige Anforderung

3
refactor