{"id":57,"date":"2011-12-25T13:17:00","date_gmt":"2011-12-25T13:17:00","guid":{"rendered":"http:\/\/tpodolak.com.hostingasp.pl\/blog\/2011\/12\/25\/silverlight-koncepcja-logowania-z-uzyciem-membershipproviders-oraz-wcf-ria-cz-2\/"},"modified":"2016-01-31T00:24:45","modified_gmt":"2016-01-31T00:24:45","slug":"silverlight-koncepcja-logowania-z-uzyciem-membershipproviders-oraz-wcf-ria-cz-2","status":"publish","type":"post","link":"https:\/\/tpodolak.com\/blog\/2011\/12\/25\/silverlight-koncepcja-logowania-z-uzyciem-membershipproviders-oraz-wcf-ria-cz-2\/","title":{"rendered":"Silverlight &#8211; koncepcja logowania z u\u017cyciem MembershipProviders oraz WCF RIA cz.2"},"content":{"rendered":"<p>W poprzednim wpisie przedstawi\u0142em w jaki spos\u00f3b zaimplementowa\u0107 mechanizm logowania po stronie serwera. Wykorzysta\u0142em do tego celu WCF RIA oraz znane z ASP MembershipProvidery. Tym razem przedstawi\u0119 jak wymusi\u0107 logowanie po stronie klienta, oraz w jaki spos\u00f3b mo\u017cna dynamicznie zmienia\u0107 provider\u00f3w, kt\u00f3rzy waliduj\u0105 usera.<br \/>\nPoprzednim razem stworzyli\u015bmy ju\u017c szkielet aplikacji, zatem mamy projekt klienta oraz projekt serwera. Zacznijmy od &#8220;w\u0142\u0105czenia&#8221; <span style=\"font-style: italic;\">FormsAuthentication<\/span> po stronie klienta. W pliku App.xaml.cs , w konstruktorze dorzu\u0107my nast\u0119puj\u0105ce linijki<\/p>\n<pre lang=\"csharp\">\r\nWebContext context = new WebContext {Authentication = new FormsAuthentication()};\r\nApplicationLifetimeObjects.Add(context);\r\n<\/pre>\n<p>Nast\u0119pnie zanim poka\u017cemy tre\u015b\u0107 naszej strony, musimy sprawdzi\u0107 czy u\u017cytkownik jest zalogowany.Je\u017celi nie, nale\u017cy pokaza\u0107 okno logowania. Mo\u017cemy to zrobi\u0107 w nast\u0119puj\u0105cy spos\u00f3b.<\/p>\n<pre lang=\"csharp\">\r\nif (!WebContext.Current.User.IsAuthenticated)\r\n{\r\n    ModalDialogService.ShowDialog(_unityContainer.Resolve<IModalView<LoginViewModel>>(),\r\n                                  val => { InitializeView();GetSecureData(); });\r\n}\r\nelse\r\n{\r\n    InitializeView();\r\n    GetSecureData();\r\n}\r\n<\/pre>\n<p>WebContext jest to klasa generowana automatycznie przy buildowaniu solucji.Jej kod powinni\u015bmy odnale\u017a\u0107 w katalogu Generated_Code po stronie klienta. Klasa ta posiada w\u0142a\u015bciwo\u015b\u0107 Current, kt\u00f3ra zwraca obecny context zarejestrowany w kolekcji ApplicationLifetimeObjects.<br \/>\nOkno logowania zosta\u0142o zaimplementowane jako ChildWindow. Jego kod mo\u017ce wygl\u0105da\u0107 w nast\u0119puj\u0105cy spos\u00f3b<\/p>\n<pre lang=\"xml\">\r\n<Grid x:Name=\"LayoutRoot\" Margin=\"2\">\r\n        <Grid.RowDefinitions>\r\n            <RowDefinition \/>\r\n            <RowDefinition Height=\"Auto\" \/>\r\n        <\/Grid.RowDefinitions>\r\n        <StackPanel VerticalAlignment=\"Center\">\r\n            <sdk:Label Content=\"Login\"><\/sdk:Label>\r\n            <TextBox Text=\"{Binding Login,NotifyOnValidationError=True,Mode=TwoWay}\" >\r\n               \r\n            <\/TextBox>\r\n            <sdk:Label Content=\"Has\u0142o\"<\/sdk:Label>\r\n            <PasswordBox  Password=\"{Binding Password,NotifyOnValidationError=True,Mode=TwoWay}\" >\r\n            <\/PasswordBox>\r\n        <\/StackPanel>\r\n        <Button x:Name=\"CancelButton\" Content=\"Cancel\" Click=\"CancelButton_Click\"  Width=\"75\" Height=\"23\" HorizontalAlignment=\"Right\" Margin=\"0,12,0,0\" Grid.Row=\"1\" \/>\r\n        <Button x:Name=\"OKButton\" Content=\"OK\" Command=\"{Binding OkCommand}\" Width=\"75\" Height=\"23\" HorizontalAlignment=\"Right\" Margin=\"0,12,79,0\" Grid.Row=\"1\" \/>\r\n<\/Grid>\r\n<\/pre>\n<p>Kontrolka logowania jest bardzo prosta, sk\u0142ada si\u0119 ona z dw\u00f3ch labelek, textboxa oraz passwordBoxa. Powy\u017cszy kod powinien wygenerowa\u0107 mniej wi\u0119cej takie oto okno<br \/>\n<a href=\"http:\/\/tpodolak.com\/blog\/wp-content\/uploads\/2011\/12\/silverlight-koncepcja-logowania-z-uzyciem-membershipproviders-oraz-wcf-ria-cz-2\/login.jpg\" rel=\"attachment wp-att-462\"><img decoding=\"async\" src=\"http:\/\/tpodolak.com\/blog\/wp-content\/uploads\/2011\/12\/silverlight-koncepcja-logowania-z-uzyciem-membershipproviders-oraz-wcf-ria-cz-2\/login.jpg\" alt=\"WCF RIA\" width=\"400\" class=\"aligncenter size-full wp-image-462\" srcset=\"https:\/\/tpodolak.com\/blog\/wp-content\/uploads\/2011\/12\/silverlight-koncepcja-logowania-z-uzyciem-membershipproviders-oraz-wcf-ria-cz-2\/login.jpg 400w, https:\/\/tpodolak.com\/blog\/wp-content\/uploads\/2011\/12\/silverlight-koncepcja-logowania-z-uzyciem-membershipproviders-oraz-wcf-ria-cz-2\/login-150x111.jpg 150w, https:\/\/tpodolak.com\/blog\/wp-content\/uploads\/2011\/12\/silverlight-koncepcja-logowania-z-uzyciem-membershipproviders-oraz-wcf-ria-cz-2\/login-300x223.jpg 300w\" sizes=\"(max-width: 400px) 100vw, 400px\" \/><\/a><br \/>\nMechanizm wywo\u0142ywania operacji logowania znajduje si\u0119 natomiast w ViewModelu kontrolki logowania. Po naci\u015bni\u0119ciu przycisku OK, dzi\u0119ki zastosowaniu <span style=\"font-style: italic;\">DelegateCommand <\/span> uruchamiany nast\u0119puj\u0105c\u0105 funkcj\u0119<\/p>\n<pre lang=\"csharp\">\r\nprivate void OkCommandExecuted()\r\n{\r\n    if (Validate())\r\n    {\r\n        PerfomrLoginOperation();\r\n    }\r\n}\r\n<\/pre>\n<p>W funkcji tej najpierw sprawdzamy, czy u\u017cytkownik poda\u0142 dane do logowania, a nast\u0119pnie wywo\u0142ujemy kolejn\u0105 funkcj\u0119 <span style=\"font-style: italic;\">PerfomrLoginOperation<\/span>. Przedstawia si\u0119 w nast\u0119puj\u0105cy spos\u00f3b<\/p>\n<pre lang=\"csharp\">\r\nprivate void PerfomrLoginOperation()\r\n{      \r\n    WebContext.Current.Authentication.Login(new LoginParameters(Login, Password, false,string.Empty, CompleteLoginOperation, Guid);\r\n}\r\n<\/pre>\n<p>Widzimy zatem, \u017ce wywo\u0142ujemy funkcj\u0119 logowania z serwisu WCF, w parametrach natomiast podajemy login oraz has\u0142o przechwycone z okna. Dodatkowo w parametrze podajemy delegata do funkcji, kt\u00f3ra zostanie wykonana po zako\u0144czeniu operacji logowania.<\/p>\n<pre lang=\"csharp\">\r\nprivate void CompleteLoginOperation(LoginOperation loginOperation)\r\n{\r\n    if (loginOperation.Error == null)\r\n        OnOperationCompleted();\r\n    else\r\n    {\r\n        if (loginOperation.Error is FormsAuthenticationLogonException)\r\n            MessageBoxService.ShowMessageBox(\"Nieprawid\u0142owy login lub has\u0142o\", MessageBoxButton.OK);\r\n        else\r\n            MessageBoxService.ShowMessageBox(\"Wyst\u0105pi\u0142 problem podczas operacji logowania\",\r\n                                             MessageBoxButton.OK);\r\n    }\r\n}\r\n<\/pre>\n<p>Je\u017celi odpowied\u017a z serwisu WCF zawiera b\u0142\u0119dy to wiemy, \u017ce operacja logowania si\u0119 nie powiod\u0142a. Wy\u015bwietlamy zatem stosowny komunikat. Je\u017celi natomiast wszystko jest Ok, pole <span style=\"font-style: italic;\">loginOperation.Error<\/span> b\u0119dzie mia\u0142o warto\u015b\u0107 null. Mo\u017cemy wtedy pokaza\u0107 nasz\u0105 wiadomo\u015b\u0107 dost\u0119pn\u0105 jedynie dla zalogowanych u\u017cytkownik\u00f3w.<br \/>\nMamy zatem ochron\u0119 przeciwko nieautoryzowanemu dost\u0119powi do klienta. Jednak co w przypadku gdy nasz klient musi w calach np. zassania danych pobra\u0107 je przy wykorzystaniu jakiego\u015b WebServicu. Na og\u00f3l wystawiany jest wtedy drugi WebService do pobierania danych z jakiej\u015b bazy. Do takiego WebServicu w teorii mo\u017ce dosta\u0107 si\u0119 ka\u017cdy &#8211; my natomiast chcemy \u017ceby dane mo\u017cna by\u0142o pobiera\u0107 jedynie po zalogowaniu si\u0119 do aplikacji. Na szcz\u0119\u015bcie nasz dodatkowy WebServise mo\u017cna w prosty spos\u00f3b zabezpieczy\u0107 przed dost\u0119pem os\u00f3b trzecich. Stw\u00f3rzmy zatem sobie nowy WebService s\u0142u\u017c\u0105cy do pobierania danych. Do projektu Webowego dodajmy now\u0105 klas\u0119 typu &#8220;Domain Service&#8221; oraz dopiszmy do nie jedn\u0105 metod\u0119 symuluj\u0105c\u0105 pobieranie danych.<\/p>\n<pre lang=\"csharp\">\r\n[EnableClientAccess()]\r\npublic class DataAccesService : DomainService\r\n{\r\n    public string GetSecureData()\r\n    {\r\n        return \"very secure data\";\r\n    }\r\n}\r\n<\/pre>\n<p>Do takiego Servicu mo\u017ce dosta\u0107 si\u0119 ka\u017cdy. Je\u017celi natomiast nasz\u0105 klas\u0119 udekorujemy atrybutem <span style=\"font-style: italic;\">[RequiresAuthentication()]<\/span> tylko u\u017cytkownicy, kt\u00f3rzy pomy\u015blnie przeszli autentykacj\u0119 przez nasz CustomAuthenticationService b\u0119d\u0105 w stanie pobra\u0107 dane. W przeciwnym wypadku zostanie rzucony wyj\u0105tek.<br \/>\n<a href=\"http:\/\/tpodolak.com\/blog\/wp-content\/uploads\/2011\/12\/silverlight-koncepcja-logowania-z-uzyciem-membershipproviders-oraz-wcf-ria-cz-2\/debugger.jpg\" rel=\"attachment wp-att-461\"><img decoding=\"async\" src=\"http:\/\/tpodolak.com\/blog\/wp-content\/uploads\/2011\/12\/silverlight-koncepcja-logowania-z-uzyciem-membershipproviders-oraz-wcf-ria-cz-2\/debugger.jpg\" alt=\"debugger\" width=\"800\" class=\"aligncenter size-full wp-image-461\" srcset=\"https:\/\/tpodolak.com\/blog\/wp-content\/uploads\/2011\/12\/silverlight-koncepcja-logowania-z-uzyciem-membershipproviders-oraz-wcf-ria-cz-2\/debugger.jpg 1226w, https:\/\/tpodolak.com\/blog\/wp-content\/uploads\/2011\/12\/silverlight-koncepcja-logowania-z-uzyciem-membershipproviders-oraz-wcf-ria-cz-2\/debugger-150x39.jpg 150w, https:\/\/tpodolak.com\/blog\/wp-content\/uploads\/2011\/12\/silverlight-koncepcja-logowania-z-uzyciem-membershipproviders-oraz-wcf-ria-cz-2\/debugger-300x78.jpg 300w, https:\/\/tpodolak.com\/blog\/wp-content\/uploads\/2011\/12\/silverlight-koncepcja-logowania-z-uzyciem-membershipproviders-oraz-wcf-ria-cz-2\/debugger-1024x266.jpg 1024w\" sizes=\"(max-width: 1226px) 100vw, 1226px\" \/><\/a><\/p>\n<p>Zastan\u00f3wmy si\u0119 teraz w jaki spos\u00f3b mo\u017cna by\u0142oby logowa\u0107 si\u0119 do naszej aplikacji np. przy pomocy konta Google. Zacznijmy od tego, \u017ce musimy stworzy\u0107 w\u0142asny <span style=\"font-style: italic;\">MembershipProvider<\/span>. W tym celu dodajmy do projektu Webowego now\u0105 klas\u0119 &#8220;<span style=\"font-style: italic;\">GoogleMembershipProvider<\/span>&#8220;, kt\u00f3ra b\u0119dzie rozszerza\u0107 klas\u0119 <span style=\"font-style: italic;\">MembershipProvider<\/span>. Klasa MembershipProvider jest klas\u0105 abstrakcyjn\u0105 zatem musimy zoverridowa\u0107 wszystkie jej funkcje (nie musimy wrzuca\u0107 tam logiki, narazie po prostu wpiszmy tam cokolwiek aby przesz\u0142a kompilacja). Maj\u0105c stworzony szkielet providera musimy go dorzuci\u0107 do listy wszystkich provider\u00f3w (tak jak to zrobili\u015bmy z SqlMembershipProvider-em). Modyfikujemy zatem sekcj\u0119 podsekcj\u0119 providers z sekcji membership na nast\u0119puj\u0105c\u0105.<\/p>\n<pre lang=\"xml\">\r\n<membership\r\n            defaultProvider=\"PremiumHandsMembershipProvider\"\r\n            userIsOnlineTimeWindow=\"20\">\r\n      <providers>\r\n        <clear\/>\r\n        <add name=\"PremiumHandsMembershipProvider\"\r\n             connectionStringName=\"aspnetdbConnectionString\"\r\n            type=\"System.Web.Security.SqlMembershipProvider\"\r\n            enablePasswordRetrieval=\"false\"\r\n            enablePasswordReset=\"false\"\r\n            requiresQuestionAndAnswer=\"true\"\r\n            passwordFormat=\"Hashed\"\r\n            applicationName=\"\/\" \/>\r\n        <add name=\"GoogleMembershipProvider\"\r\n             type=\"MainModule.Web.Providers.GoogleMembershipProvider\"\r\n             enablePasswordRetrieval=\"false\"\r\n            enablePasswordReset=\"false\"\r\n            requiresQuestionAndAnswer=\"true\"\r\n            passwordFormat=\"Hashed\"\r\n            applicationName=\"\/\"\r\n             \/>\r\n      <\/providers>\r\n<\/membership>\r\n<\/pre>\n<p>Maj\u0105c w kolekcji wszystkie nasze providery pozostaje nam si\u0119 zastanowi\u0107, w jaki spos\u00f3b raz u\u017cywa\u0107 GoogleMembershpProvidera, a innym razem SqlMembershipProvidera. Ja zrobi\u0142em to w nast\u0119puj\u0105cy spos\u00f3b. Najpierw utworzy\u0142em enuma, w kt\u00f3rym przechowuje &#8220;nazwy&#8221; moich provider\u00f3w<\/p>\n<pre lang=\"csharp\">\r\npublic enum AccountType\r\n{\r\n    PremiumHandsMembershipProvider,\r\n    GoogleMembershipProvider,\r\n    Facebook\r\n}\r\n<\/pre>\n<p>Nast\u0119pnie wykorzysta\u0142em parametr <span style=\"font-style: italic;\">customData<\/span> interfejsu <span style=\"font-style: italic;\">IAuthentication<\/span>. Po stronie klienta w funkcji logowania po prostu przesy\u0142am typ konta do kt\u00f3rego si\u0119 loguj\u0119. Wygl\u0105da to w ten spos\u00f3b:<\/p>\n<pre lang=\"csharp\">\r\n WebContext.Current.Authentication.Login(new LoginParameters(Login, Password, false,AccountType.GoogleMembershipProvider.ToString(\"g\")), CompleteLoginOperation, Guid);\r\n<\/pre>\n<p>Nast\u0119pnie po stronie serwera wybieram tego providera, przez kt\u00f3rego loguje si\u0119 u\u017cytkownik.<\/p>\n<pre lang=\"csharp\">\r\nprotected UserDTO ValidateCredentials(string name, string password, string customData, out string userData)\r\n{\r\n    UserDTO user = null;\r\n    userData = null;\r\n    if (Membership.Providers[customData].ValidateUser(name, password))\r\n    \/\/if (Membership.Provider.ValidateUser(name, password))\r\n    {\r\n        userData = name;\r\n        user = new UserDTO { DisplayName = name, Name = name, Email = name};\r\n    }\r\n    if (user != null) userData = name;\r\n    return user;\r\n}\r\n<\/pre>\n<p>Zauwa\u017cmy, \u017ce linijka<\/p>\n<pre lang=\"csharp\">\r\nif (Membership.Provider.ValidateUser(name, password))\r\n<\/pre>\n<p>zosta\u0142a zast\u0105piona przez<\/p>\n<pre lang=\"csharp\">\r\nif (Membership.Providers[customData].ValidateUser(name, password))\r\n<\/pre>\n<p>zatem teraz mamy wp\u0142yw na to, kt\u00f3rego MembershipProvidera u\u017cyjemy. Je\u017celi chodzi o samo sprawdzenie czy dany u\u017cytkownik poda\u0142 poprawne dane do konta google, to sprawa jest troch\u0119 k\u0142opotliwa(tak mi si\u0119 wydaje :D). Nie uda\u0142o mi si\u0119 u\u017cy\u0107 biblioteki dotNetOpenAuth, gdy\u017c z tego co widz\u0119 logowanie poprzez t\u0105 bibliotek\u0119 wymaga przekierowania na stron\u0119 Googla. Niestety taka operacja nie jest dozwolona w Silverlighcie (Silverlight nie wspiera cross-domain policy). Ponadto wykorzystanie tej biblioteki po stronie serwera r\u00f3wnie\u017c nie przynios\u0142o oczekiwanych rezultat\u00f3w. Ostatecznie zatem skorzysta\u0142em z opisu znajduj\u0105cego si\u0119 na stronie Googla http:\/\/code.google.com\/intl\/pl-PL\/apis\/accounts\/docs\/AuthForInstalledApps.html#Using. Czyli po prostu spreparowa\u0142em odpowiedni HTTPS Post Request. Funkcja ValidateUser z GoogleMembershipProvidera wygl\u0105da zatem nast\u0119puj\u0105co<\/p>\n<pre lang=\"csharp\">\r\npublic override bool ValidateUser(string username, string password)\r\n{\r\n    string requestUrl = string.Format(\"https:\/\/www.google.com\/accounts\/ClientLogin?service=mail&Email={0}&Passwd={1}\", username, password);\r\n    byte[] data = Encoding.ASCII.GetBytes(requestUrl);\r\n    HttpWebRequest req = (HttpWebRequest)WebRequest.Create(requestUrl);\r\n    req.ProtocolVersion = HttpVersion.Version10;\r\n    req.ContentLength = data.Length;\r\n    req.ContentType = \"application\/x-www-form-urlencoded\";\r\n    req.Method = \"POST\";\r\n    var stream = req.GetRequestStream();\r\n    stream.Write(data,0,data.Length);\r\n    stream.Close();\r\n    \r\n    try\r\n    {\r\n        HttpWebResponse response = (HttpWebResponse)req.GetResponse();\r\n        return response.StatusCode == HttpStatusCode.OK;\r\n    }\r\n    catch (WebException e)\r\n    {\r\n        return false;\r\n    }\r\n}\r\n<\/pre>\n<p>Ci\u0119\u017cko mi jednak stwierdzi\u0107 czy takie logowanie jest bezpieczne. Co prawda nie uda\u0142o mi si\u0119 wy\u0142apa\u0107 nic konkretnego poprzez Wiresharka lub Fiddlera, jednak\u017ce ekspertem od zabezpiecze\u0144 niestety nie jestem.<br \/>\nW nast\u0119pnym wpisie postaram si\u0119 kr\u00f3ciutko przedstawi\u0107 bibliotek\u0119 Microsoft Enterprise Library, a w\u0142a\u015bciwie jeden z jej blok\u00f3w &#8211; Security Block.Wykorzystam go do zezwalania u\u017cytkownikom do dost\u0119pu do funkcji serwisu, w zale\u017cno\u015bci od ich roli w systemie.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>W poprzednim wpisie przedstawi\u0142em w jaki spos\u00f3b zaimplementowa\u0107 mechanizm logowania po stronie serwera. Wykorzysta\u0142em do tego celu WCF RIA oraz znane z ASP MembershipProvidery. Tym razem przedstawi\u0119 jak wymusi\u0107 logowanie po stronie klienta, oraz w jaki spos\u00f3b mo\u017cna dynamicznie zmienia\u0107 provider\u00f3w, kt\u00f3rzy waliduj\u0105 usera. Poprzednim razem stworzyli\u015bmy ju\u017c szkielet aplikacji, zatem mamy projekt klienta oraz [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[33,31,126,44,45],"tags":[251,255,190,191],"class_list":["post-57","post","type-post","status-publish","format-standard","hentry","category-authentication","category-authorization","category-logowanie","category-silverlight","category-wpf","tag-authentication","tag-authorization","tag-silverlight","tag-wpf"],"_links":{"self":[{"href":"https:\/\/tpodolak.com\/blog\/wp-json\/wp\/v2\/posts\/57","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/tpodolak.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/tpodolak.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/tpodolak.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/tpodolak.com\/blog\/wp-json\/wp\/v2\/comments?post=57"}],"version-history":[{"count":4,"href":"https:\/\/tpodolak.com\/blog\/wp-json\/wp\/v2\/posts\/57\/revisions"}],"predecessor-version":[{"id":561,"href":"https:\/\/tpodolak.com\/blog\/wp-json\/wp\/v2\/posts\/57\/revisions\/561"}],"wp:attachment":[{"href":"https:\/\/tpodolak.com\/blog\/wp-json\/wp\/v2\/media?parent=57"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/tpodolak.com\/blog\/wp-json\/wp\/v2\/categories?post=57"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/tpodolak.com\/blog\/wp-json\/wp\/v2\/tags?post=57"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}