ASP.NET Web API'sini koruma [kapalı]


397

Üçüncü taraf geliştiricilerin uygulamamın verilerine erişmek için kullanacakları ASP.NET Web API'sini kullanarak RESTful bir web hizmeti oluşturmak istiyorum .

OAuth hakkında çok şey okudum ve standart gibi görünüyor, ancak nasıl çalıştığını açıklayan belgelerle iyi bir örnek bulmak (ve aslında işe yarıyor!) İnanılmaz derecede zor görünüyor (özellikle OAuth'a yeni başlayanlar için).

Aslında bunu oluşturan ve çalışan ve bunun nasıl uygulanacağını gösteren bir örnek var mı?

Çok sayıda örnek indirdim:

  • DotNetOAuth - Yeni başlayanlar için dokümantasyon umutsuz
  • Thinktecture - inşa edemiyorum

Ben de (gibi basit bir belirteci tabanlı düzeni düşündüren bloglar baktım bu ) - bu yeniden icat tekerlek gibi görünüyor ama kavramsal olarak oldukça basit olma avantajına sahip.

Öyle görünüyor ki SO üzerinde böyle birçok soru var ama iyi cevap yok.

Herkes bu alanda ne yapıyor?

Yanıtlar:


292

Güncelleme:

Bu bağlantıyı ASP.NET Web API için JWT kimlik doğrulamasını JWT ile ilgilenen herkes için nasıl kullanacağımı diğer yanıma ekledim .


Web API'sını güvenli hale getirmek için HMAC kimlik doğrulamasını uygulamayı başardık ve sorunsuz çalıştı. HMAC kimlik doğrulaması, her tüketici için hem tüketici hem de sunucunun hmac karma mesajını bildikleri gizli bir anahtar kullanır, HMAC256 kullanılmalıdır. Çoğu durumda, tüketicinin karma şifresi gizli bir anahtar olarak kullanılır.

İleti normalde HTTP isteğindeki verilerden veya HTTP başlığına eklenen özelleştirilmiş verilerden oluşturulur, ileti şunları içerebilir:

  1. Zaman damgası: isteğin gönderildiği zaman (UTC veya GMT)
  2. HTTP fiili: GET, POST, PUT, DELETE.
  3. veri ve sorgu dizesi yayınlamak,
  4. URL

Gelişmiş, HMAC kimlik doğrulaması:

Tüketici, HTTP isteğinin şablonu olan imzayı (hmac karma çıktısı) oluşturduktan sonra web sunucusuna bir HTTP isteği gönderir:

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

GET talebi için örnek:

GET /webapi.hmac/api/values

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

İmza almak için karma mesaj:

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

Sorgu dizeli POST isteği örneği (aşağıdaki imza doğru değil, sadece bir örnek)

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

İmza almak için karma mesaj

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

Form verilerinin ve sorgu dizesinin sırayla olması gerektiğini lütfen unutmayın, böylece sunucudaki kod doğru iletiyi oluşturmak için sorgu dizesini ve form verilerini alır.

HTTP isteği sunucuya geldiğinde, bilgi alma isteğini ayrıştırmak için bir kimlik doğrulama eylem filtresi uygulanır: HTTP fiil, zaman damgası, uri, form verileri ve sorgu dizesi, daha sonra bunları sır ile imza oluşturmak (hmac hash kullanın) anahtarı (karma parola) girin.

Gizli anahtar, istek üzerine kullanıcı adı ile veritabanından alınır.

Daha sonra sunucu kodu, istek üzerine imza ile oluşturulan imzayı karşılaştırır; eşitse, kimlik doğrulaması geçilir, aksi takdirde başarısız olur.

İmza oluşturmak için kod:

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

Peki, tekrar saldırısı nasıl önlenir?

Zaman damgası için kısıtlama ekleyin:

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

(servertime: sunucuya gelen istek zamanı)

Ve isteğin imzasını bellekte önbelleğe alın (MemoryCache kullanın, zaman sınırında kalmalıdır). Bir sonraki istek, önceki istekle aynı imzayla gelirse, reddedilir.

Demo kodu şu şekildedir: https://github.com/cuongle/Hmac.WebApi


2
@James: sadece zaman damgası çok yeterli görünmüyor, kısa süre içinde isteği simüle edebilir ve sunucuya gönderilebilir, yazımı düzenledim, her ikisini de kullanın en iyisi olacaktır.
cuongle

1
Bunun olması gerektiği gibi çalıştığından emin misiniz? zaman damgasını mesajla özetler ve bu mesajı önbelleğe alırsınız. Bu, her istek farklı bir imza anlamına gelir ve bu da önbelleğe alınmış imzanızı işe yaramaz hale getirir.
Filip Stas

1
@FilipStas: Senin fikrini anlamadım, burada Cache kullanmanın nedeni röle saldırısını önlemek, başka bir şey değil
cuongle

1
@ChrisO: [Bu sayfa] 'ya başvurabilirsiniz ( jokecamp.wordpress.com/2012/10/21/… ). Bu kaynağı yakında güncelleyeceğim
cuongle

1
Önerilen çözüm işe yarıyor, ancak Ortadaki Adam saldırısını önleyemezsiniz, bunun için HTTPS'yi uygulamak zorundasınız
refactor

34

İlk önce en basit çözümlerle başlamayı öneririm - belki de basit HTTP Temel Kimlik Doğrulama + HTTPS, senaryonuzda yeterlidir.

Değilse (örneğin, https kullanamazsınız veya daha karmaşık anahtar yönetimine ihtiyacınız olmaz), başkaları tarafından önerilen HMAC tabanlı çözümlere göz atabilirsiniz. Bu tür API'lara iyi bir örnek Amazon S3'tür ( http://s3.amazonaws.com/doc/s3-developer-guide/RESTAuthentication.html )

ASP.NET Web API'sında HMAC tabanlı kimlik doğrulama hakkında bir blog yazısı yazdım. Hem Web API hizmetini hem de Web API istemcisini tartışır ve kod bitbucket'te kullanılabilir. http://www.piotrwalat.net/hmac-authentication-in-asp-net-web-api/

Web API'sında Temel Kimlik Doğrulama hakkında bir yazı: http://www.piotrwalat.net/basic-http-authentication-in-asp-net-web-api-using-message-handlers/

Üçüncü taraflara API sağlayacaksanız, büyük olasılıkla istemci kitaplıklarını sunmaktan da sorumlu olacağınızı unutmayın. Temel kimlik doğrulamanın, kutudan çıkan çoğu programlama platformunda desteklendiği için burada önemli bir avantajı vardır. Öte yandan HMAC standart değildir ve özel uygulama gerektirecektir. Bunlar nispeten basit olmalı, ancak yine de iş gerektirmelidir.

PS. HTTPS + sertifikalarını kullanma seçeneği de vardır. http://www.piotrwalat.net/client-certificate-authentication-in-asp-net-web-api-and-windows-store-apps/


23

DevDefined.OAuth'u denediniz mi?

WebApi'mi 2 Aşamalı OAuth ile güvence altına almak için kullandım. Ben de başarıyla PHP istemcileri ile test ettim.

Bu kütüphaneyi kullanarak OAuth için destek eklemek oldukça kolaydır. ASP.NET MVC Web API sağlayıcısını nasıl uygulayabileceğiniz aşağıda açıklanmıştır:

1) DevDefined.OAuth'un kaynak kodunu alın: https://github.com/bittercoder/DevDefined.OAuth - en yeni sürüm OAuthContextBuildergenişletilebilirliğe izin verir .

2) Kütüphaneyi oluşturun ve Web API projenize referans verin.

3) Aşağıdakilerden bir içerik oluşturmayı desteklemek için özel bir içerik oluşturucu oluşturun 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) Bir OAuth sağlayıcısı oluşturmak için bu öğreticiyi kullanın: http://code.google.com/p/devdefined-tools/wiki/OAuthProvider . Son adımda (Korunan Kaynağa Erişim Örneği) bu kodu AuthorizationFilterAttributeözniteliğinizde kullanabilirsiniz:

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

Ben kendi sağlayıcı uyguladım bu yüzden (tabii ki WebApiOAuthContextBuilderbenim sağlayıcıda kullandığım hariç) yukarıdaki kodu test etmedi ama iyi çalışması gerekir.


Teşekkürler - Şuna bir bakacağım, ancak şimdilik kendi HMAC tabanlı çözümümü yuvarladım.
Craig Shearer

1
@CraigShearer - merhaba, kendininkini yuvarladığını söylüyorsun .. paylaşmanın sakıncası yoksa sadece birkaç sorum oldu. Ben nispeten küçük bir MVC Web API var benzer bir konumda değilim. API denetleyicileri, form yetkisi altındaki diğer denetleyicilerin / eylemlerin yanında bulunur. OAuth uygulamak zaten kullanabileceğim bir üyelik sağlayıcısı olduğunda aşırıya kaçıyor gibi görünüyor ve sadece bir avuç operasyonu güvence altına almam gerekiyor. Gerçekten şifreli bir belirteç döndüren bir kimlik doğrulama eylemi istiyorum - daha sonra sonraki çağrılarda belirteci kullanılır? Mevcut bir kimlik doğrulama çözümü uygulamayı taahhüt etmeden önce herhangi bir bilgi hoş geldiniz. Teşekkürler!
sambomartin

@Maksymilian Majer - Sağlayıcıyı nasıl uyguladığınızı paylaşma şansınız var mı? Yanıtları istemciye geri gönderirken bazı sorunlar yaşıyorum.
jlrolin

21

Web API [Authorize], güvenlik sağlamak için bir Özellik tanıttı . Bu global olarak ayarlanabilir (global.asx)

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

Veya denetleyici başına:

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

Elbette, kimlik doğrulama türünüz değişebilir ve kendi kimlik doğrulamanızı yapmak isteyebilirsiniz, bu durumda Yetkilendirme Özelliğinden devralınmayı ve gereksinimlerinizi karşılayacak şekilde genişletmeyi yararlı bulabilirsiniz:

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

Ve kumandanızda:

[DemoAuthorize]
public class ValuesController : ApiController{

WebApi Yetkilendirmeleri için diğer özel uygulamalara ilişkin bir bağlantı:

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


@Dalorzo örneği için teşekkürler, ancak bazı sorunlar var. Ekteki bağlantıya baktım, ancak bu talimatları takip etmek işe yaramıyor. Ayrıca gerekli bilgi eksik bulundu. İlk olarak, yeni projeyi oluşturduğumda, kimlik doğrulama için Bireysel Kullanıcı Hesaplarını seçmek doğru mu? Yoksa kimlik doğrulamamı bırakıyorum. Ayrıca belirtilen 302 hatasını almıyorum, ancak 401 hatası alıyorum. Son olarak, gerekli bilgileri görüşümden denetleyiciye nasıl iletirim? Ajax çağrım nasıl olmalı? Btw, MVC görünümlerim için form kimlik doğrulaması kullanıyorum. Bu bir problem mi?
Amanda

Fevkalade çalışıyor. Öğrenmek ve kendi erişim belirteçlerimiz üzerinde çalışmaya başlamak çok güzel.
KodAdı47

Küçük bir yorum - AuthorizeAttributefarklı ad alanlarında aynı ada sahip iki farklı sınıf olduğundan dikkatli olun : 1. System.Web.Mvc.AuthorizeAttribute -> MVC denetleyicileri için 2. System.Web.Http.AuthorizeAttribute -> WebApi için.
Vitaliy Markitanov

5

API'nizi bir sunucuda sunucu biçiminde korumak istiyorsanız (2 aşamalı kimlik doğrulaması için web sitesine yeniden yönlendirme yapılmaz). OAuth2 İstemci Kimlik Bilgileri Verme protokolüne bakabilirsiniz.

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

WebAPI'nıza bu tür desteği kolayca eklemenize yardımcı olabilecek bir kütüphane geliştirdim. Bir NuGet paketi olarak kurabilirsiniz:

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

Kitaplık .NET Framework 4.5'i hedefler.

Paketi projenize ekledikten sonra, projenizin kök dizininde bir benioku dosyası oluşturur. Bu paketin nasıl yapılandırılacağını / kullanılacağını görmek için o benioku dosyasına bakabilirsiniz.

Şerefe!


5
Bu çerçeve için açık kaynak olarak kaynak kodu paylaşıyor / sağlıyor musunuz?
barrypicker

JFR: İlk Bağlantı
Bozuldu ve NuGet

3

@ Cuong Le'nin cevabına devam ederken, tekrar saldırılarını önleme yaklaşımım

// Paylaşılan özel anahtarı (veya kullanıcının şifresini) kullanarak Unix Zamanını İstemci tarafında şifreleyin

// İstek üstbilgisinin bir parçası olarak sunucuya gönder (WEB API'sı)

// Paylaşılan özel anahtarı (veya kullanıcının parolasını) kullanarak Sunucudaki Unix Zamanının (WEB API) şifresini çözme

// İstemcinin Unix Saati ile Sunucunun Unix Süresi arasındaki zaman farkını kontrol edin, x saniyeden fazla olmamalıdır

// Kullanıcı Kimliği / Karma Parola doğruysa ve şifresi çözülmüş UnixTime sunucu süresinin x saniyesinde ise geçerli bir istektir

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.