Artykuł opublikowany na Codeguru.pl
Z zabezpieczeniami aplikacji webowych spotykamy się prawie przy każdej okazji, gdy logujemy się do dowolnego serwisu internetowego. Dzięki temu dostajemy możliwość dostępu do różnych zasobów aplikacji WWW, które niekoniecznie są dostępne dla wszystkich użytkowników usług webowych. Wydawałoby się, że uzyskanie dostępu do zabezpieczonych części aplikacji ASP.NET wymaga autoryzacji przy każdej próbie połączenia z chronioną stroną. Na szczęście prosty mechanizm ciasteczek (ang. cookies) zwalnia nas od tej uciążliwości. Ciasteczka stanowią podstawę zabezpieczania stron ASP.NET przy użyciu mechanizmów bezpieczeństwa Forms. Są to specjalne formy HTML, które zawierają dane użytkownika (zazwyczaj nazwę użytkownika i hasło), i z których są podawane dane w celu autoryzacji zabezpieczonych zasobów aplikacji.
Zasada działania zabezpieczeń Forms
Pierwszą czynnością, jaką musimy wykonać by wykorzystać mechanizmy Forms jest ustawienie zabezpieczeń w serwerze Internetowych Usług Informacyjnych IIS (ang. Internet Information Services) na dostęp anonimowy. Dzięki temu zapytanie przechodzi przez IIS i jest bezpośrednio kierowane do ASP.NET. Tam jest rozpoznawane czy do zapytania dołączone jest ciasteczko. Jeśli ciasteczka nie ma to następuje przekierowanie do strony, na której można się zalogować. Po wpisaniu swoich danych, (zwyczajowo jest to login i hasło), i zatwierdzeniu danych następuje sprawdzenie w bazie danych prawdziwości podanych informacji. Jeśli autoryzacja wypada na naszą korzyść uzyskujemy dostęp do chronionych zasobów oraz tworzone jest ciasteczko, które mając w sobie zaszyfrowane dane, przesyłane jest do przeglądarki zalogowanego użytkownika. W przypadku, gdy nasze dane nie zostały autoryzowane pomyślnie, uzyskujemy informację o braku dostępów do zasobów i mamy możliwość ponownej próby zalogowania się do systemu. Została jeszcze jedna możliwość, a mianowicie, gdy jesteśmy już zalogowani i mamy ciasteczko. Ciasteczko jest dołączane do naszego zapytania i najpierw następuje sprawdzenie czy do zapytanie jest dołączone ciasteczko. Potem jest ono autoryzowane by następnie zostało nam przyznane zezwolenie do uzyskania zasobów i użytkownik może zobaczyć chronioną stronę. Poniżej (rys. 1), za pomocą diagramu aktywności została przedstawiona zasada korzystania z ciasteczek.
Rys. 1 Zasada działania mechanizmu bezpieczeństwa Forms – diagram aktywności
Przykład praktyczny
Poznaliśmy już teoretycznie jak, funkcjonuje mechanizm zabezpieczania aplikacji ASP.NET w oparciu o Forms. Do zobrazowania praktycznego wykorzystania tego typu zabezpieczeń rozszerzona zostanie prosta aplikacja, która przedstawiłem w artykule „Trójwarstwowa struktura aplikacji ASP.NET z wykorzystaniem procedur wbudowanych”. Zakres jej funkcjonalności został przedstawiony na diagramie przypadków użycia (rys.2).
W naszym systemie istnieją trzy typu użytkowników: Użytkownicy, Pracownicy i Administratorzy. Użytkownicy mają możliwość tylko zobaczenia danych o studentach. Pracownicy jak i Administratorzy po zalogowaniu mogą edytować dane studentów. Najwięcej uprawnień mają Administratorzy, którzy nie tylko mogą zastępować pozostałych aktorów, ale także mogą administrować systemem. Administrowanie systemem polega na dodawaniu nowych użytkowników i ich kasowaniu ich oraz daje możliwość przypisania użytkownikom funkcji.
Rys. 2 Funkcjonalność aplikacji – diagram przypadków użycia
W przedstawionym przykładzie zdefiniowane są dwie role: pierwsza to Pracownik odpowiadające aktorowi „Pracownik” a druga to Admin odpowiadająca aktorowi Administrator. Użytkownicy nie mają przypisanej żadnej roli gdyż obejrzenie pierwszej strony nie wymaga logowania.
Zaprezentowane rozwiązanie jest skonstruowane w oparciu o trójwarstwową architekturę aplikacji webowych wykorzystujących ASP.NET. Poniżej (rys.3), przedstawiono rozmieszczenie klas biorących udział w zabezpieczaniu aplikacji ASP.NET.
Rys.3 Trójwarstwowa struktura rozmieszczenia klas w mechanizmie bezpieczeństwa Forms – diagram klas (Three-Tier Diagram)
Klasy te mają nadane stereotypy w postaci odpowiednich ikon. I tak w warstwie prezentacji znajdują się klasy odpowiedzialne za komunikację użytkownika z systemem. Znajdują się tu klasy specyfikujące strony aspx oraz ascx i mają one stereotyp klas interfejsu (ang. Boundary Class). W warstwie usług znajdują się klasy ze stereotypem klasy sterującej (ang. Control Class), które sterują aplikacją i odpowiadają za jej działanie. Klasy te stanowią most pomiędzy klasami znajdującymi się w pozostałych warstwach. W warstwie danych natomiast znajdują się klasy ze stereotypem klas danych (ang. Entity Class) i reprezentują one tabele w bazie danych.
Korzystając z klas zamieszczonych na rysunku 3 możemy przedstawić scenariusz obrazujący proces logowania.
Rys. 4 Logowanie do aplikacji ASP.NET – diagram sekwencji
Implementacja zabezpieczeń
Pierwszą czynnością, jaką należy wykonać by uruchomić zabezpieczenia aplikacji w oparciu o mechanizmy Forms jest wpisanie do pliku Web.config następujących linijek kodu:
<authentication mode="Forms">
<!– ustawienie nazwy ciasteczka na "cookies",
ustawienie ochrony na wszystkie strony,
ustawienie czasu ważności ciasteczka na 60 min ->
<forms name="cookies" protection="All" timeout="60" />
</authentication>
Gdy zostały uaktywnione zabezpieczenia aplikacji należy zaimplementować klasę, która stworzy nam ciasteczko i będzie czuwać nad ochroną każdej strony. Taką funkcjonalność realizuje zdarzenie Application_AuthenticateRequest znajdujące się w klasie Global. Każde zdarzenie w systemie powoduje zbadanie czy jest autoryzacja. Ma to miejsce w linijce
if (Request.IsAuthenticated == true)
Gdy jest autoryzacja następuje sprawdzenie czy istnieje ciasteczko.
if ((Request.Cookies["studentcookies"] == null)||(Request.Cookies["studentcookies"].Value == ""))
Jeśli ciasteczko istnieje to:
{
// Pobranie roli z ciasteczka
FormsAuthenticationTicket ticket = FormsAuthentication.Decrypt(Context.Request.Cookies["studentcookies"].Value);
//konwersja roli z danych na liste uporzadkowaną
ArrayList userRoles = new ArrayList();
foreach (String role in ticket.UserData.Split( new char[] {´;´} ))
{
userRoles.Add(role);
}
roles = (String[]) userRoles.ToArray(typeof(String));
}
jeśli natomiast ciasteczko nie istnieje, jest tworzone, szyfrowane algorytmem MD5 i wysyłane do przeglądarki użytkownika.
String[] roles;
// Tworzenie cookies jeśli nie jest stworzone jeszcze w tej sesji
if ((Request.Cookies["studentcookies"] == null) || (Request.Cookies["studentcookies"].Value == ""))
{
// Pobranie ról z tabeli użytkownicy i dodanie do ciasteczka
UzytkownikCTR user = new UzytkownikCTR();
roles = user.WezRole(User.Identity.Name);
// stworzenie string, a w celu utwardzenia ciasteczka
String roleStr = "";
foreach (String role in roles)
{
roleStr += role;
roleStr += ";";
}
// Tworzenie ciasteczka
FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(
1, // wersja
Context.User.Identity.Name, // nazwa użytkownika
DateTime.Now, // czas stworzenia
DateTime.Now.AddHours(1), // czas wygaśnięcia
false, // nie utworzenie ciasteczka
roleStr); // funkcja(rola) w systemie);
// Zaszyfrowanie ciasteczka
String cookieStr = FormsAuthentication.Encrypt(ticket);
// Wysłanie ciasteczka do przeglądarki klienta
Response.Cookies["studentcookies"].Value = cookieStr;
Response.Cookies["studentcookies"].Path = "/";
Response.Cookies["studentcookies"].Expires = DateTime.Now.AddMinutes(1);
Kolejnym etapem jest stworzenie formularza, który umożliwi zalogowanie się do systemu.
Formularz taki jest stworzony w postaci pliku Logowanie.ascx i razem z plikiem Studenci.ascx są przypisane do głównej strony Default.aspx. Formularz logowania jest zaimplementowany w prawy panel i pokazuje się wtedy, gdy istnieje konieczność zalogowania – autoryzacja zapytania kończy się niepowodzeniem.
// Sprawdzenie czy jest autoryzacja
if (Request.IsAuthenticated == false)
{
//Jeśli nie ma autoryzacji wyświetlany jest po prawej stronie formularz do logowania
PrawyPanel.Controls.Add(Page.LoadControl("~/ochrona/Logowanie.ascx"));
PrawyPanel.Visible = true;
}
Formularz Logowanie.ascx składa się z dwóch kontrolek typu TextBox i przycisku LogujBtn. Naciśnięcie przycisku powoduje wywołanie metody, która sprawdza poprawność wpisanych danych
// Obsługa przycisku Loguj
private void LoginBtn_Click(Object sender, ImageClickEventArgs e) {
// Próba sprawdzenia poprawności danych
UzytkownikCTR konto = new UzytkownikCTR();
String uzytkownikId = konto.Logowanie(login.Text, StudentOchrona.Szyfr(haslo.Text));// kodowanie hasła
if ((uzytkownikId != null) && (uzytkownikId != ""))
{
// Zapisanie nazwy użytkownika w postaci loginu do ciasteczka znajdującego sie po stronie klienta
FormsAuthentication.SetAuthCookie(login.Text, RememberCheckbox.Checked)
// Powrót do strony poczatkowej z przed logowania
Response.Redirect(Request.ApplicationPath);
}
else {
Info.Text = "<" + "br" + ">Logowanie nie powiodło się!" + "<" + "br" + ">";
}
}
Gdy zalogujemy się do systemu, znika nam formularz logowania a na banerze (rys. 5), który jest dołączony do każdej strony, pojawia się w prawym górnym rogu komunikat witający zalogowanego użytkownika oraz przycisk Wyloguj, dzięki któremu możemy się wylogować.
Rys. 5 Baner aplikacji „Ewidencja Studentów”
Naciśnięcie tego przycisku uruchamia stronę Wyloguj.aspx, która zawiera kod programu kasujący informacje zawarte w ciasteczku.
public class Wyloguj : System.Web.UI.Page
{
private void Page_Load(object sender, System.EventArgs e) {
// Wylogowanie z systemu autoryzacji
FormsAuthentication.SignOut()
// ustawienie wartości ciasteczka na puste lub nieaktualne
Response.Cookies["studentcookies"].Value = null;
Response.Cookies["studentcookies"].Expires = new System.DateTime(1999, 10, 12);
Response.Cookies["studentcookies"].Path = "/";
// Powrót do strony początkowej
Response.Redirect("~/Default.aspx");
}
Ostatnim elementem interfejsu użytkownika, zajmującego się bezpieczeństwem, jest strona BrakUpawnien.aspx. Zawarty jest w niej komunikat informujący użytkownika, o braku uprawnień do uzyskania dostępu do zasobów oraz link do strony głównej gdzie znajduje się formularz logowania. Strona BrakUprawnien.aspx uruchomiona zostanie wtedy, gdy strona do której się próbuje dostać użytkownik jest zabezpieczona i próba autoryzacji przejdzie niepomyślnie. W przedstawionym przykładzie strony są zabezpieczone przed dostępem w dwojaki sposób. Możliwość edycji danych studentów jest chroniona za pomocą poniższego kodu:
//Sprawdzenie autoryzacji
string funkcje=" ";
if (Request.IsAuthenticated == true)
{
// Pobranie roli użytkownika
UzytkownikCTR osoba = new UzytkownikCTR();
SqlDataReader dr = osoba.WezFunkcje(Context.User.Identity.Name);
dr.Read();
funkcje= (String) dr["NazwaFunkcji"];
dr.Close();
}
// sprawdza czy przypisane są funkcje
if ((funkcje=="Pracownik") || (funkcje == "Admins"))
{
// Gdy przypisane są role pracownika lub administratora to wyświetlony
// jest link, który daje możliwość dodania nowego użytkownika
NowyStudent.Visible=true;
}
Dostęp do zasobów administratora jest chroniony w inny sposób:
// Sprawdzenie czy obecnie zalogowany użytkownik ma uprawnienia do otworzenia strony
if (StudentOchrona.SprawdzenieFunkcji("Admins") == false)
{
//Przekierowanie na stronę informujacą o braku uprawnień Response.Redirect("~/ochrona/BrakUprawnien.aspx");
}
Administrator
W przedstawionym rozwiązaniu, gdy jesteśmy zalogowani jako administrator mamy wszystkie uprawnienia do edycji danych o studentach oraz możliwość dodania i kasowania użytkowników z możliwością przypisania im ról. Aby dodać lub skasować użytkownika należy po zalogowaniu jako administrator, kliknąć na przycisk Administrator znajdujący się w lewym dolnym rogu banera. Przycisk ten jest dostępny tylko, gdy zalogowany użytkownik ma przypisaną rolę Admins. Gdy wejdziemy w zasoby administratora pokaże się nam prosty formularz pozwalający na zarządzanie użytkownikami:
Rys. 6 Zarządzanie użytkownikami
Po wybraniu użytkownika możemy zmienić hasło lub dodać/zabrać uprawnienia.
Edycja danych
Stosowanie zabezpieczeń Forms daje jeszcze jedną ważną funkcjonalność a mianowicie można zapisać w bazie danych, kto dokonał modyfikacji zawartości komórek. W celu wykorzystania tej zalety należy w tabeli z danymi dodać kolumnę, w której będą przechowywane te dane a następnie dodać parametr w metodzie klasy sterującej odpowiedzialny za przekazanie danych do bazy danych. Poniżej znajduje się wycinek klasy sterującej StudentCTR z fragmentem metody poprawiającej dane studenta:
//Metoda PoprawStudenta jest odpowiedzialna za poprawianie danych studenta
public void PoprawStudenta (int NrAlbumu, String Nazwisko, String Imie, String Adres, String Wydzial, String Zmodyfikowal)
{
// tworzenie egzeplarza dla połączenia z bazą danych
// parametryPolaczenia sa pobierane z pliku Web.config
SqlConnection conn= new SqlConnection(ConfigurationSettings.AppSettings["parametryPolaczenia"]);
//Definioweanie polecenia z bazą danych
SqlCommand comm = new SqlCommand("Popraw_Studenta", conn);
// Zdefiniowanie polecenia jako Stored Procedure
comm.CommandType = CommandType.StoredProcedure;
// Dodawanie parametrów do Stored Procedure
SqlParameter parameternrAlbumu = new SqlParameter("@nrAlbumu", SqlDbType.Int, 4);
parameternrAlbumu.Value = NrAlbumu;
comm.Parameters.Add(parameternrAlbumu);
(…)
SqlParameter parameterzmodyfikowal= new SqlParameter("@zmodyfikowal", SqlDbType.NVarChar, 50);
parameterzmodyfikowal.Value = Zmodyfikowal;
comm.Parameters.Add(parameterzmodyfikowal);
// Otworzenie połączenia z bazą danych
conn.Open();
// Wykonanie polecenia "Popraw_Studenta"
comm.ExecuteNonQuery();
//Zamknięcie połączenia z bazą danych
conn.Close();
}
Dodatkowo, podczas zapisywania danych, należy odwołać się do nazwy użytkownika zawartej w ciasteczku. Odwołanie to uzyskujemy po przez Context.User.Identity.Name a cała linia odpowiedzialna za poprawienie danych studentów wygląda tak:
// Poprawienie danych w tabeli Student
dane.PoprawStudenta( NrAlbumu,NazwiskoPole.Text, ImiePole.Text, AdresPole.Text,WydzialPole.Text,Context.User.Identity.Name );
Podsumowanie
Przedstawione rozwiązanie pokazuje tylko czubek góry lodowej, jaką jest zabezpieczanie aplikacji ASP.NET przy wykorzystaniu mechanizmów bezpieczeństwa Forms. Wykorzystanie ciasteczek daje możliwość personalizacji naszych stron, ciasteczka sprawdzają się w aplikacjach WWW używających ASP.NET. Zabezpieczenia Forms, poprzez uwierzytelnianie umożliwiają zarządzanie dostępem do poszczególnych zasobów systemu różnym typom użytkowników. Wykorzystanie ciasteczek jest proste w implementacji i pozwala na zastosowanie niezależnie od platformy systemu operacyjnego, co daje przewagę zwłaszcza przy zabezpieczaniu aplikacji internetowych.
Kod źródłowy aplikacji i backup bazy danych.
Wykaz literatury
McManus J.P., Kinsman C., „C# Developer´s Guide to ASP.NET, XML, and ADO.NET”, Addison Wesley, 2002
Microsoft Corporation „Tworzenie Bezpiecznych Aplikacji Microsoft ASP.NET”, Microsoft Press, 2002
Dodawanie komentarzy zostało zablokowane.