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:
- 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".
- 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:
- sformatowane jako string: "name:password"
- base64-encoded
- 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; }
Brak komentarzy:
Prześlij komentarz