Windows CardSpace med ASP.NET MVC
Jag har tidigare skrivit några artiklar om bl.a. hur man kommer igång med Windows Cardspace och hur man använder det med Javascript. De har dock tagit upp hur det fungerar med ASP.NET Web Forms, men för att få det att fungera med ASP.NET MVC på serversidan så måste man tänka lite annorlunda.
Det vi ska göra är att använda standardsajten som följer med ASP.NET MVC och bygga ut den med CardSpace-stöd. Den har från början inbyggt stöd för ASP.NET Membership, och den vill vi helt enkelt bara bygga ut. När en person har registrerat sig och loggat in så skall vi göra det möjligt för denne att associera ett InfoCard med sitt konto. Efter det skall det vara möjligt för användaren att logga in på sidan genom att endast skicka in sitt InfoCard.
Då det redan finns funktionalitet för att skapa användare och låta dem logga ut på sidan så behöver vi inte ta med det nu, men vi behöver däremot skapa en ny Controller som jag i exemplet kallar för CardSpaceController. Vi behöver även en mapp under Views med namn CardSpace.
Controllern skall ha action-metoder för att logga in samt associera ett InfoCard med kontot. Jag tog grunden av koden från den befintliga AccountController-klassen för att få alla nödvändiga properties, men har skrivit om den för att få den här strukturen:
[HandleError]
public class CardSpaceController : Controller
{
public CardSpaceController() : this(null, null) { }
public CardSpaceController(IFormsAuthentication formsAuth, IMembershipService service)
{
FormsAuth = formsAuth ?? new FormsAuthenticationService();
MembershipService = service ?? new AccountMembershipService();
}
public IFormsAuthentication FormsAuth
{
get;
private set;
}
public IMembershipService MembershipService
{
get;
private set;
}
public ActionResult LogOn()
{
//Visa vy för inloggning
}
[AcceptVerbs(HttpVerbs.Post)]
[ValidateInput(false)]
public ActionResult LogOn(string xmlToken)
{
//Använda det inskickade xmlToken för att hämta den användaren som har detta.
}
public ActionResult AssociateInfoCardWithAccount()
{
//Visa en sida där användaren kan associera kontot genom att klicka på en knapp.
}
[AcceptVerbs(HttpVerbs.Post)]
[Authorize]
[ValidateInput(false)]
public ActionResult AssociateInfoCardWithAccount(string token)
{
//Spara token för det aktuella kontot
}
}
Det vi har här är två olika typer av action-metoder, vi har en för att visa en inloggningssida samt genomföra inloggningen, samt en för att visa associeringssidan samt associera kontot.
Då det är en XML-fil som skickas genom Windows CardSpace så kommer ValidateRequest att sätta stopp för detta. Med ASP.NET Web Forms så fick vi då sätta validateRequest till false i antingen page-direktivet eller i page-elementet i web.config, men då vi kör ASP.NET MVC här så fungerar det annorlunda. Då man kan nå en aspx-fil genom flera olika action-metoder, samtidigt som en action-metod kan rendera olika vyer så kan man inte längre sätta detta. Istället så får vi använda ValidateInput(false), vilket gör detsamma.
I de metoder där vi skickar in data har vi även satt AcceptVerbs så att bara postningar kan komma till dessa metoder. Om det är ett get-anrop så visar vi istället formulärsvyerna.
Vi vill även att man bara skall kunna associera ett konto om man är inloggad, så på de metoderna har vi Authorize-attributet.
För att hämta ut det PPID som skickas med i XML-filen så kommer jag att använda samma metoder som jag har med i tidigare artiklar.
För att logga in så hämtar jag i LogOn-metoden (den som man postar till) en Xml-token och hämtar därifrån e-postadress samt PPID. Sedan kollar jag om det finns någon användare med det PPID:t och loggar då in med denne.
[AcceptVerbs(HttpVerbs.Post)]
[ValidateInput(false)]
public ActionResult LogOn(string xmlToken)
{
XmlDocument doc = new XmlDocument();
doc.LoadXml(Request.Form["xmlToken"]);
string email = doc.SelectSingleNode("/saml:Assertion/saml:AttributeStatement/saml:Attribute[@AttributeName='emailaddress']", GetNamespaces(doc)).InnerText;
string ppid = doc.SelectSingleNode("/saml:Assertion/saml:AttributeStatement/saml:Attribute[@AttributeName='privatepersonalidentifier']", GetNamespaces(doc)).InnerText;
MembershipUserCollection users = Membership.FindUsersByEmail(email);
foreach (MembershipUser mu in users)
{
if (mu.Comment != null && mu.Comment.Equals(ppid))
FormsAuthentication.RedirectFromLoginPage(mu.UserName, false);
}
return View();
}
Om användaren inte kan loggas in så visas återigen LogOn-vyn.
För att associera ett InfoCard med ett konto så sparar jag undan PPID:t i användarens kommentarsfält. Detta leder till att man bara kan ha ett InfoCard associerat till kontot, men det är lätt att bygga ut.
[AcceptVerbs(HttpVerbs.Post)]
[Authorize]
[ValidateInput(false)]
public ActionResult AssociateInfoCardWithAccount(string token)
{
MembershipUser user = Membership.GetUser();
user.Comment = GetPpid();
Membership.UpdateUser(user);
return RedirectToAction("Index", "Home");
}
När användaren har associerat kontot så kommer man återigen till startsidan.
Det här är ett enkelt exempel på hur man kan låta användaren logga in på en ASP.NET MVC-sida med ett InfoCard istället för användarnamn och lösenord. Just det här exemplet kräver att man kör utan SSL, vilket man aldrig bör göra. Om man har SSL aktiverat så kommer dock XML-filen att krypteras. Koden bör inte användas i produktion, utan är som sagt bara en demonstration på hur det kan fungera.
All kod med vyerna inkluderade finns att ladda ned här:
http://cid-4aa13e17331c7398.skydrive.live.com/self.aspx/Public/ASP.NET/MvcCardSpace.rar