Pokazywanie postów oznaczonych etykietą Web API. Pokaż wszystkie posty
Pokazywanie postów oznaczonych etykietą Web API. Pokaż wszystkie posty

poniedziałek, 19 maja 2014

Prosty klient do konsumowania serwisu ASP.NET Web Api

Załóżmy, że napisaliśmy serwis, który zwraca nam pewne produkty. Nawigując pod odpowiednie dwa adresy URI pobierzemy odpowiednie zasoby:

  • http://localhost/api/products - lista wszystkich produktów
  • http://localhost/api/product/1 - odpowiedni produkt
Wchodzą pod pierwszy adres dostaniemy listę produktów, np. w takiej postaci:



Wiedząc co zwraca nasz serwis możemy wygenerować sobie klasę "Product", z której będziemy mogli później korzystać:
public class Product
{
   public int Id { get; set; }
   public string Name { get; set; }
   public string Description { get; set; }
   public decimal Price { get; set; }
}
W celu połączenia się z naszym Web API, musimy skorzystać z klasy HttpClient. Nasz cały kod może wyglądać następująco:
Metoda, dzięki której tworzymy sobie połączenie do naszego serwisu REST:
private HttpClient GetClient(string mediaType)
{
   string baseUri = @baseAddress + port;
   HttpClientHandler handler = new HttpClientHandler()
   {
      UseDefaultCredentials = false
   };
   handler.Credentials = new NetworkCredential("username", "password");
   HttpClient httpClient = new HttpClient(handler);
   httpClient.BaseAddress = new Uri(baseUri);
   httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue(mediaType));
   return httpClient;
}
Metoda dzięki której pobieramy produkt:
private async void Get()
{
   using (var client = GetClient("application/json"))
   {
      HttpResponseMessage response = await client.GetAsync("/api/Products/1");
      if (response.IsSuccessStatusCode)
      {
         Product product = await response.Content.ReadAsAsync();
         //Do something with product
      }
   }
}
Metoda za pomocą której dodajemy nasz produkt:
private async void Post()
{
   using(var client = GetClient("application/json"))
   {
      var message = await client.PosAsJsonAsync("api/products",
      new Product
      { 
         Id=111,
         Name="Product 111 name",
         Description = "Product 111 description",
         Price=222,22
      });
   }
}
Metoda za pomocą której usuwamy nasz produkt:
private void Delete()
{
   using(var client = GetClient("application/json"))
   {
      var message = await Client.DeleteAsync("api/products/1");
   }
}

Należy pamiętać, że metoda "ReadAsAsync" jest metodą rozszerzającą i aby z niej skorzystać należy pobrać następującą bibliotekę z nuget'a: Microsoft.AspNet.WebApi.Client

ASP.NET Web API - basic authentication

Basic authentication (ASP.NET Web API)

Tworzenie serwisu REST z wykorzystaniem ASP.NET Web API jest stosunkowo proste. Gdy nasz serwis jest już wystawiony na świat, prawdopodobnym stanie się fakt odpowiedniego jego zabezpieczenia. Jednym z najprostszych i najbardziej standardowym sposobem jest zabezpieczenie takiego serwisu z wykorzystaniem basic authentication. 
Basic authentication jest najprostszym i najłatwiejszym mechanizmem autentykacji poprzez zastosowanie pary "username/password" w "plaintext".
Basic authentication działa w następujący sposób:

  1. Jeżeli żądanie "request" wymaga autentykacji, serwer zwraca status code "401" (unauthorized). Odpowiedź taka zawiere "WWW-uthenticate header", które mówi, że serwer wspiera "basic authentication".
  2. Klient wysyła kolejne żądanie ("request") ale tym razem już z odpowiednimi "credentials" umieszczonymi w "WWW-Authenticate header". Charakteryzują je następujące właściwości: 
    1. sformatowane jako string: "name:password"
    2. base64-encoded
    3. brak szyfrowania

Basic authentication wykonywany jest w kontekście pewnej domeny ("realm"). Serwer dodaje nazwę tej domeny do nagłówka WWW-Authenticate header. 
WAŻNE:
  • Ponieważ "credentials", które przesyłane są od klienta na serwer nie są szyfrowane, stosowanie basic authentication jest sensowne i bezpieczne jedynie jeśli komunikacja jest bezpieczna i przebiega przez szyfrowaną wersję protokołu HTTP czyli HTTPS. 
  • Basic authentication nie jest odporne na ataki CSRF (cross-site request forgery)

Niniejszy post przedstawia jak zaimplementować taką autentykację w naszym ASP.NET Web API (post nie dotyczy ASP.NET Web API 2, które jest prostsze i zawiera już zaimplementowane techniki autentykacji serwisu bez konieczności pisania dodatkowego kodu).

AuthorizeAttribute 

Implementacja basic authentication będzie polegała na stworzeniu własnego atrybutu do autoryzacji. W pierwszej kolejności jednak musimy napisać metodę pomocniczą, która wyłuska nam credentials (nazwę użytkownika i hasło) z wiadomości, która przychodzi do serwera. Metoda taka może wyglądać następująco:

private Credentials ParseAuthorizationHeader(string authHeader)
{
   string[] credentials = Encoding.ASCII.GetString(Convert.FromBase64String(authHeader)).Split(new[] { ':' });
   if (credentials.Length != 2 || string.IsNullOrEmpty(credentials[0]) || string.IsNullOrEmpty(credentials[1]))
      return null;
   return new Credentials() { Username = credentials[0], Password = credentials[1], };
}
Teraz wystarczy napisać naszą klasę, która będzie dziedziczyła po "AuthorizeAttribute":
public class CustomAuthorizeAttribute : AuthorizeAttribute
{
   private const string BasicAuthResponseHeader = "WWW-Authenticate";
   private const string BasicAuthResponseHeaderValue = "Basic";

   protected IPrincipal CurrentUser
   {
      get { return Thread.CurrentPrincipal as IPrincipal; }
      set { Thread.CurrentPrincipal = value as IPrincipal; }
   }

   public override void OnAuthorization(System.Web.Http.Controllers.HttpActionContext actionContext)
   {
      try
      {
         AuthenticationHeaderValue authValue = actionContext.Request.Headers.Authorization;
         Credentials parsedCredentials = ParseAuthorizationHeader(authValue.Parameter);
         if (parsedCredentials != null)
         {
            this.CurrentUser = new DummyPrincipalProvider().CreatePrincipal(parsedCredentials.Username, parsedCredentials.Password);
            if (!CurrentUser.Identity.IsAuthenticated)
            {
               actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.Forbidden);
               actionContext.Response.Headers.Add(BasicAuthResponseHeader, BasicAuthResponseHeaderValue);
               return;
            }
         }
      }
      catch (Exception ex)
      {
         actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.Unauthorized);
         actionContext.Response.Headers.Add(BasicAuthResponseHeader, BasicAuthResponseHeaderValue);
         return;
      }
  }
}
W powyższej implementacji korzystam z klasy "DummyPrincipalProvider", która jest dziedziczy z IPrincipalProvider i jest moją "fake'ową" implemetacją dostarczyciela tożsamości. Wygląda następująco:
public class DummyPrincipalProvider : IProvidePrincipal
{
   private const string Username = "username";
   private const string Password = "password";

   public IPrincipal CreatePrincipal(string username, string password)
   {
      if (username != Username || password != Password)
      {
         return null;
      }
      var identity = new GenericIdentity(Username);
      IPrincipal principal = new GenericPrincipal(identity, new[] { "User" });
      return principal;
    }
 }
W tym miejscu jednak powinna być implemetacja odpowiedniego dortarczyciela tożsamości, np. jeśli dane użytkowników przechowywane są bazie to wtedy najlepszym rozwiązaniem będzie użycie gołego ADO.NET lub z wykorzystaniem EntityFramework do pobrania takich danych z bazy danych. Mając tak napisany atrybut, wystarczy teraz, że klasę lub metodę, którą chcemy zabezpieczyć udekorujemy tym atrybutem:
[CustomAuthorizeAttribute]
[HttpGet, HttpHead]
public Product Get(int id)
{
   var product = productRepository.Get(id);
   if (product == null)
      throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.NotFound));
   return product;
}