ASP.NET Web API 2.2'de Bireysel Hesaplar ve Yerel Oturum Açma ile Web API'lerinin güvenliğini sağlama

Tarafından Mike Wasson

Örnek Uygulamayı İndir

Bu konuda, üyelik veritabanında kimlik doğrulaması yapmak için OAuth2 kullanarak bir web API'sini güvenli hale getirmek gösterilmektedir.

Öğreticide kullanılan yazılım sürümleri

Visual Studio 2013'de Web API proje şablonu size kimlik doğrulaması için üç seçenek sunar:

  • Bireysel hesaplar. Uygulama bir üyelik veritabanı kullanır.
  • Kuruluş hesapları. Kullanıcılar Azure Active Directory, Office 365 veya şirket içi Active Directory kimlik bilgileriyle oturum açar.
  • Windows kimlik doğrulaması. Bu seçenek Intranet uygulamalarına yöneliktir ve Windows Kimlik Doğrulaması IIS modülünü kullanır.

Bu seçenekler hakkında daha fazla ayrıntı için bkz. Visual Studio 2013'da ASP.NET Web Projeleri Oluşturma.

Bireysel hesaplar, kullanıcının oturum açması için iki yol sağlar:

  • Yerel oturum açma. Kullanıcı siteye kaydolarak bir kullanıcı adı ve parola girer. Uygulama, üyelik veritabanında parola karması depolar. Kullanıcı oturum açtığında, ASP.NET Kimliği sistemi parolayı doğrular.
  • Sosyal oturum açma. Kullanıcı Facebook, Microsoft veya Google gibi bir dış hizmetle oturum açar. Uygulama yine de üyelik veritabanında kullanıcı için bir giriş oluşturur, ancak kimlik bilgilerini depolamaz. Kullanıcı, dış hizmette oturum açarak kimlik doğrulaması yapar.

Bu makalede yerel oturum açma senaryosuna bakabilirsiniz. Web API'sinde hem yerel hem de sosyal oturum açma bilgileri için isteklerin kimliğini doğrulamak için OAuth2 kullanılır. Ancak, kimlik bilgisi akışları yerel ve sosyal oturum açma için farklıdır.

Bu makalede, kullanıcının oturum açmasına ve kimliği doğrulanmış AJAX çağrılarını bir web API'sine göndermesine olanak tanıyan basit bir uygulama göstereceğim. Örnek kodu buradan indirebilirsiniz. Benioku, Visual Studio'da sıfırdan örneğin nasıl oluşturulacağını açıklar.

Örnek formun görüntüsü

Örnek uygulama, veri bağlama için Knockout.js ve AJAX istekleri göndermek için jQuery kullanır. AJAX aramalarına odaklanacağım, bu nedenle bu makalenin Knockout.js bilmenize gerek yok.

Yol boyunca şunları açıklayacağım:

  • Uygulamanın istemci tarafında yaptığı şey.
  • Sunucuda olup bitenler.
  • Ortadaki HTTP trafiği.

İlk olarak, bazı OAuth2 terminolojisini tanımlamamız gerekir.

  • Kaynak. Korunabilecek bazı veriler.
  • Kaynak sunucusu. Kaynağı barındıran sunucu.
  • Kaynak sahibi. Kaynağa erişim izni verebilen varlık. (Genellikle kullanıcıdır.)
  • İstemci: Kaynağa erişmek isteyen uygulama. Bu makalede, istemci bir web tarayıcısıdır.
  • Erişim belirteci. Bir kaynağa erişim izni veren belirteç.
  • Taşıyıcı belirteci. Belirteci herkesin kullanabileceği özelliğine sahip belirli bir erişim belirteci türü. Başka bir deyişle, bir istemcinin taşıyıcı belirteci kullanmak için şifreleme anahtarına veya başka bir gizli diziye ihtiyacı yoktur. Bu nedenle taşıyıcı belirteçlerin yalnızca https üzerinden kullanılması ve görece kısa süre sonu süreleri olması gerekir.
  • Yetkilendirme sunucusu. Erişim belirteçleri veren bir sunucu.

Bir uygulama hem yetkilendirme sunucusu hem de kaynak sunucusu olarak görev yapabilir. Web API proje şablonu bu deseni izler.

Yerel Oturum Açma Kimlik Bilgisi Akışı

Yerel oturum açma için Web API'sinde OAuth2'de tanımlanan kaynak sahibi parola akışı kullanılır.

  1. Kullanıcı istemciye bir ad ve parola girer.
  2. İstemci bu kimlik bilgilerini yetkilendirme sunucusuna gönderir.
  3. Yetkilendirme sunucusu kimlik bilgilerinin kimliğini doğrular ve bir erişim belirteci döndürür.
  4. Korumalı bir kaynağa erişmek için istemci, ERIŞIM belirtecini HTTP isteğinin Yetkilendirme üst bilgisine ekler.

Yerel oturum açma kimlik bilgisi akışının diyagramı

Web API proje şablonunda Bireysel hesaplar'ı seçtiğinizde, proje kullanıcı kimlik bilgilerini doğrulayan ve belirteçleri veren bir yetkilendirme sunucusu içerir. Aşağıdaki diyagramda, Web API bileşenleri açısından aynı kimlik bilgisi akışı gösterilmektedir.

Web A P I'de tek tek hesapların seçildiği diyagram

Bu senaryoda, Web API denetleyicileri kaynak sunucusu görevi görür. Kimlik doğrulama filtresi erişim belirteçlerini doğrular ve kaynağı korumak için [Authorize] özniteliği kullanılır. Bir denetleyici veya eylem [Authorize] özniteliğine sahip olduğunda, bu denetleyiciye veya eyleme yapılan tüm isteklerin kimliği doğrulanmalıdır. Aksi takdirde yetkilendirme reddedilir ve Web API'si 401 (Yetkisiz) hatası döndürür.

Yetkilendirme sunucusu ve kimlik doğrulama filtresi, OAuth2'nin ayrıntılarını işleyen bir OWIN ara yazılım bileşenine çağrır. Bu öğreticinin ilerleyen bölümlerinde tasarımı daha ayrıntılı olarak açıklayacağım.

Yetkisiz İstek Gönderme

Başlamak için uygulamayı çalıştırın ve API'yi Çağır düğmesine tıklayın. İstek tamamlandığında Sonuç kutusunda bir hata iletisi görmeniz gerekir. Bunun nedeni, isteğin bir erişim belirteci içermemesi ve dolayısıyla isteğin yetkisiz olmasıdır.

Sonuç hata iletisinin görüntüsü

ÇAĞRı API'si düğmesi~ /api/values öğesine bir AJAX isteği gönderir ve bu da bir Web API denetleyicisi eylemi çağırır. AJAX isteğini gönderen JavaScript kodunun bölümü aşağıdadır. Örnek uygulamada, tüm JavaScript uygulama kodu Scripts\app.js dosyasında bulunur.

// If we already have a bearer token, set the Authorization header.
var token = sessionStorage.getItem(tokenKey);
var headers = {};
if (token) {
    headers.Authorization = 'Bearer ' + token;
}

$.ajax({
    type: 'GET',
    url: 'api/values/1',
    headers: headers
}).done(function (data) {
    self.result(data);
}).fail(showError);

Kullanıcı oturum açana kadar taşıyıcı belirteci yoktur ve bu nedenle istekte Yetkilendirme üst bilgisi yoktur. Bu, isteğin 401 hatası döndürmesine neden olur.

HTTP isteği aşağıdadır. (HTTP trafiğini yakalamak için Fiddler'ı kullandım.)

GET https://localhost:44305/api/values HTTP/1.1
Host: localhost:44305
User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64; rv:32.0) Gecko/20100101 Firefox/32.0
Accept: */*
Accept-Language: en-US,en;q=0.5
X-Requested-With: XMLHttpRequest
Referer: https://localhost:44305/

HTTP yanıtı:

HTTP/1.1 401 Unauthorized
Content-Type: application/json; charset=utf-8
Server: Microsoft-IIS/8.0
WWW-Authenticate: Bearer
Date: Tue, 30 Sep 2014 21:54:43 GMT
Content-Length: 61

{"Message":"Authorization has been denied for this request."}

Yanıtın, sınamanın Taşıyıcı olarak ayarlandığı bir Www-Authenticate üst bilgisi içerdiğine dikkat edin. Bu, sunucunun taşıyıcı belirteç beklediğini gösterir.

Kullanıcı Kaydetme

Uygulamanın Kaydet bölümünde bir e-posta ve parola girin ve Kaydet düğmesine tıklayın.

Bu örnek için geçerli bir e-posta adresi kullanmanız gerekmez, ancak gerçek bir uygulama adresi onaylar. (Bkz. Oturum açma, e-posta onayı ve parola sıfırlama ile güvenli ASP.NET MVC 5 web uygulaması oluşturma.) Parola için büyük harf, küçük harf, sayı ve alfasayısal olmayan karakter içeren "Parola1!" gibi bir karakter kullanın. Uygulamayı basit tutmak için istemci tarafı doğrulamayı unuttum, bu nedenle parola biçimiyle ilgili bir sorun varsa 400 (Hatalı İstek) hatası alırsınız.

Kullanıcı kaydetme bölümünün resmi

Kaydet düğmesi ~/api/Account/Register/ adresine bir POST isteği gönderir. İstek gövdesi, adı ve parolayı tutan bir JSON nesnesidir. İsteği gönderen JavaScript kodu aşağıdadır:

var data = {
    Email: self.registerEmail(),
    Password: self.registerPassword(),
    ConfirmPassword: self.registerPassword2()
};

$.ajax({
    type: 'POST',
    url: '/api/Account/Register',
    contentType: 'application/json; charset=utf-8',
    data: JSON.stringify(data)
}).done(function (data) {
    self.result("Done!");
}).fail(showError);

HTTP isteği, burada $CREDENTIAL_PLACEHOLDER$ parola anahtar-değer çifti için bir yer tutucudur:

POST https://localhost:44305/api/Account/Register HTTP/1.1
Host: localhost:44305
User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64; rv:32.0) Gecko/20100101 Firefox/32.0
Accept: */*
Content-Type: application/json; charset=utf-8
X-Requested-With: XMLHttpRequest
Referer: https://localhost:44305/
Content-Length: 84

{"Email":"alice@example.com",$CREDENTIAL_PLACEHOLDER1$,$CREDENTIAL_PLACEHOLDER2$"}

HTTP yanıtı:

HTTP/1.1 200 OK
Server: Microsoft-IIS/8.0
Date: Wed, 01 Oct 2014 00:57:58 GMT
Content-Length: 0

Bu istek sınıfı tarafından AccountController işlenir. Üyelik veritabanını yönetmek için dahili olarak AccountController ASP.NET Identity kullanır.

Uygulamayı Visual Studio'dan yerel olarak çalıştırırsanız, kullanıcı hesapları LocalDB'de AspNetUsers tablosunda depolanır. Visual Studio'da tabloları görüntülemek için Görünüm menüsüne tıklayın, Sunucu Gezgini'ni seçin ve veri bağlantıları'nı genişletin.

Veri Bağlantılarının Görüntüsü

Erişim Belirteci Alma

Şimdiye kadar herhangi bir OAuth işlemi gerçekleştirmedik, ancak şimdi erişim belirteci istediğimizde OAuth yetkilendirme sunucusunu çalışırken göreceğiz. Örnek uygulamanın Oturum Aç alanına e-postayı ve parolayı girin ve Oturum Aç'a tıklayın.

Oturum açma bölümünün görüntüsü

Oturum Aç düğmesi belirteç uç noktasına bir istek gönderir. İsteğin gövdesi şu form-url ile kodlanmış verileri içerir:

  • grant_type: "parola"
  • kullanıcı adı: <kullanıcının e-posta adresi>
  • parola: <parola>

AJAX isteğini gönderen JavaScript kodu aşağıdadır:

var loginData = {
    grant_type: 'password',
    username: self.loginEmail(),
    password: self.loginPassword()
};

$.ajax({
    type: 'POST',
    url: '/Token',
    data: loginData
}).done(function (data) {
    self.user(data.userName);
    // Cache the access token in session storage.
    sessionStorage.setItem(tokenKey, data.access_token);
}).fail(showError);

İstek başarılı olursa yetkilendirme sunucusu yanıt gövdesinde bir erişim belirteci döndürür. Belirteci daha sonra API'ye istek gönderirken kullanmak üzere oturum depolama alanında depoladığımıza dikkat edin. Bazı kimlik doğrulama biçimlerinden (tanımlama bilgisi tabanlı kimlik doğrulaması gibi) farklı olarak tarayıcı, erişim belirtecini sonraki isteklere otomatik olarak eklemez. Uygulamanın bunu açıkça yapması gerekir. CsRF güvenlik açıklarını sınırladığı için bu iyi bir şeydir.

HTTP isteği:

POST https://localhost:44305/Token HTTP/1.1
Host: localhost:44305
User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64; rv:32.0) Gecko/20100101 Firefox/32.0
Accept: */*
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
X-Requested-With: XMLHttpRequest
Referer: https://localhost:44305/
Content-Length: 68

grant_type=password&username=alice%40example.com&password=Password1!

İsteğin kullanıcının kimlik bilgilerini içerdiğini görebilirsiniz. Aktarım katmanı güvenliği sağlamak için HTTPS kullanmalısınız.

HTTP yanıtı:

HTTP/1.1 200 OK
Content-Length: 669
Content-Type: application/json;charset=UTF-8
Server: Microsoft-IIS/8.0
Date: Wed, 01 Oct 2014 01:22:36 GMT

{
  "access_token":"imSXTs2OqSrGWzsFQhIXziFCO3rF...",
  "token_type":"bearer",
  "expires_in":1209599,
  "userName":"alice@example.com",
  ".issued":"Wed, 01 Oct 2014 01:22:33 GMT",
  ".expires":"Wed, 15 Oct 2014 01:22:33 GMT"
}

Okunabilirlik için JSON'un girintisini artırdım ve erişim belirtecini keserek oldukça uzun bir süre kullandım.

access_token, token_typeve expires_in özellikleri OAuth2 belirtimi tarafından tanımlanır. Diğer özellikler (userName, .issuedve .expires) yalnızca bilgilendirme amaçlıdır. Bu ek özellikleri ekleyen kodu yöntemindeki TokenEndpoint /Providers/ApplicationOAuthProvider.cs dosyasında bulabilirsiniz.

Kimliği Doğrulanmış İstek Gönderme

Taşıyıcı belirtecimiz olduğuna göre API'ye kimliği doğrulanmış bir istek gönderebiliriz. Bu, istekte Yetkilendirme üst bilgisi ayarlanarak yapılır. Bunu görmek için API'yi ara düğmesine yeniden tıklayın.

Arama sonrasındaki görüntü P I düğmesine tıklandı

HTTP isteği:

GET https://localhost:44305/api/values/1 HTTP/1.1
Host: localhost:44305
User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64; rv:32.0) Gecko/20100101 Firefox/32.0
Accept: */*
Authorization: Bearer imSXTs2OqSrGWzsFQhIXziFCO3rF...
X-Requested-With: XMLHttpRequest

HTTP yanıtı:

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Server: Microsoft-IIS/8.0
Date: Wed, 01 Oct 2014 01:41:29 GMT
Content-Length: 27

"Hello, alice@example.com."

Oturumu Kapat

Tarayıcı kimlik bilgilerini veya erişim belirtecini önbelleğe almadığından oturumu kapatma işlemi, belirteci oturum depolama alanından kaldırarak "unutma" meselesidir:

self.logout = function () {
    sessionStorage.removeItem(tokenKey)
}

Bireysel Hesaplar Proje Şablonunu Anlama

ASP.NET Web Uygulaması proje şablonunda Bireysel Hesaplar'ı seçtiğinizde proje şunları içerir:

  • OAuth2 yetkilendirme sunucusu.
  • Kullanıcı hesaplarını yönetmek için bir Web API uç noktası
  • Kullanıcı hesaplarını depolamak için bir EF modeli.

Bu özellikleri uygulayan ana uygulama sınıfları şunlardır:

  • AccountController. Kullanıcı hesaplarını yönetmek için bir Web API uç noktası sağlar. Eylem Register , bu öğreticide kullandığımız tek eylemdir. Sınıftaki diğer yöntemler parola sıfırlamayı, sosyal oturum açma bilgilerini ve diğer işlevleri destekler.
  • ApplicationUser, /Models/IdentityModels.cs içinde tanımlanır. Bu sınıf, üyelik veritabanındaki kullanıcı hesapları için EF modelidir.
  • ApplicationUserManager, /App_Start/IdentityConfig.cs içinde tanımlanır Bu sınıf UserManager'dan türetilir ve kullanıcı hesaplarında yeni kullanıcı oluşturma, parolaları doğrulama vb. işlemler gerçekleştirir ve veritabanındaki değişiklikleri otomatik olarak kalıcı hale getirir.
  • ApplicationOAuthProvider. Bu nesne OWIN ara yazılımına takılır ve ara yazılım tarafından tetiklenen olayları işler. OAuthAuthorizationServerProvider'dan türetilir.

Ana uygulama sınıflarının görüntüsü

Yetkilendirme Sunucusunu Yapılandırma

StartupAuth.cs dosyasında, aşağıdaki kod OAuth2 yetkilendirme sunucusunu yapılandırıyor.

PublicClientId = "self";
OAuthOptions = new OAuthAuthorizationServerOptions
{
    TokenEndpointPath = new PathString("/Token"),
    Provider = new ApplicationOAuthProvider(PublicClientId),
    AuthorizeEndpointPath = new PathString("/api/Account/ExternalLogin"),
    AccessTokenExpireTimeSpan = TimeSpan.FromDays(14),
    // Note: Remove the following line before you deploy to production:
    AllowInsecureHttp = true
};

// Enable the application to use bearer tokens to authenticate users
app.UseOAuthBearerTokens(OAuthOptions);

TokenEndpointPath özelliği, yetkilendirme sunucusu uç noktasının URL yoludur. Bu, uygulamanın taşıyıcı belirteçleri almak için kullandığı URL'dir.

Provider özelliği, OWIN ara yazılımına bağlanan ve ara yazılım tarafından tetiklenen olayları işleyen bir sağlayıcı belirtir.

Uygulama belirteç almak istediğinde temel akış aşağıdadır:

  1. Erişim belirteci almak için uygulama ~/Token'a bir istek gönderir.
  2. OAuth ara yazılımı sağlayıcıyı çağırır GrantResourceOwnerCredentials .
  3. Sağlayıcı kimlik bilgilerini doğrulamak ve bir talep kimliği oluşturmak için öğesini çağırır ApplicationUserManager .
  4. Bu başarılı olursa sağlayıcı, belirteci oluşturmak için kullanılan bir kimlik doğrulama bileti oluşturur.

Yetkilendirme akışı diyagramı

OAuth ara yazılımı kullanıcı hesapları hakkında hiçbir şey bilmez. Sağlayıcı ara yazılım ile ASP.NET Kimliği arasında iletişim kurar. Yetkilendirme sunucusunu uygulama hakkında daha fazla bilgi için bkz. OWIN OAuth 2.0 Yetkilendirme Sunucusu.

Web API'sini Taşıyıcı Belirteçleri kullanacak şekilde yapılandırma

yönteminde WebApiConfig.Register , aşağıdaki kod Web API işlem hattı için kimlik doğrulamasını ayarlar:

config.SuppressDefaultHostAuthentication();
config.Filters.Add(new HostAuthenticationFilter(OAuthDefaults.AuthenticationType));

HostAuthenticationFilter sınıfı, taşıyıcı belirteçleri kullanarak kimlik doğrulamasını etkinleştirir.

SuppressDefaultHostAuthentication yöntemi, Web API'sine istek Web API'si işlem hattına ulaşmadan önce IIS veya OWIN ara yazılımı tarafından gerçekleşen kimlik doğrulamasını yoksayması gerektiğini bildirir. Bu şekilde Web API'sini yalnızca taşıyıcı belirteçleri kullanarak kimlik doğrulamasıyla kısıtlayabiliriz.

Not

Özellikle, uygulamanızın MVC bölümü kimlik bilgilerini tanımlama bilgisinde depolayan Forms kimlik doğrulamasını kullanabilir. Tanımlama bilgisi tabanlı kimlik doğrulaması, CSRF saldırılarını önlemek için sahteciliğe karşı koruma belirteçleri kullanılmasını gerektirir. Web API'sinin istemciye sahtecilik önleme belirtecini göndermesinin uygun bir yolu olmadığından, bu web API'leri için bir sorundur. (Bu sorunla ilgili daha fazla arka plan bilgisi için bkz. Web API'sinde CSRF Saldırılarını Önleme.) SuppressDefaultHostAuthentication çağrısı, Web API'lerinin tanımlama bilgilerinde depolanan kimlik bilgilerinden gelen CSRF saldırılarına karşı savunmasız olmamasını sağlar.

İstemci korumalı bir kaynak istediğinde, Web API işlem hattında şunlar gerçekleşir:

  1. HostAuthentication filtresi, belirteci doğrulamak için OAuth ara yazılımını çağırır.
  2. Ara yazılım belirteci talep kimliğine dönüştürür.
  3. Bu noktada isteğin kimliği doğrulanır ancak yetkilendirilmemiş olur.
  4. Yetkilendirme filtresi talep kimliğini inceler. Talepler kullanıcıyı bu kaynak için yetkilendiriyorsa, istek yetkilendirilmiştir. Varsayılan olarak, [Authorize] özniteliği kimliği doğrulanmış tüm istekleri yetkilendir. Ancak rol veya diğer taleplere göre yetkilendirme yapabilirsiniz. Daha fazla bilgi için bkz. Web API'sinde Kimlik Doğrulaması ve Yetkilendirme.
  5. Önceki adımlar başarılı olursa denetleyici korumalı kaynağı döndürür. Aksi takdirde, istemci bir 401 (Yetkisiz) hatası alır.

İstemcinin korumalı bir kaynak istediği zaman diyagramı

Ek Kaynaklar