407 Stimmen

Wie man eine ASP.NET Web API sichert

Ich möchte eine RESTful Webdienst mit ASP.NET Web API, den Drittentwickler für den Zugriff auf die Daten meiner Anwendung verwenden werden.

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

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

Ich habe zahlreiche Beispiele heruntergeladen:

  • DotNetOAuth - Dokumentation ist aus der Sicht eines Neulings hoffnungslos
  • Thinktecture - kann nicht erstellt werden

Ich habe mir auch Blogs angesehen, die ein einfaches Token-basiertes System vorschlagen (wie diese ) - das scheint, als ob man das Rad neu erfinden müsste, aber es hat den Vorteil, dass es konzeptionell recht einfach ist.

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

Was machen alle in diesem Bereich?

300voto

cuongle Punkte 71553

Aktualisierung:

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


Wir haben es geschafft, die HMAC-Authentifizierung auf eine sichere Web-API anzuwenden, und es hat gut funktioniert. Bei der HMAC-Authentifizierung wird für jeden Verbraucher ein geheimer Schlüssel verwendet, der sowohl dem Verbraucher als auch dem Server bekannt ist, um eine Nachricht mit HMAC256 zu verschlüsseln. In den meisten Fällen wird das gehashte Passwort des Verbrauchers als geheimer Schlüssel verwendet.

Die Nachricht wird in der Regel aus Daten in der HTTP-Anfrage oder sogar aus benutzerdefinierten Daten, die dem HTTP-Header hinzugefügt werden, erstellt:

  1. Zeitstempel: Zeitpunkt der Übermittlung der Anfrage (UTC oder GMT)
  2. HTTP-Verb: GET, POST, PUT, DELETE.
  3. Postdaten und Abfragezeichenfolge,
  4. URL

Unter der Haube würde die HMAC-Authentifizierung so aussehen:

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

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 zu hashende Nachricht, um die Signatur zu erhalten:

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

Beispiel für eine POST-Anfrage mit Query-String (die nachstehende Signatur ist nicht korrekt, sondern 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 zu hashende Nachricht, 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

Bitte beachten Sie, dass die Formulardaten und der Abfrage-String in der richtigen Reihenfolge sein sollten, damit der Code auf dem Server den Abfrage-String und die Formulardaten erhält, um die richtige Nachricht zu erstellen.

Wenn eine HTTP-Anfrage auf dem Server eingeht, wird ein Authentifizierungsfilter implementiert, um die Anfrage zu analysieren und Informationen zu erhalten: HTTP-Verb, Zeitstempel, URL, Formulardaten und Abfragezeichenfolge. Auf dieser Grundlage wird dann auf dem Server eine Signatur (unter Verwendung eines Hmac-Hashes) mit dem geheimen Schlüssel (gehashtes Passwort) erstellt.

Der geheime Schlüssel wird zusammen mit dem Benutzernamen bei der Anfrage aus der Datenbank geholt.

Dann vergleicht der Servercode die Signatur der Anfrage mit der erstellten Signatur; ist sie gleich, wird die Authentifizierung bestanden, andernfalls ist sie fehlgeschlagen.

Der Code zur Erstellung 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 Replay-Angriff verhindern?

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

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

(servertime: Zeitpunkt des Eingangs der Anfrage beim Server)

Und, Cache die Signatur der Anfrage im Speicher (verwenden MemoryCache, sollte in der Grenze der Zeit zu halten). Wenn die nächste Anforderung mit der gleichen Signatur wie die vorherige Anforderung kommt, wird sie abgelehnt.

Der Demo-Code ist hier zu finden: https://github.com/cuongle/Hmac.WebApi

34voto

Piotr Walat Punkte 971

Ich würde vorschlagen, zunächst mit den einfachsten Lösungen zu beginnen - vielleicht ist eine einfache HTTP-Basisauthentifizierung + HTTPS in Ihrem Szenario ausreichend.

Wenn dies nicht der Fall ist (z. B. weil Sie kein https verwenden können oder eine komplexere Schlüsselverwaltung benötigen), können Sie sich die von anderen vorgeschlagenen HMAC-basierten Lösungen ansehen. 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 HMAC-basierte Authentifizierung in ASP.NET Web API geschrieben. Er behandelt sowohl den Web-API-Dienst als auch den Web-API-Client und der Code ist auf Bitbucket verfügbar. http://www.piotrwalat.net/hmac-authentication-in-asp-net-web-api/

Hier ist ein Beitrag über Basic Authentication in Web API: http://www.piotrwalat.net/basic-http-authentication-in-asp-net-web-api-using-message-handlers/

Denken Sie daran, dass Sie, wenn Sie Dritten eine API zur Verfügung stellen, höchstwahrscheinlich auch für die Bereitstellung von Client-Bibliotheken verantwortlich sein werden. Die Basisauthentifizierung hat hier einen erheblichen Vorteil, da sie von den meisten Programmierplattformen standardmäßig unterstützt wird. HMAC hingegen ist nicht so standardisiert und erfordert eine eigene Implementierung. Diese sollten relativ einfach sein, erfordern aber dennoch Arbeit.

PS. Es gibt auch eine Option zur Verwendung von HTTPS + Zertifikaten. http://www.piotrwalat.net/client-certificate-authentication-in-asp-net-web-api-and-Windows-store-apps/

23voto

Maksymilian Majer Punkte 2908

Haben Sie DevDefined.OAuth ausprobiert?

Ich habe es verwendet, um meine WebApi mit 2-Legged OAuth zu sichern. Ich habe es auch erfolgreich mit PHP-Clients getestet.

Es ist recht einfach, mit dieser Bibliothek Unterstützung für OAuth hinzuzufügen. Hier erfahren Sie, wie Sie den Provider für ASP.NET MVC Web API implementieren können:

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 die Erstellung eines Kontexts aus 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) Verwenden Sie diese Anleitung zum Erstellen eines OAuth-Anbieters: 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 Ihrer AuthorizationFilterAttribute Attribut:

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 die WebApiOAuthContextBuilder die ich bei meinem Provider verwende), aber es sollte gut funktionieren.

22voto

Dalorzo Punkte 19688

Web API hat ein Attribut eingeführt [Authorize] um Sicherheit zu bieten. Dies kann global eingestellt werden (global.asx)

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

Oder pro Controller:

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

Natürlich kann Ihre Art der Authentifizierung variieren und Sie möchten vielleicht Ihre eigene Authentifizierung durchführen. In diesem Fall kann es nützlich sein, von Authorizate Attribute 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-Autorisierungen:

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

5voto

Varun Chatterji Punkte 5019

Wenn Sie Ihre API auf Server-zu-Server-Basis absichern wollen (keine Umleitung zur Website für die zweigleisige 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 der Sie diese Art von Unterstützung leicht zu Ihrer WebAPI hinzufügen können. Sie können sie als NuGet-Paket installieren:

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

Die Bibliothek zielt auf .NET Framework 4.5 ab.

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

Zum Wohl!

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X