Autentisering mot ASP.NET Membership-tjänst från Silverlight

EDIT: la till länkar till live-tester av demos samt en länk till ASP.NET AJAX-dokumentationen. 

Ett scenario som kommer att bli allt vanligare i takt med att fler och fler verksamhetsstödjande system börjar utvecklas med Silverlight är anrop från klienten mot webbtjänster som exponeras via IIS.

Med hjälp av Client Access-policy-filer kan du styra vilka klienter som kan anropa tjänsten, baserat på vilken domän som klienten härstammar ifrån. Men naturligtvis finns även behovet att skydda vissa tjänster från anonymt användande med krav på någon form av autentisering för att komma åt dem. Att via roller kunna styra vilka operationer i tjänsten som går att anropa - baserat på vilken användare som loggat in - kan vara ett annat krav.

I den här artikeln kommer jag visa på tre olika alternativ för autentisering mot ASP.NET:s inbyggda Membership-tjänst. Det är tre snarlika alternativ som bygger på lite olika förutsättningar i vad server-miljön erbjuder. För alla alternativen gäller förutsättningen att Silverlight-applikationen härstammar från samma domän som ASP.NET-applikationen.

Silverlight använder webbläsarens nätverksstack för att göra alla anrop och har inte någon egen hantering av cookies. Det innebär att när en webbplats sätter en cookie som säger att en användare är inloggad, t.ex. en Forms-authentication cookie från ASP.NET, så kommer cookien även automatiskt att skickas med när Silverlight gör ett webbservice-anrop mot ASP.NET-applikationen. I alla tre exempel nedan används denna funktionalitet för att möjliggöra anrop mot en behörighetsskyddad tjänst efter att användaren autentiserats mot ASP.NET Membership-tjänst. Jag har utgått ifrån Silverlight 2 Beta 2 i alla exempel.

Alternativ 1. Autentisering via vanligt HTML-formulär (Login-kontrollen) mot ASP.NET.

Det här alternativet är egentligen det som är minst integrerat med själva Silverlight-applikationen - men kan ändå vara gångbart i vissa fall. Främst så är det ett alternativt där inte något annat än ASP.NET 2.0 krävs på serversidan, vilket kan vara förutsättningen t.ex. i  ett hostat scenario.

Ungefär så här ser min testsida ut:

clip_image001[4]

Principen är oerhört enkel - jag har lagt till en Service-referens i Silverlight-applikationen till en enkel HelloWorld-liknande ASMX-tjänst i ASP.NET-applikationen. I ASP.NET-applikationens root Web.config har jag slagit på Forms-authentication och satt authorization till att endast tillåta användaren 'test':

<authentication mode="Forms">

<forms loginUrl="WebForm1.aspx" defaultUrl="WebForm1.aspx"/>

</authentication>   

<authorization>

  <deny users="?"/>

  <allow users="test"/>

</authorization>

 

För att Silverlight-applikationen ska vara möjlig att hämta till klienten utan att vara inloggad från början har jag även lagt en Web.config i min ClientBin-katalog med följande authorization-element:

<authorization>

<allow users="?" />

</authorization>

 

När användaren klickar på 'Anropa WS'-knappen utan att vara inloggad kastas ett exception (en "404 Not Found" returneras från ASP.NET). När användaren loggat in kommer anropet att gå igenom. Vill du testa applikationen kan du göra det här. ASP.NET-användaren har user: test, passw: Pass_word1

Naturligtvis vore det bättre att t.ex. gråa ut Silverlight-knappen och notifiera ifall användaren inte är inloggad, detta skulle enkelt vara möjligt genom att t.ex. skicka in startparametrar till Silverlight-applikationen från ASP.NET i object-taggen - ex:

<param name="initParams" value="loggedin=false" />

och sedan göra en metod i Silverlight anropningsbar från Javascript som visar när användaren loggat in.

Tim Heuer har skrivit en bra artikel som (bland annat) beskriver start-parametrar i Silverlight 2. Men - som sagt - denna lösning för autentisering är ju egentligen inte integrerad med Silveright, även om det går att få till - lite yxigt dock. Så låt oss titta på ett något elegantare alternativ.

Alternativ 2. Autentisering via ASP.NET AJAX mot ASP.NET.

Det här alternativet kräver ASP.NET AJAX version 1.0 (eller senare - fast kör du ASP.NET 3.5 så är alternativ 3 nedan förmodligen att föredra). I det här fallet har jag lagt in ett inloggningsformulär i Silverlight-applikationen som kommunicerar med ASP.NET Membership-tjänst via Javascript - som i sin tur använder stödet i ASP.NET AJAX klientscriptbibliotek för att sköta autentisering mot tjänsten.

OBS! - kontrollen för inmatning av lösenord är lite utav ett hack som jag lånat från Brad Abrams - använd inte den i en produktionslösning eftersom den är smått buggig (ibland visar den t.ex. klartext istället för stjärn-tecken...)

I exemplet har jag också använt ASP.NET AJAX 3.5 eftersom jag inte ville installera ASP.NET AJAX 1.0 separat - om du vill använda ASP.NET AJAX 1.0 i Visual Studio 2008 så kolla in Scott Guthries post här.

Konfigureringsmässigt skiljer sig inte denna lösning åt från alternativ 1, annat än att jag har lagt till ett authenticationService-element i Web.config:

<system.web.extensions>

<scripting>

<webServices>

<authenticationService enabled="true" />

</webServices>

</scripting>

</system.web.extensions>

I princip har jag använt lösningen som finns beskriven här för att anropa ASP.NET med hjälp AJAX-klientscriptbibliotek.

I Silverlight används sedan System.Windows.Browser.HtmlPage.Window.Invoke för att anropa javascript för inloggning. När svaret kommer tillbaka från ASP.NET authenticationService görs en alert i sidan för att visa resultatet. Vill du testa alternativ 2 kan du göra det här. ASP.NET-användaren har user: test, passw: Pass_word1

Alternativ 3. Autentisering via WCF mot ASP.NET.

I och med .NET Framework 3.5 och ASP.NET 3.5 har vi fått möjligheten att anropa ASP.NET's autentiseringstjänst direkt via en exponerad WCF-tjänst. Med ASP.NET 3.5 kan vi alltså autentisera användare genom ett anrop direkt ifrån Silverlight (eller en Windows-klient), utan att behöva gå via Javascript och AJAX. Detta alternativ är naturligtvis det mest eleganta av de tre - och det som du bör satsa på ifall du kan köra ASP.NET 3.5 på din webbserver.

Jag har i princip modifierat lösningen som jag använt för de två tidigare exemplen genom att följa Brad Abrams post i ämnet här. Det är också därifrån jag lånat kontrollen som hanterar inmatning av lösenord eftersom Silverlight än så länge inte har någon inbyggd sådan. För att autentiseringstjänsten ska vara nåbar utan att först behöva logga in (Duh! :-)  så har jag lagt AuthenticationService.svc i mitt ClientBin-bibliotek.

Vill du testa alternativ 3 kan du göra det här. ASP.NET-användaren har user: test, passw: Pass_word1

...

Så - för att sammanfatta de alternativ jag visat

  1. Inloggning med "vanliga" ASP.NET login-kontrollen (fungerar med ASP.NET 2).
  2. Inloggning med hjälp av ASP.NET AJAX (fungerar med ASP.NET AJAX 1.0)
  3. Inloggning med hjälp av WCF-autentiseringsservice (fungerar med ASP.NET 3.5)

Du kan ladda hem källkoden för alla alternativ här.