vrijdag 24 mei 2013

Caching in Windows Azure

Wanneer je jouw eerste website hebt gemaakt, wil je die natuurlijk delen met anderen. Daarvoor ga je op zoek naar een hostingpartij, iemand die een heleboel computers in de lucht houdt die dag en nacht bereikbaar zijn. Zo'n hostingprovider verzorgt ook de hele infrastructuur. Het enige wat jij hoeft te doen is jouw website te publiceren. In het begin ga je natuurlijk de goedkoopste uitzoeken. Wanneer blijkt dat je interessante inhoud hebt die veel bezoekers trekt, zul je op een gegeven moment merken dat de site erg traag wordt. De provider is jou aan het afknijpen want je gebruikt teveel resources (die je deelt met anderen) waardoor andere websites daar hinder van ondervinden. Op dat moment wordt het tijd om een dedicated webserver te huren en die kosten, afhankelijk van de hardware die je wenst, aanzienlijk meer. Ook die dedicated webserver kan op een gegeven moment aan zijn limiet komen en dan kun je een aantal dingen doen:
  1. Meer hardware kopen (processor, geheugen, bandbreedte)
  2. Kijken waarom die machine aan zijn capaciteit zit
De eerste is eigenlijk geen optie, dat kan altijd nog. De tweede wil ik hier bespreken.

Jouw website wordt gehost op een dedicated server en hij is niet meer vooruit te branden. Na een korte analyse zie je dat de processor eigenlijk altijd de 100% aantikt en het geheugen vol loopt. Wat is er aan de hand? Wat is die machine allemaal aan het doen?
Bij de meeste websites worden de webpagina's gegenereerd door programma's zoals asp.net, php en ruby on rails. Dat genereren kost natuurlijk tijd. Daarnaast moet er data uit de database gehaald worden en dat kost natuurlijk ook weer tijd en resources. Je kunt je afvragen of dat genereren en ophalen van data echt telkens opnieuw moet gebeuren. In veel gevallen is dat niet nodig omdat de inhoud van de pagina niet continu verandert. Die pagina's wil je na de eerste generatie eigenlijk opslaan zodat de volgende bezoeker de opgeslagen versie te zien krijgt, en dat is nu caching. Het beste is om die gecachte pagina in het geheugen van de server op te slaan omdat toegang tot het geheugen supersnel is, maar ook het geheugen is beperkt. Je zult je dus de volgende vragen moeten stellen:
  1. Welke pagina's ga je cachen? Dat hangt van een aantal factoren af
    1. Kost het veel (tijd, resources) om de pagina te genereren?
    2. Wordt de pagina vaak geraadpleegd?
  2. Hoe lang ga je een pagina cachen? Dat bepaal je door je af te vragen hoe vaak de inhoud verandert. Pagina's die eens per maand veranderen, kun je langer in het geheugen houden dan pagina's die eens per dag wijzigen.
  3. Ga je de hele pagina cachen of slechts bepaalde onderdelen van de pagina? Stel je hebt een productpagina, ga je dan de hele pagina voor ieder product bewaren of sla je alleen de productdata op zodat de toegang tot de database verminderd wordt.
Bij BesteProduct hebben we met al deze factoren te maken gehad en redelijk opgelost met de hulpmiddelen die ASP.NET ons biedt, maar toch lopen we tegen de hardwarelimieten aan. Reden om de hele website in Windows Azure te hangen. Hiermee hebben we wel weer een nieuw probleem gecreëerd. Omdat we nu met meerdere machines werken, betekent dit dat de data nu ook op verschillende machines gecacht wordt. De cache zal dus alleen goed werken als het verzoek op op de juiste machine aankomt en dat is bij Windows Azure niet het geval omdat de loadbalancer bepaalt welke machine het verzoek afhandelt. Azure gebruikt hiervoor de zogenaamde "round robin" benadering, ieder verzoek wordt telkens naar een andere server afgevuurd (zie afbeelding 1)

Afbeelding 1. De round robin benadering. Er worden door diverse clients negen verzoeken afgevuurd. Ieder verzoek wordt telkens door een andere server afgehandeld (bron: Citrix support).
Bijkomend nadeel is dat dezelfde pagina meerdere keren wordt gecacht, en dat is natuurlijk zonde van de resources.

Windows Azure biedt gelukkig uitkomst met een gedistribueerde cache: Windows Azure Caching (voorheen Velocity). Met Windows Azure Caching kun je de cache van de afzonderlijke servers delen (co-located) of je kunt een aparte server inrichten die louter en alleen geheugen aanlevert (dedicated). Laten we eens gaan bekijken hoe dat werkt.
Met de oude benadering, waarbij je de pagina caching middels het OutputCache attribute instelt (zie listing hieronder), zou je het resultaat uit afbeelding 2 krijgen.


public class HomeController : Controller
{
    [OutputCache(Duration=900)]
    public ActionResult Index()
    {
        ViewBag.RoleID = RoleEnvironment.CurrentRoleInstance.Id;
        ViewBag.CurrentTime = DateTime.Now.ToLongTimeString();
        return View();
    }
}
Afbeelding 2. Het gevolg van de standaard caching. Op de eerste server bestaat een gecachte pagina met een andere tijd dan op de tweede server. Dit is dus niet wenselijk.
Om met de gedistribueerde caching te werken, installeer je eerst de Windows Azure SDK (als je dat nog niet gedaan hebt) en, via NuGet,  de package Windows Azure Caching. Daarna selecteer je de betreffende Azure Web Role in Visual Studio 2012 en ga je naar het "Caching" menu (zie afbeelding 3).


Afbeelding 3. Het Caching menu van de Web Role "CacheTest".
In dit scherm schakel je de caching in (Enable Caching), geef je aan of je co-located wilt of dedicated. In het geval van co-located, waarbij het geheugen geleverd wordt door de webservers zelf, geef je aan hoeveel procent van het geheugen je voor caching wil inzetten (niet teveel anders heeft de webserver zelf niet genoeg geheugen meer). Je zult ook een blobstorage moeten opgeven waarin de cache configuratie wordt opgeslagen en eventueel kun je het cachegedrag instellen. In de configuratie van de website (web.config) pas je de volgende sectie aan (wordt met de installatie van Windows Azure caching automatisch toegevoegd) waarin het "identifier" attribuut wordt ingesteld op de naam van de web role (CacheTest in dit voorbeeld)


<dataCacheClients>
  <dataCacheClient name="default">
    <autoDiscover isEnabled="true" identifier="CacheTest" />
  </dataCacheClient>
</dataCacheClients>

Deze handeling leidt nu tot het resultaat uit afbeelding 4.


Afbeelding 4. Distributed cache geactiveerd. Je krijgt nu op alle instanties dezelfde gecachte pagina te zien.
Het feit dat we hier telkens server 1 zien is natuurlijk ook een gevolg van de page output cache. Om die te laten variëren en de tijd gelijk te houden moeten we de tijd handmatig in de cache opslaan. Normaliter zou je hier HttpContext.Cache voor gebruiken, maar dat werkt niet in het gedistribueerde systeem. We moeten nu een beroep doen op de classen DataCacheFactory en DataCache die uit de namespace Microsoft.ApplicationServer.Caching komen. De DataCacheFactory is verantwoordelijk voor het aanleveren van een DataCache object waarop je uiteindelijk werkt. Dit doe je door op het DataCacheFactory de methode "GetCache("cache_name")" aan te roepen ("cache_name" verwijst naar een cache configuratie die je instelt in het "Caching" menu (zie afbeelding 3, helemaal onderaan. "Named Cache Settings").
Hierbij moet je opletten dat meerdere servers deze gedeelde pot tegelijk kunnen benaderen hetgeen weer tot ongewenste bijverschijnselen kan leiden. Eerst maar eens de centrale toegang tot de DataCache inregelen. Omdat het aanmaken van het DataCache object vrij duur is, is het handig om hiervoor het singleton patroon in te zetten.


public class HomeController : Controller
{
    private static object stick = new object();
    private static DataCache _cache = null;

    private static DataCache Cache
    {
        get
        {
            if (_cache == null)
            {
                DataCacheFactory factory = new DataCacheFactory();
                lock (stick)
                {
                    _cache = factory.GetCache("my_cache");
                }
            }
            return _cache;
        }
    }
}

DataCache heeft de methodes Put, Add en Get om de cache te benaderen, maar houden geen rekening met het tegelijkertijd benaderen door alle servers (concurrency). Het is dus heel goed mogelijk dat deze methodes een fout zullen opleveren als de resource in gebruik is of als een resource nog niet bestaat. Vandaar dat het handig kan zijn om er speciale toegangsfuncties voor te definiëren. Hieronder volgt een implementatie van hoe die functies eruit zouden kunnen zien. 


private static bool TryCacheGet(string key, out object cachedItem, int counter = 0)
{
    cachedItem = null;
    try
    {
        cachedItem = Cache.Get(key);
        return cachedItem != null;
    }
    catch (DataCacheException e)
    {
        if (e.ErrorCode == DataCacheErrorCode.KeyDoesNotExist) return false;
        if (e.ErrorCode == DataCacheErrorCode.RetryLater)
        {
            if (counter > 9) return false;
            Thread.Sleep(100);
            return TryCacheGet(key, out cachedItem, ++counter);
        }
    }
    return false;
}

private static bool TryCachePut(string key, object cachedItem, int counter = 0)
{
    DataCacheLockHandle locker;
    try
    {
        object result = Cache.GetAndLock(key, TimeSpan.FromSeconds(1), out locker);
        Cache.PutAndUnlock(key, cachedItem, locker);
        return true;
    }
    catch (DataCacheException e)
    {
        if (e.ErrorCode == DataCacheErrorCode.KeyDoesNotExist)
        {
            Cache.Put(key, cachedItem);
            return true;
        }
        if (e.ErrorCode == DataCacheErrorCode.ObjectLocked)
        {
            if (counter > 9) return false;
            Thread.Sleep(100);
            return TryCachePut(key, cachedItem, ++counter);
        }
    }
    return false;
}

De GetAndLock en PutAndUnlock zijn handige functies die veel concurrency problemen voor hun rekening nemen. 
Het gebruik in een MVC-action ziet er dan als volgt uit:


public ActionResult Index()
{
    ViewBag.RoleID = RoleEnvironment.CurrentRoleInstance.Id;
    object cachedItem;
    if (TryCacheGet("time"out cachedItem))
    {
        ViewBag.CurrentTime = cachedItem as string;
    }
    else
    {
        ViewBag.CurrentTime = DateTime.Now.ToLongTimeString();
        TryCachePut("time", ViewBag.CurrentTime);
    }
    return View();
}
Tenslotte moet je in de web.config de volgende sectie uitcommentariëren

<caching>
  <outputCache defaultProvider="AFCacheOutputCacheProvider">
    <providers>
      <add name="AFCacheOutputCacheProvider" type="Microsoft.Web.DistributedCache.DistributedCacheOutputCacheProvider, Microsoft.Web.DistributedCache" cacheName="default" dataCacheClientName="default" />
    </providers>
  </outputCache>
</caching>

Bij mij gaf deze actie de volgende foumelding:

Could not load file or assembly 'Microsoft.WindowsAzure.ServiceRuntime, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35' or one of its dependencies. The system cannot find the file specified.


Dat klopt want mijn ServiceRuntime versie is 1.8.0.0. Kennelijk is de Windows Azure Cache package gecompileerd met een ServiceRuntime versie 2.0.0.0, een probleem dat met een binding redirect eenvoudig op te lossen is (hopelijk raak ik daarbij geen fancy functionaliteiten kwijt).

<runtime>
  <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
    <dependentAssembly>
      <assemblyIdentity name="Microsoft.WindowsAzure.ServiceRuntime" publicKeyToken="31bf3856ad364e35" />
      <bindingRedirect oldVersion="2.0.0.0" newVersion="1.8.0.0" />
    </dependentAssembly>
  </assemblyBinding>
</runtime>

Het resultaat is nu in afbeelding 5 te bewonderen

Afbeelding 5. De tijd gecacht in de gedeelde cache van Windows Azure.
Wanneer je nu besluit om een dedicated cache server in te richten, maak je een nieuwe Worker Role aan (we noemen die voor het gemak even CacheRole) waarin je de Cache aanzet (afbeelding 3) en vervolgens de "Dedicated" optie selecteert. Het geheugengebruik kun je niet langer instellen omdat al het bruikbare geheugen nu voor cache wordt ingezet. In de web.config van de website geef je voor de "identifier" nu de naam van de CacheRole op.


<dataCacheClients>
  <dataCacheClient name="default">
    <autoDiscover isEnabled="true" identifier="CacheRole" />
  </dataCacheClient>
</dataCacheClients>

Voor de rest verandert er niets.

vrijdag 17 mei 2013

Autorisatie met ACS (Deel 2)

In de post "Windows Identity Foundation met ACS" heb je kunnen zien hoe je op jouw website/applicatie gebruik kunt maken van Identity Providers (IP's) zoals Facebook, LiveID en Yahoo!. Access Control System (ACS) speelde hierbij de rol van mediator (de federation provider), die de informatie van de verschillende IP's transformeert naar claims (jouw uiteindelijke paspoort). We hebben ook gezien dat voor autorisatiedoeleinden deze basisset niet echt bruikbaar is. 
In "Autorisatie met ACS (Deel 1)" heb ik beschreven hoe je de claims in ACS kunt verrijken met extra claims op basis van de binnenkomende claims van de IP. Daarnaast heb ik beschreven hoe je die claims op jouw website kunt gebruiken om rechten tot bepaalde delen van de website af te regelen. Het probleem bij deze benadering is, dat het wel erg bewerkelijk is om op die manier claims toe te kennen aan specifieke gebruikers.
In dit laatste deel laat ik zien hoe je de claims van ACS kunt verrijken met informatie uit jouw eigen database.

Wanneer een onaangemelde gebruiker op jouw site komt, wordt hij meteen doorverwezen naar een Identity Provide (IP). Wanneer de authenticatie slaagt, komt hij weer terug op jouw website (passieve STS) alwaar nogmaals bekeken wordt of de gebruiker geauthenticeerd is. Indien dat het geval is, mag hij door. Dit proces is een eenmalige actie, zodat hij niet telkens voor iedere pagina opnieuw zijn credentials hoeft op te geven. Dit principe staat bekend als Single Sign-On (SSO). Afbeelding 1 geeft weer hoe de aanmeldprocedure verloopt.

Afbeelding 1. Aanmeldprocedure (bron: Windows Azure)

  1. Gebruiker bezoekt website (RP = Relaying Party, om het moeilijker te maken)
  2. Gebruiker krijgt een lijstje van ondersteunde Identity Providers (Yahoo!, Google)
  3. Gebruiker wordt doorverwezen naar de IP van zijn keuze en meldt zich aan
  4. Gebruiker krijgt zijn security token (paspoort) als de aanmelding slaagt
  5. Gebruiker keert terug bij ACS alwaar het token wordt getransformeerd
  6. Gebruiker krijgt nu een ACS-token terug waarmee hij naar de website kan
  7. Gebruiker komt geauthenticeerd terug bij de website
Bij deze laatste check zou je nu de extra claims uit de database kunnen toevoegen. Hier hebben we meteen een probleem. Om rechten aan een gebruiker te kunnen toekennen, moet je eerst zijn identifier achterhalen, en die verschilt van provider tot provider. Aan die identifier valt niets af te lezen dus moet je ook een correlatie leggen met de gebruikersnaam. Bij sommige providers wordt de naam wel meegegeven die je dan kunt gebruiken, maar bij Live ID krijg je alleen die identifier terug. Je zou iets kunnen doen met een speciale webpagina die de identifier aan de aangemelde gebruiker presenteert die hij vervolgens naar de beheerder mailt.

Het datamodel kun je weer zo makkelijk of moeilijk maken als je wilt, maar uiteindelijk zul je de binnenkomende claims moeten verrijken. Dit kun je doen door gebruik te maken van de ClaimsAuthenticationManager (voeg hiervoor eerst een referentie naar System.IdentityModel en System.IdentityModel.Services toe). ClaimsAuthenticationManager nestelt zich, net zoals ClaimsAuthorizationManager, in de request pipeline van Internet Information Service (IIS). Het enige dat je hoeft te doen, is een nieuwe class te laten erfen van ClaimsAuthenticationManager en de methode Authenticate() overschrijven. Deze methode wordt alleen bij de eerste aanmelding aangeroepen en is dus de ideale plek om de claimset te verrijken. Via het "incomingPrincipal" argument kun je de identifier van de gebruiker achterhalen en de gerelateerde data uit de database vissen om vervolgens de extra claims eraan te hangen. In de code hieronder kun je zien hoe dat in zijn werk gaat.

public class MyAuthenticationManager : ClaimsAuthenticationManager
{
    public override ClaimsPrincipal Authenticate(string resourceName, ClaimsPrincipal incomingPrincipal)
    {
        if (incomingPrincipal != null && incomingPrincipal.Identity.IsAuthenticated)
        {
            ClaimsIdentity ci = incomingPrincipal.Identity as ClaimsIdentity;
            if (ci != null)
            {
                // Add roles from database here.
                ci.AddClaim(new Claim(ClaimTypes.Role, "User"));
                ci.AddClaim(new Claim(ClaimTypes.Role, "Admin"));
            }
        }
        return incomingPrincipal;
    }
}

Vervolgens moet je de module in de IIS pipeline injecteren en dat gaat, net zoals bij de AuthorizationManager, via de web.config.

<system.identityModel>
  <identityConfiguration>
    <claimsAuthenticationManager type="MvcApplication1.Auth.MyAuthenticationManager, MvcApplication1"/>
    <claimsAuthorizationManager type="MvcApplication1.Auth.MyAuthorizationManager, MvcApplication1"/>
  </identityConfiguration>
</system.identityModel>

En dat is eigenlijk alles. 

Claims Based Identity is een erg krachtige techniek om zowel de authenticatie als autorisatie mee af te handelen, maar op zich vrij lastig. Echter, met de Identity and Access Tool wordt het al een stuk eenvoudiger en tesamen met Access Control System (ACS) een peuleschil.  

vrijdag 10 mei 2013

Autorisatie met ACS (Deel 1)

In de vorige blog heb je kunnen lezen hoe je ACS gebruiksklaar maakt voor jouw web site, zodat alleen aangemelde gebruikers op de web site kunnen komen. In dit deel gaat het over wat een gebruiker mag op jouw site, ofwel autorisatie. In principe ga je op dezelfde manier te werk als de uitsmijter bij de nachtclub, je checkt het paspoort op bepaalde kenmerken, in dit geval het geboortejaar van de bezoeker. Heeft de bezoeker de gewenste leeftijd, dan mag hij door anders wordt hij vriendelijk doch dringend verzocht op te hoepelen. Claims zijn eigenlijk de persoonskenmerken in het paspoort. Grofwef bestaat zo'n claim uit een type en een bijbehorende waarde. Een aantal veelvoorkomende claimtypes zijn:

"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier"
"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name" 
"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress"
"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/dateofbirth"
"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/gender"

Een Security Token Service (STS) kan een aantal deze claims aanleveren, maar garanties zijn er niet. Zo zal Live ID alleen de "nameidentifier" meeleveren, terwijl Google ook het email-adres en naam zal leveren.
Welke claims een STS aanlevert, kun je eenvoudig onderzoeken met de web applicatie die we in de vorige blog hebben gemaakt. We breiden de controller uit met het volgende code:

public class HomeController : Controller
{
    public ActionResult Index()
    {
        ClaimsIdentity claims = User.Identity as ClaimsIdentity;
        return View(claims);
    }
}
ClaimsIdentity komt uit de namespace System.Security.Claims, de nieuw te gebruiken namespace in .NET Framework 4.5 (was Microsoft.IdentityModel.Claims).
De bijbehorende View kan er dan als volgt uitzien:

@using System.Security.Claims
@model ClaimsIdentity

<h1>My Claims</h1>
<table>
    <thead>
        <tr>
            <th>Type</th>
            <th>Value</th>
        </tr>
    </thead>
    <tbody>
        @foreach (Claim claim in Model.Claims)
        {
            <tr>
                <td>@claim.Type</td>
                <td>@claim.Value</td>
            </tr>
        }
    </tbody>
</table>

Het resultaat voor de diverse providers is in afbeelding 1 te bewonderen. (Ooooh, wat ben ik hiervoor diep door het stof gegaan. Ik heb een Facebook account aangemaakt om dit te kunnen demonstreren :-()


Afbeelding 1. De claims, zoals die standaard door de verschillende STS's wordt aangeboden.
Deze claims zijn op zich vrij nutteloos als je een beetje autorisatie wilt bedrijven. Gelukkig kun je in ACS de claimset verrijken (de visum stempels in je paspoort) met extra claims. Om deze claims toe te voegen, ga je via de Azure Portal naar het ACS configuratiescherm. Selecteer hieruit het menu Rule Group en vervolgens een reeds bestaande Rule Group. Het leidt je naar het venster in afbeelding 2.


Afbeelding 2. Rules aanmaken.
In het "Add Claim Rule" kun je nu nieuwe claims definieren (afbeelding 3).

Afbeelding 3. Een rule aanmaken.

Het werkt als volgt: Als (If) via Facebook (Input claim issuer) een gebruiker met email-adres (Input claim type emailaddress) bla@bla.nl (Input claim value) binnenkomt, maak voor hem dan (Then) een nieuwe claim aan van type "role" (Output claim type) en geef die claim de waarde (Output claim value) "User" (Natuurlijk mag je hier alles verzinnen wat je te binnen schiet). Vergeet de Description niet, ook al is die optioneel. Wanneer je nu de applicatie weer start, zie je het volgende (afbeelding 4)


Afbeelding 4. Het resultaat van de claimverrijking. De claim "role" met waarde "User" is erbij gekomen.
We zouden nu in de MVC Action die claims kunnen uitvlooien en op basis daarvan besluiten of de gebruiker erin mag, maar WIF heeft iets veel mooiers hiervoor. Je kunt vrij eenvoudig een autorisatiemodule schrijven die zich in de request pipeline van IIS nestelt. Het komt erop neer dat je een nieuwe class erft van de class ClaimsAuthorizationManager (voeg hiervoor eerst een referentie naar System.IdentityModel en System.IdentityModel.Services toe) en overschrijf de CheckAccess methode. Deze methode retourneert "true" wanneer de aan een bepaalde conditie is voldaan, in dit geval de aanwezigheid van een bepaalde claim en "false" wanneer de gebruiker niet door mag gaan.

public class MyAuthorizationManager : ClaimsAuthorizationManager
{
    public override bool CheckAccess(AuthorizationContext context)
    {
        string requestedResource = context.Resource.First().Value;

        bool hasAccess = context.Principal.HasClaim(ClaimTypes.Role, requestedResource);
        return hasAccess;
     }
}

Deze class moeten we vervolgens in de request pipeline pluggen. Dat gaat via de web.config

<system.identityModel>
  <identityConfiguration>
    <claimsAuthorizationManager type="MyAuthorizationManager, MvcApplication1"/>
  </identityConfiguration>
</system.identityModel>

Tenslotte is het nog een kwestie van MVC Actions voorzien van het ClaimsPrincipalPermission attribuut. Let hierbij op het Resource attribuut. Dit attribuut wordt in de ResourceManager gebruikt om de bezoeker te testen.

[HandleError(View="Permissions")]
public class HomeController : Controller
{
    [ClaimsPrincipalPermission(SecurityAction.Demand, Operation="Open", Resource = "User")]
    public ActionResult Index()
    {
        ClaimsIdentity claims = User.Identity as ClaimsIdentity;
        return View(claims);
    }
    [ClaimsPrincipalPermission(SecurityAction.Demand, Operation = "Open", Resource = "Admin")]
    public ActionResult AdminSite()
    {
        return View();
    }
}

En dat is dan alles. De rest is claims instellen in Azure (alsof het niks is).

Dit is voor kleine organisaties met heel weinig mensen nog wel te doen, maar voor iets grotere bedrijven is dit eigenlijk een gebed zonder einde. In dat geval is het wellicht handiger om ADFS als STS in te zetten. Soms kom je ook oplossingen tegen waarbij men eigen STS-services gaat schrijven. Vaak wordt er dan een kopie van zo'n custom STS, zoals die WIF 3.5 en 4.0 werd bijgeleverd, gemaakt. Niet doen. Die custom STS is niet bedoeld voor productie-omgevingen. In Visual Studio 2012 zijn die templates dan ook verdwenen. In het volgende artikel zal ik aangeven hoe je de claimset (paspoort), die je terugkrijgt van ACS, kunt verrijken met informatie uit jouw database (het plaatsen van de visumstempels).

vrijdag 3 mei 2013

Windows Identity Foundation met ACS

Wie denkt dat BesteProduct alleen maar wat prijzen en reviews ophoest, heeft het helemaal mis. Naast ontwikkelaars, die de site uitbreiden en onderhouden, is de groep invoerders continu bezig om producten actueel te houden. Deze groep is ook verantwoordelijk voor het doorspitten van honderden tijdschriften uit alle hoeken van de wereld, op zoek naar tests. Deze testresultaten worden via een invoerapplicatie in de database opgeslagen. Verder is er nog een legertje redacteuren aanwezig die artikelen schrijven, filmpjes maken en zelf producten testen.
Redacteuren zijn een slag apart. Ze bedienen zich van allerlei hippe apparaten, begeven zich op de meest uiteenlopende sociale netwerken en zijn luidruchtig. Door die sociale media hebben ze veel accounts en dus veel wachtwoorden te onthouden. Voor het invoersysteem komt daar nog een account extra bij en dat werd net teveel van het goede. Vandaar dat besloten is om het aanmelden via de bestaande accountdiensten (Live ID, Google, Facebook) te laten verlopen. Bijkomend voordeel is de beveiliging. Dat moet je zelf niet willen doen. Nadeel is dat al die diensten weer verschillende protocollen hanteren waardoor het weer te bewerkelijk wordt. Hier komt Access Control System (ACS) om de hoek kijken. Dit is een dienst van het Windows Azure platform dat de data van al die verschillende aanmelddiensten transformeert naar een uniforme set (claims).
Om goed om te kunnen gaan met ACS is het dus van belang dat je weet wat Claim Based Identity inhoudt. Claim Based Identity laat zich het best vergelijken met een paspoort. Hierin staat bijvoorbeeld jouw naam, geboortedatum en sofi-nummer (claims). Om vervalsingen tegen te gaan, wordt zo'n paspoort uitgegeven door een geautoriseerde dienst (issuer). In ons geval de overheid. Wanneer je nu een nachtclub in wilt, kan de uitsmijter op basis van jouw geboortedatum (claim) besluiten of hij jou al dan niet binnenlaat. Ook andere landen kunnen onze overheid vertrouwen waardoor het paspoort in het buitenland ook bruikbaar is. Men spreekt dan van een zogenaamde "trust" relatie. Wanneer die trust er niet of gedeeltelijk is, zul je naar een ambassade moeten alwaar jouw paspoort met een visum (claim) wordt verrijkt.
Met Claim Based Security werkt het eigenlijk hetzelfde. De issuer is dan Live ID, Google of Yahoo!. Welke claims je krijgt, hangt van de issuer af, maar in ieder geval krijg je een unieke identifier. Afhankelijk van de provider kun je ook het email-adres of de naam van de gebruiker terugkrijgen.

Om van ACS gebruik te kunnen maken, moeten we eerst een namespace hiervoor definiëren. Daarvoor ga je eerst naar de Azure Portal (afbeelding 1). Van daaruit ga je naar het menu "Active Directory" en het submenu "Access Control Namespaces".


Afbeelding 1. Active Directory window in de Azure Portal.
Vervolgens selecteer je "Create New Namespace" hetgeen leidt tot het scherm in afbeelding 2. Hierin geef je de namespace op en de locatie van het datacenter waar die aangemaakt wordt.


Afbeelding 2. Aanmaken van de namespace.
Wanneer dit alles geslaagd is, zie je in het Active Directory scherm (na enige tijd) een "Manage" knop verschijnen (afbeelding 3). Deze knop leidt naar het ACS-configuratiescherm (afbeelding 4).


Afbeelding 3. Na enige tijd verschijnt de "Manage" knop onderin beeld.

Afbeelding 4. Het ACS-configuratiescherm.
Het ACS-configuratiescherm schrijft vier stappen voor die je moet doen om jouw web applicatie gebruik te laten maken met ACS:
  1. Identity Providers toekennen. Identity Providers zijn bijvoorbeeld Live ID, Google, Facebook, Yahoo! en Active Directory Federation Services (ADFS) (afbeelding 5).
  2. Relying Party Applications instellen. Hier moet je het adres van de website, waarvoor je ACS wilt gebruiken, opgeven. Daarnaast kun je eigenschappen van het security token aanpassen, welke Identity Providers je wilt gebruiken en welke claims van de Identity Providers je wilt gebruiken.
  3. Rule Groups. Hierin kun je instellen welke claims van de Identity Providers je wilt gebruiken. Daarnaast kun je de claims verrijken met extra claims (de visum stempels).
  4. (Optioneel) kun je hier instellen hoe ACS zich in jouw applicatie zal gedragen.
Afbeelding 5. Aangeven welke identity providers je wilt gebruiken.
Wanneer je de stappen 1, 2 en 3 hebt doorlopen, kun je aan jouw web applicatie gaan werken. Daarvoor kun je het best de Windows Identity Foundation (WIF) SDK installeren. WIF komt voor .NET Framework versies 3.5 en 4. Voor Visual Studio 2012 en het .NET Framework 4.5, waar het grootste deel van WIF in is geïntegreerd, moet je de Identity and Access Tool installeren en kun je stappen 2 en 3 in het ACS-configuratiescherm overslaan. In deze blog ga ik verder uit van .NET Framework 4.5 en Visual Studio 2012.

Nadat je de Identity en Access Tool hebt geïnstalleerd, kun je een web applicatie gaan bouwen. In afbeelding 6 is een nagenoeg kale MVC 4 web applicatie in gereedheid gebracht.


Afbeelding 6. Een basic ASP.NET MVC 4 web application.
Om nu van ACS gebruik te kunnen maken, selecteer je "Identity and Access..." uit het contextmenu van het project welke leidt tot de configuratieschermen in afbeelding 7.


Afbeelding 7. ACS configureren.
In het eerste scherm geef je aan dat je gebruik wilt maken van het Windows Azure Access Control System. Vervolgens ga je naar "Configure..." hetgeen het tweede scherm start. Hierin geef je de ACS-namespace op en de access key. Die access key vind je in het ACS-configuratiescherm (afbeelding 8). Tenslotte geef je aan welke logindiensten je wilt aanbieden (afbeelding 7, laatste scherm) en ACS is klaar voor gebruik.


Afbeelding 8. Management key ophalen.
Optioneel kun je op het tabblad "Configuration" aangeven hoe omgegaan moet worden met ongeldige credentials.

Wanneer je de web applicatie start, zal eerst een keuzemenu getoond worden en vervolgens het loginscherm (afbeelding 9).


Afbeelding 9. Kies welke identity provider je wilt hebben en meld je aan (Hiervoor heb ik schoorvoetend een Facebook account aangemaakt)
Wanneer je jouw credentials hebt opgegeven, zul je eenmalig een melding van de aanmelddienst krijgen dat ACS jouw gegevens gaat gebruiken. Wanneer je dat toestaat, beland je op de site (afbeelding 10).


Afbeelding 10. Waarschuwingsvenster van Google en de uiteindelijke website.
Het opzetten van ACS en het gebruik daarvan met behulp van WIF is ogenschijnlijk vrij eenvoudig, maar nu heb je een web site die alleen gebruikers met een geldige login toelaat. In de praktijk zul je ook willen bepalen wat die aangemelde gebruikers allemaal mogen. Dan komen we op het gebied van de  autorisatie en dat is een verhaal apart. Daarover een volgende keer meer.