bancuri, glume, imagini, video, fun, bancuri online, bancuri tari, imagini haioase, videoclipuri haioase, distractie online Pe HaiSaRadem.ro vei gasi bancuri, glume, imagini, video, fun, bancuri online, bancuri tari, imagini haioase, videoclipuri haioase, distractie online. Nu ne crede pe cuvant, intra pe HaiSaRadem.ro ca sa te convingi.

Aplicatii Web cu C# si ASP.NET - Lectia 4

Securitate

Exista mai multe moduri de a securiza o aplicatie ASP.NET. In general, se poate face acest lucru atat din IIS Management Console (click dreapta pe un director virtual din IIS, Properties, Directory Security, Edit) – unde se pot alege mai multe tipuri de autentificare {Anonymous, Windows Integrated, Digest for Windows domain servers, Basic}, cat si la nivel de aplicatie ASP.NET. Pentru asta se foloseste fisierul de configurare web.config, dupa cum se vede mai jos. Forms poate fi inlocuit cu Windows sau Passport.

<authentication mode="Forms">

In primul rand, trebuie totusi sa vorbim de aplicatii publice care nu necesita nici un fel de autentificare. Trebuie sa fiti atenti, acesta este modul default in care este creata o noua aplicatie ASP.NET. Adica, atunci cand creati un proiect ASP.NET cu Visual Studio, se creaza o aplicatie web accesibila anonim.

Exista 2 moduri in care server-ul IIS asigneaza un user (un cont Windows) unui utilizator care ruleaza o aplicatie web ASP.NET, iar aceste moduri se pot configura din web.config:

Noi vom folosi in continuare modul "non-impersonate" in care aplicatia va rula sub un cont fix <nume_server>\ASPNET.

Pentru o aplicatie securizata, avem mai multe posibilitati de autentificare, cele mai des intalnite fiind sintetizate in tabelul care urmeaza. Implementarea politicii de securitate se poate face atat din IIS (la nivel de conturi Windows), cat si din aplicatia ASP.NET (la un nivel logic, conturi virtuale).

Tipul aplicatiei

Modul de autentificare

Descriere

Aplicatie web publica pe Internet. Anonim Nu avem nevoie de securizare.
Aplicatie web pentru Intranet. Windows Integrated Acest mod autentifca utilizatorii folosind lista de useri de pe server (Domain Controller). Drepturile userilor in aplicatia web este dat de nivelul de privilegii al contului respectiv.
Aplicatie web disponibila pe Internet, dar cu acces privat. Windows Integrated Utilizatorii companiei pot accesa aplicatia din afara Intranetului, folosind conturi din lista serverului (Domain Controller).
Aplicatii web comerciale. Forms Authentication Aplicatii care au nevoie de informatii confidentiale si eventual in care sunt mai multe tipuri de utilizatori.

Am sa insist pe modul Forms Authentication, intrucat acesta se va folosi si la aplicatia la care lucram impreuna. Acest mod se foloseste atunci cand utilizatorii sunt stocati intr-o baza de date, asa cum este si cazul aplicatiei noastre. Mai mult, permite sa avem mai multe tipuri de utilizatori, fiecare putand sa acceseze doar anumite sectiuni ale sitului.

Iata cum trebuie sa arate web.config-ul unei aplicatii pentru a utiliza autentificare Forms. In sectiunea <configuration><system.web>, trebuie sa identificati tag-ul <authentication> si sa il modificati la: 

<authentication mode="Forms">
      <forms name=".Documents" protection="None" loginUrl="login.aspx">
      </forms>
</authentication>

Pentru a implementa acest tip de autentificare, se folosesc cookie-uri, astfel: utilizatorul ajunge pe o pagina protejata, aspnet-ul ii cere un anume cookie (in cazul nostru .Documents). Daca acesta nu exista, browser-ul utilizatorului este redirectat catre pagina de login (login.aspx). Aici, utilizatorul introduce nume/parola si, daca acestea sunt corecte, va primi un cookie de identificare. Aceste cookie, pe langa informatii despre utilizator (Name, Group-ul din care face parte eventual), contine si informatii despre perioada de valabilitate a sa (implicit, cookie-ul este valid 20 minute, dar acest lucru se poate seta printr-un atribut in web.config).

Pana acuma am specificat doar tipul de autentificare si catre ce pagina va fi redirectat utilizatorul cand acceseaza o pagina protejata. Dar care pagini sunt protejate? Putem avea intr-un site, atat pagini publice (accesibile utilizatorilor anonimi), cat si pagini private. Pentru a specifica acest lucru se folosesc elementele location si authorization in web.config astfel:

<location path="user.aspx">
<system.web>
      <authorization>
            <allow roles="user" />
            <deny users="*" />
      </authorization>
</system.web>
</location>

In exemplul de fata, am protejat pagina user.aspx, astfel incat doar utilizatorii din grupul (<allow roles>) "user" sa aiba acces la ea, toti ceilalti useri (<deny users>) neavand acces. In contextul acesta, al limitarii accesului, "*" inseamna "toti", in timp ce "?" inseamna "utilizatori anonimi". De ex., pentru a limita accesul la o pagina, astfel incat doar utilizatorii inregistrati sa aiba acces, trebuie sa aveti o clauza <deny users="?" />

In exemplul de mai sus, form-ul user.aspx va putea fi accesat numai de catre rolul numit "user". Acesta este un rol creat de mine si evident ca aplicatia nu stie de existenta lui. Eu trebuie intr-un fel sau altul sa setez acest rol atunci cand fac autentificarea la nivel de aplicatie, astfel incat aspnet-ul sa aiba cunostiinta de existenta lui si sa poata sa dea astfel acces sau sa interzica accesul la pagina respectiva in functie de apartenenta user-ului la acel rol sau nu.

In aplicatia noastra, noi avem deja o functie Authenticate care ia ca parametru un nume si o parola si returneaza intr-un parametru tipul utilizatorului daca autentificarea a avut succes. Aceasta functie o putem folosi in pagina login.aspx si daca ea returneaza true, inseamna ca trebuie sa creem un cookie de autentificare pentru utilizator in care sa introducem atat numele utilizatorului (pentru identificare), cat si role-ul din care face parte (User sau Admin).

Dupa ce am determinat rolul utilizatorului, trebuie sa creez un FormsAuthenticationTicket, adica un fel de « bilet » care sa contine toate informatiile de identificare (nume, role, data expirarii). Iata cum se poate face asta:

public HttpCookie GetAuthenticationCookie(string name, UserType type)
{
    // creez un bilet de autentificare
    FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(
        1,                                        // versiunea
        name,                                     // numele utilizatorului
        DateTime.Now,                            // data la care a fost emis
        DateTime.Now.AddMinutes(20),            // data la care expira
        true,                                    // cookie persistent (adica ramane valid intre sesiuni); 
                                                //daca e false, atunci la inchiderea browser-ului se pierde cookie-ul
        type.ToString(),                        // userdata (folosim in acest caz pentru a pastra informatii despre rolul utilizatorului
        FormsAuthentication.FormsCookiePath        // 
    );

    // criptez biletul => un string
    string data = FormsAuthentication.Encrypt(ticket);

    // creez un cookie cu numele specificat in web.config (.Documents) si adaug biletul creat anterior
    HttpCookie hc = new HttpCookie(FormsAuthentication.FormsCookieName, data);
    // setez data la care expira cookie-ul
    hc.Expires = ticket.Expiration;
    return hc;
}

Ok, in acest moment avem un cookie pe care-l putem transmite utilizatorului (Response.Cookie.Add(GetAuthenticationCookie(name, type)). A mai ramas un singur pas, pentru ca totul sa fie perfect, si anume, atunci cand un utilizator se conecteaza, sa testam daca are cookie-ul de autentificare si sa-i dam voie sau nu sa acceseze pagina respectiva. Pentru aceasta, vom folosi metoda AuthenticateRequest, care face parte din clasa Global (o gasiti in fisierul Global.asax.cs). Aici vom pune codul care extrage din cookie biletul de autentificare. Iata codul:

protected void Application_AuthenticateRequest(Object sender, EventArgs e)
{
    HttpCookie hc = Context.Request.Cookies[FormsAuthentication.FormsCookieName];
    if (hc != null) // avem cookie?
    {
        // extragem biletul de autentificare (prin decriptare)
        FormsAuthenticationTicket ticket = FormsAuthentication.Decrypt(hc.Value);

        // cream obiectul User (folosind biletul si role-ul din care face parte utilizatorul
        // User sau Admin)
        Context.User = new GenericPrincipal(new FormsIdentity(ticket),
            new string[] { ticket.UserData } );
    }
}

Asp.Net-ul stie sa foloseasca acest obiect User nou creat si va face automat verificarile daca respectivul user are drepturi sa acceseze o anumita pagina (specificata in web.config). Discutia despre cum "stie" Asp.Net-ul sa faca aceste verificari este ceva mai lunga si mai complicata. Pentru moment luati codul de mai sus ca atare, chiar daca nu este foarte clar. Daca doriti totusi sa aflati mai multe informatii cititi paginile de help de la interfetele IPrincipal, IIdentity si clasele ce implementeaza aceste interfete GenericPrincipal si FormsIdentity. Vezi si codul de mai sus pentru exemplu de folosire. Ideea de baza este ca acel Context.User (accesibil de altfel si din paginile web prin intermediul proprietatii User), contine atat informatia de nume (necesara de ASP.NET pentru a rezolva accese specificate in web.config prin clauza <allow/deny users="nume">), cat si informatii de role (pentru rezolvarea clauzelor de tipul <allow/deny roles="role">).

Concluzie: pentru a implementa o aplicatie cu pagini securizate prin Forms Authentication, exista 3 pasi ce trebuie urmati:

  1. specificarea in web.config a tipului de autentificare (<authentication>) + specificarea user-ilor sau a role-urile care au, respectiv nu au, acces la o anumita pagina (<location>, <authorization> etc.)

  2. Crearea din cod a cookie-ului de autentificare cu informatii legate de numele utilizatorului, role-ul din care face parte, data expirarii cookie-ului

  3. Crearea obiectului Context.User in Application_AuthenticateRequest.

Dupa ce am parcurs acesti pasi, pe langa faptul ca ASP.NET-ul va da/restrictiona accesul la pagini in mod automat, vom putea folosi obiectul User si din cod pentru a lua decizii in functie de numele user-ului sau role-ul din care face parte, astfel:

// in default.aspx
if (User.Identity.Name=="florin")
{
    // fa ceva pentru ca user-ul curent este florin
}
else
if (User.IsInRole("Admin"))
{
    // fa altceva pentru ca user-ul curent face parte din
    // role-ul Admin.
}

Dupa cum am vazut mai sus, un cookie de autentificare expira dupa o anumita perioada de timp. Daca dorim ca sa fortam expirarea acestuia la un moment dat, putem folosi metoda FormsAuthentication.Signout in acest scop. De ex., cand un utilizator doreste sa paraseasca site-ul nostru, el poate apasa un buton de logout. Aceasta expirare fortata a cookie-ului este foarte utila mai ales in cazul in care utilizatorul foloseste un calculator public pentru a se autentifica pe un site si nu doreste ca, dupa ce a plecat, un alt utilizator sa poate folosi cookie-ul sau pentru a intra pe acel site.

Tratarea erorilor

Atunci cand apare o exceptie in codul unei aplicatii ASP.NET, pentru utilizator este vizibil imediat si intr-un mod nu tocmai placut – apare o pagina urata in care este afisata exceptia si utilizatorul nu mai poate sa faca nimic decat sa apese Back in browser.

Fiind programatori responsabili, nu dorim sa se intample asta. De aceea se recomanda ca portiunile de cod in care se pot intampla lucruri necontrolate sa fie incluse in blocuri de tratare a erorilor. Iata doua exemple:

try
{
      // open file
      m_trFile = File.OpenText(strPath);
      // read number of questions
      line = m_trFile.ReadLine();
      NoQuestions = System.Convert.ToInt32(line,10);
      // create Questions array.
      Questions = tions = new Tester.TestQuestion[NoQuestions];
      // do some work ...
      m_trFile.Close(); lose(); 
      return "All questions read ok.";;
}
catch(System.IO.IOException ioex)
{
      return "Could not open the file!!!";
}
catch(System.InvalidCastException icex)
{
      return "Could not read the number of questions from file!!!";
}
catch(Exception ex)
{
      return "Could not read the questions.";
}

In exemplul de mai sus, am izolat in try {…} zona de cod considerata de mine critica. In aceasta, se pot produce mai multe exceptii, cum ar fi IOException (de exemplu nu am gasit fisierul specificat), InvalidCastException (conversia de la string la int a esuat) si altele.

Observati va rog ca am scris mai multe blocuri catch, primele doua tratand cazurile in care se produc exceptii concrete, stiute, iar ultimul tratand cazul in care se produce o alta exceptie (orice exceptie). Conteaza si ordinea in care le-am scris. In mod intentionat am pus tratarea exceptiei generale ultima, deoarece asta trebuie intalnita numai daca exceptia nu este de tipul primelor doua, caz in care vreau sa returnez ceva specific. Daca as fi pus prima data cazul cu Exception ex, atunci orice exceptie s-ar fi intalnit, ar fi fost tratata de acest caz si nu s-ar fi ajuns la urmatoarele.

try
{
      sqlConn.Open();
      comMark.ExecuteNonQuery();
      // do something more ...
}
catch(SqlException sqlex)
{
      // handle error ...
}
finally
{
      sqlConn.Close();
}

In acest caz, am inclus si un bloc finally. Liniile de cod scrise in acest bloc se executa indiferent daca s-a generat o exceptie sau nu. In mod normal daca apare o exceptie la executia liniei comMark.ExecuteNonQuery();  exceptia este prinsa si tratata in blocul catch si ceea ce apare dupa in try, adica // do something more ... nu va mai fi executat.

Dupa cum se vede mai sus, in finally inchid conexiunea la baza de date, care se recomanda pentru a nu consuma resurse aiurea. Daca as fi dorit sa inchid conexiunea la baza de date in blocul try, dupa executia comenzii si mi-ar fi aparut eroare la executia comenzii, conexiunea nu s-ar mai fi inchis.

Nu am sa discut aici cazul folosirii cuvantului cheie throw, atunci cand dorim „sa aruncam” o exceptie. Iata totusi un exemplu, in care atunci cand se doreste „sarirea” imediata peste linii de cod din blocul try direct in blocul catch.

try
{
    // (1) Check if file is zero-length (file does not exist).
    if (filUpload.PostedFile.ContentLength == 0)
    throw new System.IO.FileNotFoundException();
    // (2) Save uploaded file to server using the file base name.
    filUpload.PostedFile.SaveAs(Request.MapPath(Request.ApplicationPath) + "\\" + strFilename);
    // Add file to list of server files.
    lstFiles.Items.Add(strFilename);    
    // Select the first item in the list box.
    lstFiles.SelectedIndex = 0;
}
catch (System.IO.FileNotFoundException ex) // (3) Handle possible exceptions.
{
    litError.Text = "<p>The file does not exist.</p>";
}

Deployment

Copiere

Evident ca dupa dezvoltarea unei aplicatii, ea trebuie livrata beneficiarului. Pentru ca ea sa mearga, este nevoie sa fie “depusa” pe serverul acestuia de web. In primul rand, fisierele trebuie copiate undeva pe hard-disk-ul serverului web (nu neaparat in calea predefinita …\Inetpub\wwwroot\). Dupa aceea, in IIS-ul server-ului web, trebuie creat un nou director virtual, care sa indice catre calea directorului fizic unde se afla de fapt aplicatia.

 Deci, dupa ce ati depus proiectul ASP.NET pe serverul web (asta se face prin simpla copiere), mergeti in IIS, click dreapta pe Default Web Site, New -> Virtual Directory, alegeti un Alias (acesta va fi de fapt numele aplicatiei asa cum va fi ea vazuta din afara), navigati la calea directorului propriu-zis si … gata. gata. Acum, aplicatia va putea fi referita din exterior cu numele: http://<nume_server>/<alias> ; unde <nume_server> este de fapt situl principal expus de serverul web, iar <alias> este ce ati ales voi la crearea directorului virtual in IIS.

 Evident, trebuie neaparat sa aveti grija de securizarea aplicatiei daca este cazul, dupa cum se vede si in paragraful precedent.ecedent.

 Proiect de instalare

Scenariul simplu de mai sus este suficient in cazul in care faceti deployment pe un server pe care il controlati. Totusi, daca aplicatia scrisa de voi are ca destinatie un client sau mai multi, poate fi necesara crearea unui proiect de instalare. Rezultatul va fi un fisier de tip .msi, pe care clientul il va rula pentru a-si instala aplicatia web.       

Iata cum se face asta. Avand deschis proiectul vostru ASP.NET, mergeti la File, New, Project, alegeti Setup and Deployment Projects, si in fine Setup Wizard. Aveti grija sa fie selectat “Add to Solution”, dati un nume proiectului si alegeti calea unde sa il faca. In mod normal, calea este sub directorul aplicatiei voastre web, dar puteti sa il puneti si in alta parte. Chiar se recomanda acest lucru, deoarece proiectul de instalare pe care il facem acum nu este unul web, deci nu prea are ce sa caute in wwwroot.

In fine, dupa alegerea caii, bifati Create a Setup for a Web Application, Next, iar la Choose projects outputs to include, trebuie neaparat sa bifati Output Files (.dll care “contine” aplicatia ASP.NET) si Content Files (Form-urile si web.config). Daca aplicatia are si anumite resurse localizate (adica specifice anumitor limbi) atunci mai trebuie selectat si Localized Resources, de asemenea putem sa selectam Documentation Files daca au fost generate. In fine, e suficient sa selectati Output Files.

La pasul urmator, puteti sa mai adaugati alte fisiere (sa spunem de genul bine-cunoscutelor fisiere README). Dupa apasarea butonului Finish, se poate observa ca solutia noastra initiala are acum un proiect in plus, anume acesta de instalare.

In sectiunea Properties, pot fi date foarte multe informatii despre produs : autorul, versiunea, numele, etc. Foarte importante pot sa fie : Remove Previous Versions  - in cazul in care clientul are o versiune anterioara a produsului nostru si o primeste pe cea curenta, nu cred ca doreste sa le aiba pe ambele ; Detect Newer Installed Version – nu dorim sa instalam o versiune veche peste una noua.

In fine, daca dati Build pe proiectul de instalare, veti observa ca se construieste prima data proiectul web, apoi se construieste .msi si doua arhive : Setup.exe si <nume_proiect_web>.dll. Daca rulati .msi, vi se porneste un wizard care instaleaza aplicatia web.

Dupa terminarea instalarii, ar trebui sa vedeti in IIS un nou director virtual, cu numele pe care l-ati ales voi (este de fapt numele dat la Project Name in proiectul de instalare). In acest moment aplicatia poate fi accesata cu http://localhost/<nume_aplicatie>, daca aplicatia voastra are o pagina default.aspx. Daca nu are, atunci aplicatia va putea fi accesata numai daca introduceti un link de genul : http://localhost/<nume_aplicatie>/<StartForm>.aspx sau daca mergeti in IIS, la directorul virtual corespunzator aplicatiei, Click Dreapta, Properties, Documents si adaugati <StartForm>.aspx, unde <StartForm> este numele primei pagini a aplicatiei.

Configurare, mentenanta

Acum, sa povestim putin despre avantajele folosirii unui fisier web.config. In primul si in primul rand, sa ne imaginam o aplicatie de E-Commerce care lucreaza cu un server de baze de date si sa ne imaginam ca noi facem aplicatia si o dam la mai multi clienti.

Bineinteles ca serverul de baze de date se va numi in mod diferit la fiecare client. Cum facem deci sa putem livra aplicatia clientilor, fara a recompila codul la fiecare ? Cum facem conexiunea la baza de date pentru fiecare client ? Ei bine, folosim web.config :

<appSettings>
            <add key="Database" value="Data Source=FLORIN\FLORINSQL;Initial Catalog=elearning;User Id=***;Password=***" />
</appSettings>

Dupa cum se poate vedea aici, am pus o inregistrare care contine chiar un string de conexiune la baza de date. Cum fac conexiunea in aplicatia mea ? Caut in web.config o cheie cu numele « Database ». Iata:

public static SqlConnection ConnectToDB()
{
      string strParameters;
      System.Configuration.AppSettingsReader apsr = new System.Configuration.AppSettingsReader();      
      strParameters = (string)apsr.GetValue("Database",typeof(string));
      SqlConnection sqlConn = new SqlConnection(strParameters);
      return sqlConn;
}

Atunci, tot ce trebuie sa facem la fiecare client atunci cand instalam aplicatia, este, in cel mai rau caz, sa modificam acea inregistrare din web.config cu numele serverului clientului. S-ar putea chiar sa nu fie nevoie sa facem asta. In multe cazuri, serverele de MS SQL nu au nume de instanta, asa cum se vede ca apare in exemplul de mai sus. In web.config – ul meu, Data Source este FLORIN\FLORINSQL, deoarece MS SQL Server de pe calculatorul meu are nume de instanta. Dar atunci cand MS SQL Server nu are nume de instanta, Data Source poate sa fie (local). Prin urmare, pentru un grad cat mai mare de generalitate, se recomanda sa scrieti in web.config stringul de conexiune urmator si doar in anumite cazuri veti fi nevoiti sa il modificati:

<appSettings>
            <add key="Database" value="Data Source=(local);Initial Catalog=elearning;User Id=***;Password=***" />
</appSettings>

Poate ca ati observat in alte exemple de aplicatii faptul ca in web.config se pot scrie mai multe constante care se folosesc in aplicatie si care se pot modifica de-a lungul timpului. Pentru un cost de mentenanta cat mai redus, se recomanda acest lucru, deoarece la modificarea acelor constante nu mai este nevoie sa recompilati codul aplicatiei, ci doar sa editati un fisier XML.

Tema 4

Va rog sa porniti de la starterul pe care il gasiti aici: http://maryuss-thebest.uv.ro/cursuri/starter4.zip .

Acestea sunt cerintele :

Folosindu-va de starter-ul oferit, va trebui sa asigurati securitatea bazata pe roluri (user, admin), in functie de tipul utilizatorilor din baza de date (coloana Type din Users), pe care va trebui sa il cititi si interpretati. Va puteti "inspira" din exemplele de cod inclus in lectie.

Un "User" va putea accesa doar paginile login.aspx si user.aspx, in timp ce un "Admin" poate vizualiza doar login.aspx si admin.aspx. Mai creati o pagina RegisteredUsers, accesibila tutoror utilizatorilor inregistrati (cei care nu sunt anonimi), pe care afisati lista utilizatorilor din baza de date (doar nume si tipul utilizatorului).

De asemenea implementati in meniuri si facilitatea de "Logout". In meniurile UserMenu.ascx si AdminMenu.ascx introduceti cate un link, care va realiza logout-ul (folosind FormsAuthentication.Signout). (2 p)

Toate functiile care acceseaza baza de date trebuie sa contina blocuri try .. catch ..finally. (0.5 p) 

In starterul oferit, string-ul de conectare la baza de date apare in cod. Daca dorim sa instalam aplicatia la mai multi clienti, abordarea asta e gresita. Deci trebuie sa gasiti o solutie prin care stringul de conexiune sa fie tinut minte undeva, astfel incat sa nu fie nevoie de recompilarea codului la fiecare client. (1 p) 

Va trebui sa creati un proiect de instalare: Setup Wizard pentru proiectul ASP.NET, in care sa includeti cel putin Output Files si Content Files. In momentul in care predati tema, va rog sa includeti si proiectul de instalare. (1.5 p)

preview --- Cursuri --- next