Eén van de handige nieuwe features in ASP.NET 2.0 is de membership en de role provider. Dit mooi ontworpen systeem heeft een goede scheiding tussen opslag en gebruik van gegevens. Door deze scheiding is het als applicatieontwikkelaar gemakkelijk om dit systeem te gebruiken zonder dat men zich zorgen hoeft te maken over waar de data opgeslagen wordt. Dit laatste kan runtime geconfigureerd worden en desgewenst kunnen er nieuwe vormen van dataopslag gemaakt worden zonder dat de applicatiecode hoeft te wijzigen.
Dit is een zeer krachtig en bruikbaar concept, maar wat mij in eerste instantie aantrok aan het model was het gemak waarmee een applicatieontwikkelaar gebruikers en op rollen gebaseerde beveiliging toe kan passen. Door gebruik te maken van de standaard componenten hoeft de ontwikkelaar geen implementatie te bedenken en kan hij bestaande uitbreidingen ook zonder extra werk gebruiken, een groot pluspunt als je het mij vraagt.
De providers zijn ontworpen door het ASP.NET team voor het gebruik binnen ASP.NET en een deel van de applicaties die ik ontwikkel zijn gewone traditionele Windows applicaties. Gelukkig blijkt het niet moeilijk te zijn om deze providers ook binnen een WinForms applicatie te gebruiken, iets wat ik nu door middel van een eenvoudige console applicatie wil laten zien.
Registreren en valideren van gebruikers
Laten we om te beginnen een nieuwe Visual Basic console applicatie maken met Visual Studio 2005. Indien je liever met C# werkt is dit geen probleem, alles werkt op exact de zelfde manier. Ik geef hier alleen voorbeelden in Visual Basic met het oog op de lengte van de broncode.
Als eerste moeten we een referentie naar System.Web toevoegen aan het project. Dit voelt misschien een beetje onnatuurlijk aan, maar het is gewoon een assembly als elke andere en kan dus prima gebruikt worden.
Voeg nu de volgende regel toe boven in Module1.vb:
Imports System.Web.Security
En zet vervolgens de volgende code in de Sub Main():
' Validate a username/password
If Membership.ValidateUser("Maurice", "Password_1") Then
Console.WriteLine("User validated.")
Else
Console.WriteLine("User invalid!")
End If
Console.ReadKey()
Nu kunnen we de applicatie al starten. Als we dat doen, meldt de applicatie dat de gebruiker niet geldig is. Niet echt een verrassing, we hebben per slot van rekening nog niets gedaan om gebruikers te registreren.
Voeg nu de volgende code toe aan de Sub Main(), en wel voor de ValidateUser aanroep:
' Creating a new user
Dim status As MembershipCreateStatus
Membership.CreateUser( _
"Maurice", _
"Password_1", _
"maurice@TheProblemSolver.nl", _
"Password question", _
"Password answer", _
True, _
status)
Console.WriteLine(status.ToString())
Start de applicatie nu opnieuw.
Als je de membership provider nog nooit eerder gebruikt hebt, zal het je misschien verbazen dat dit gewoon werkt. Tijdens het uitvoeren kan de gebruiker gewoon toegevoegd worden en daarna gevalideerd worden. Run je de applicatie nu nog een keer dan krijg je de foutmelding DuplicateUserName, omdat de gebruiker al bestaat maar de ValidateUser() functie werkt gewoon. Hoe kan dit? We hebben nergens een database aangemaakt of aangegeven welke database of SQL Server gebruikt moet worden. Het geheim is dat de membership provider automatisch een nieuwe SQL Express database maakt, ASPNETDB genaamd, in een App_Data subdirectory. Deze database wordt vervolgens gebruikt om de benodigde gegevens, zoals gebruikersnaam en password, op te slaan.
Het geheim is dat de membership provider automatisch een nieuwe SQL Express database maakt, ASPNETDB genaamd
Tot nu toe gaat alles van een leien dakje en hebben we met heel weinig werk gebruikers geregistreerd en laten inloggen. Helaas moesten we bij het registreren wel een hoop informatie opgeven. Die informatie, zoals password-vraag en -antwoord, is misschien nuttig in sommige websites maar zelden of nooit in een Windows applicatie. Gelukkig heeft de Membership.CreateUser() ook een overload die alleen om de gebruikerscode en password vraagt, en dat ligt meer in de lijn van wat we willen gebruiken. Helaas gaat het niet zo makkelijk en geeft de onderstaande code een System.Web.Security.MembershipCreateUserException met de boodschap “The password-answer supplied is invalid.”.
Dim user As MembershipUser
user = Membership.CreateUser("User2", "Password")
Console.WriteLine(user.UserName)
Verder zijn er misschien andere membership eigenschappen van toepassing, zoals de verplichting niet-alfanumerieke karakters in het password op te nemen. Alhoewel de Membership class deze eigenschap via de MinRequiredNonAlphanumericCharacters leesbaar maakt, kan dit niet veranderd worden. Om ze wel te veranderen moeten we het applicatie configuratie file gebruiken. Aangezien we die nog niet hebben is dit een goed moment om de app.config toe te voegen. Voeg nu de volgende sectie toe onderaan de sectie:
type="System.Web.Security.SqlMembershipProvider,
System.Web, Version=2.0.0.0, Culture=neutral,
PublicKeyToken=b03f5f7f11d50a3a"
connectionStringName="LocalSqlServer"
enablePasswordRetrieval="false"
enablePasswordReset="true"
requiresQuestionAndAnswer="false"
applicationName="/"
requiresUniqueEmail="false"
passwordFormat="Hashed"
maxInvalidPasswordAttempts="5"
minRequiredPasswordLength="1"
minRequiredNonalphanumericCharacters="0"
passwordAttemptWindow="10"
passwordStrengthRegularExpression="" />
Dit ziet er misschien in eerste instantie een beetje vreemd uit. Het is per slot van rekening niet gebruikelijk om een system.web configuratie blok toe te voegen voor een console applicatie. Maar als we er rekening mee houden dat de memberschip provider door het ASP.NET team geschreven is voor het gebruik in ASP.NET en dat de code gewoon in het huidige configuratie file kijkt, klinkt het al een stuk minder gek.
Maar wat doet dit configuratie blok eigenlijk? Het eerste wat er gebeurt bij het inlezen van het configuratie blok, is het verwijderen van de huidige AspNetSqlMembershipProvider. De AspNetSqlMembershipProvider staat in de machine.config vermeld als de standaard implementatie voor de membership provider. Daarna wordt dezelfde AspNetSqlMembershipProvider weer toegevoegd, alleen nu met een minder strikte password controle en zonder de eis een email adres en password vraag op te geven.
Gebruikersrollen toevoegen
Nu we gebruikers toe kunnen voegen en valideren is het een goed punt om ook gebruikersrollen te gaan gebruiken. Om met gebruikersrollen te werken moeten we de klasse System.Web.Security.Roles gebruiken. In grote lijnen werkt deze klasse op dezelfde manier als de membership provider. In dit geval roepen we de CreateRole() functie aan om nieuwe rollen toe te voegen en AddUserToRole() om gebruikers aan die rol te koppelen. De volgende code kan gebruikt worden om de rol Developer aan mijn eerder gecreëerde gebruiker toe te voegen:
Roles.CreateRole("Developer")
Roles.AddUserToRole("Maurice", "Developer")
Helaas is het deze keer toch niet helemaal zo simpel. Als we de bovenstaande code uitvoeren krijgen we namelijk een System.Configuration.Provider.ProviderException exceptie met de tekst “The Role Manager feature has not been enabled.”. Gelukkig is de oplossing niet moeilijk; alles wat we moeten doen is de volgende regel in de system.web sectie van app.config toevoegen:
Zodra dit is gebeurd, kunnen we rollen aan gebruikers koppelen en deze ook weer controleren. Met de onderstaande code kan gecontroleerd worden of een bepaalde gebruiker een specifieke rol heeft:
If Roles.IsUserInRole("Maurice", "Developer") Then
Console.WriteLine("Is a developer.")
Else
Console.WriteLine("Doesn't write code.")
End If
De huidige gebruiker bewaren
Nu we de ingelogde gebruiker kunnen valideren en kunnen zien over welke rollen hij beschikt, blijft er nog één probleem over en dat is waar de gebruikersnaam te bewaren voor die momenten dat we op de aanwezigheid van een specifieke rol moeten controleren. Naast de hierboven gebruikte IsUserInRole() functie is er nog een overload die met maar één parameter, de rol naam, aangeroepen wordt. Wat gebruikt deze functie dan als gebruikersnaam? Een snelle blik, met behulp van Lutz Roeder’s Reflector, leert dat dit afhankelijk is van hoe de applicatie gehost is. In een web applicatie kijkt de code naar HttpContext.Current.User en in een Windows applicatie wordt naar Threading.Thread.CurrentPrincipal gekeken. Dit is niet echt verbazingwekkend aangezien dit helemaal in lijn is met de standaard plaats om binnen het .NET framework de huidige gebruiker te zien. Des te meer reden om hier niet van af te wijken en datzelfde mechanisme ook te gebruiken.
De CurrentPrincipal property geeft een object terug dat de interface IPrincipal implementeert, dus dat is wat we nodig hebben. Het ASP.NET team heeft hiervoor een System.Web.Security.RolePrincipal klasse toegevoegd die we kunnen gebruiken. Aangezien deze RolePrincipal een System.Security.Principal.IIdentity als parameter in de constructor nodig heeft. moeten we een object hebben dat deze interface ondersteunt. Ook hiervoor hoeven we geen nieuwe klasse te maken want het .NET framework bevat standaard al System.Security.Principal.GenericIdentity die we met de gebruikersnaam als parameter voor de constructor kunnen aanmaken. Met deze twee standaard klasses is het plaatje compleet en kunnen we de code weer uitbreiden. Voeg de volgende regel toe aan de top van de Module1.vb:
Imports System.Security.Principal
Nu kunnen we de huidige gebruiker bewaren en eventueel aanwezige rollen bevragen met de volgende code:
Dim user As MembershipUser =_
Membership.GetUser("Maurice")
Dim identity As New GenericIdentity(user.UserName)
Dim principal As New RolePrincipal(identity)
Threading.Thread.CurrentPrincipal = principal
If Roles.IsUserInRole("Developer") Then
Console.WriteLine("Is a developer.")
Else
Console.WriteLine("Doesn't write code.")
End If
Een alternatieve, maar langere manier om hetzelfde te bereiken, verloopt als volgt:
Console.Write(_
Threading.Thread.CurrentPrincipal.Identity.Name)
If Threading.Thread.CurrentPrincipal.IsInRole(_
"Developer") Then
Console.WriteLine(" is a developer.")
Else
Console.WriteLine(" doesn't write code.")
End If
De ASPNETDB database
In de code hierboven werd de ASPNETDB database automatisch aangemaakt zonder dat we hier iets voor hoefden te doen. Dat is gemakkelijk om snel te beginnen, maar het is goed mogelijk dat we al ergens een SQL Server hebben draaien met onze data en dat we de gebruikersgegevens gewoon op dezelfde plaats op willen slaan. Gelukkig is dit, net zoals de rest in dit artikel, ook niet moeilijk te doen en heeft het ASP.NET team de nodige middelen aangeleverd.
De eerste stap is een database aan te maken met de juiste structuur. Om dit te doen komt er bij de .NET 2.0 SDK een hulp programma met de naam aspnet_regsql.exe. Dit programma is op twee manieren te gebruiken: zonder user interface en helemaal via opstart parameters of door het zonder parameters te starten en de user interface te gebruiken. De eerste optie geeft wat meer mogelijkheden maar de tweede optie, met user interface, is makkelijker om te beginnen.
Vervolgens moeten we de Membership provider naar de nieuwe centrale database laten kijken
Vervolgens moeten we de Membership provider naar de nieuwe centrale database laten kijken in plaats van de lokale. Ook dit is weer makkelijk te doen aangezien de Membership provider standaard naar de LocalSqlServer connectie string kijkt in de app.config. We hebben ook hier weer twee opties: op het moment dat we de AspNetSqlMembershipProvider opnieuw toevoegen in de app.config kunnen we een nieuwe connectie string naam opgeven in het connectionStringName attribuut of we kunnen de LocalSqlServer connectie string wijzigen. In dit voorbeeld heb ik het laatste gedaan door het volgende aan de app.config toe te voegen:
connectionString="Data Source=.\sqlexpress;Initial
Catalog=MyUsers;Integrated Security=True"/>
Conclusie
In dit artikel heb ik laten zien hoe je de ASP.NET Membership en Role providers in een gewone Windows applicatie kunt gebruiken. Dit is zeer gemakkelijk en het levert direct grote voordelen op. Aangezien gebruikersauthenticatie iets is dat ik in de meeste applicaties moet doen is dit een techniek die ik zeker vaker zal gebruiken.