Dziś krótki wpis o mojej własnej implementacji Membership Providera, która wnosi trochę usprawnień związanych z bezpieczeństwem, wydajnością oraz możliwością wykorzystania bazy MySQL. Dość istotnie jednak skupiłem się na ograniczeniu niepotrzebnych funkcjonalności, których nie używa się w standardowych projektach webowych (co można zauważyć porównując ilość tabel bazodanowych oryginalnej implementacji i mojej wersji). Zdaję sobie sprawę, że istnieje obecnie kilka implementacji do bazy MySQL, a nawet jest możliwość użycia standardowego SQLMembershipProvidera do automatycznego wygenerowania tabel w bazie MySQL i korzystania z nich; jednak są to implementacje nieco niedostosowane do specyfiki bazy innej od SQL Servera. Inna sprawa, że mało kto lubi posługiwać się w aplikacji kluczami głównymi w formie GUIDów (nie dość, że 128 bitowe, to jeszcze trzeba je ręcznie generować).

Najpierw jednak krótkie porównanie samych schematów: po lewo wersja domyślna ze standardowego SQLMembershipProvidera; po prawo – moja wersja.

mp2 mp1

Jak widać, nastąpiło sporo uproszczeń, które można sprowadzić do następującej listy:

  • wyrzucenie Application – w większości przypadków nie będziemy potrzebować rozdzielać zarządzania użytkownikami na wiele aplikacji. Ja osobiście nie oddzielam tej logiki zbytnio od logiki samego projektu, oraz nie trzymam danych użytkowników w bazie zewnętrznej. Poza uproszczeniem zwiększa to także wydajność przez krótsze zapytania z mniejszą ilością złączeń tabel.
  • Wyrzucenie SchemaVersions, które w przypadku własnej wersji Membership Providera nie było specjalnie potrzebne
  • usunięcie niektórych kolumn, które uznałem za nieprzydatne (np. wyrzucone podpowiedzi, których już się raczej dziś nie używa (a które mogą być potencjalną luką bezpieczeństwa)
  • w obecnej wersji nie są używane procedury składowane. Standardowa implementacja MP to duży zestaw procedur; w MySQL i przy na tyle uproszczonych tabelach nie ma to większego znaczenia, a znacznie ułatwi wprowadzanie własnych modyfikacji w kodzie.
  • Jedyna możliwa forma przechowywania hasła w bazie to hashed z solą. Stosowanie każdej innej opcji to poważne uchybienie pod względem bezpieczeństwa całej aplikacji.

Przykładowa konfiguracja web.config jak widać poniżej nie różni się dużo od standardowej. Warto zauważyć klucz SecretHashKey w appSettings, który jest niezbędny do pracy providera. Jest to wynik zastąpienia MachineKey jako generatora soli przy hashowaniu haseł pewną unikalną, sekretną frazą, którą można podać w konfiguracji. Jest to o tyle przydatne, że można zachować jednolitość danych pomiędzy środowiskiem deweloperskim, a testowym. Ponadto, w kolejnej wersji najprawdopodobniej każdy użytkownik będzie miał własną sól dodawaną do hasła, co zwiększy nieco bezpieczeństwo.

<appSettings>  
    <add key="SecretHashKey" value="enter something and do not change"/>
</appSettings>  
<system.web>  
    <membership defaultProvider="PureMembershipProvider" userIsOnlineTimeWindow="15">
      <providers>
        <clear/>
        <add name="PureMembershipProvider" connectionStringName="defaultConnectionString" 
             type="PureDev.Common.PureMembershipProvider, PureMembershipProvider" enablePasswordRetrieval="false" 
             enablePasswordReset="true" requiresQuestionAndAnswer="false" requiresUniqueEmail="true" passwordFormat="Hashed" 
             maxInvalidPasswordAttempts="5" minRequiredPasswordLength="6" minRequiredNonalphanumericCharacters="0" 
             passwordAttemptWindow="10" passwordStrengthRegularExpression="" />
      </providers>
    </membership>
    <roleManager enabled="true" defaultProvider="PureRoleProvider">
      <providers>
        <clear/>
        <add name="PureRoleProvider" connectionStringName="defaultConnectionString" 
             type="PureDev.Common.PureRoleProvider, PureMembershipProvider"/>
      </providers>
    </roleManager>
  </system.web>

Jeszcze kilka słów o wdrożeniu – w plikach do ściągnięcia jest dostępny plik db_script.sql do wygenerowania niezbędnych tabel dla MySQL Membership Providera (projekt, nawiasem mówiąc, nosi właściwą nazwę PureMembershipProvider). Ponadto jest też projekt testowy, który warto uruchomić z pomocą NUnita przed wdrożeniem, by przekonać się, czy wszystko dobrze działa. Część testów bada też wydajność, więc można też zobaczyć ile czasu zajmuje np. 1000 prób zalogowania się, itd.

Linki do ściągnięcia i do strony na GitHubie:

Download MySQLMembershipProvider   Strona projektu na GitHub