Vrátím se ještě k předchozímu příspěvku, kde jsem popisoval UserNamePasswordValidator při ověřování klienta ve WCF službě. Setkal jsem se totiž s jedním problémem při přesunu na testovací server, který mi zabral pár hodin zkoumání.
Nastavil jsem na testovacím serveru vše potřebné včetně certifikátu. Po spuštění klienta jsem ale neustále dostával tu samou nic neříkající chybovou zprávu "An error occurred when verifying security for the message.". Pátral jsem po webu, co by to mohlo být. Zhruba 90% všech nalezených řešení, spojených s touto chybou, se týkalo špatně nastaveného času mezi klientem a serverem - defaultně se toleruje odchylka max. 5 minut, ale dá se pomocí konfigurace WCF změnit nastavením MaxClockSkew. Toto ale nebyl můj případ, jelikož hodiny byli na obou stranách komunikace synchronizované automaticky a nerozcházely se.
Pak jsem narazil na jeden příspěvek, který řešil stejný problém, ale příčina chyby nebyla v čase nýbrž ve velikosti zprávy. To mě přivedlo na myšlenku, že ta chyba může být vlastně libovolná a chybová zpráva neodpovídá skutečné příčině chyby. A měl jsem pravdu. Po chvilce laborování jsem našel špatně nastavené připojení do databáze, takže při ověřování klienta se nepodařilo připojit k SQL serveru a přitom se vygenerovala tato nic neříkající chyba.
Takže jaký je závěr? Dokud není sestaveno spojení, tak nelze věřit plně chybovým zprávám. Vypadá to, že někdy se některé chyby spolknou a k vám se dostane až nějaká další chyba, která při sestavování komunikace vznikla.
I programátoři jsou jen lidé, i když ne každý tomu věří ;-)
Zobrazují se příspěvky se štítkemWCF. Zobrazit všechny příspěvky
Zobrazují se příspěvky se štítkemWCF. Zobrazit všechny příspěvky
pondělí 15. listopadu 2010
středa 3. listopadu 2010
WCF a UserNamePasswordValidator
Laboroval jsem teď nějakou chvíli s ověřováním uživatelů ve WCF. Potřeboval jsem kontrolovat, že uživatel volající metody WCF služby, má právo je využívat. Bohužel jsem byl předem omezen mantinely:
- credentials uživatelů v MS SQL
- basicHttpBinding
- služba hostovaná v IISku
Použitý binding je omezující v tom, že komunikace musí být vždy zabezpečená pomocí certifikátu, bez něj to prostě nejde. Zajímavé je, že to nemusí být na IISku definované jako "required". Ale i když jsem definoval referenci služby bez https, tak se generoval endpoint s https. Je to někde "natvrdo" nastavené a bez https to nefunguje.
Jsou asi jen 2 možnosti, jak ověřovat uživatele:
- využít ASP.NET providera
- využít třídu UserNamePasswordValidator
Rozdíl je asi jen v komplexnosti, co od toho očekáváte. Výhoda ASP.NET providera je v tom, že lze použít i návazné role a profily.
Mě stačilo jednoduché ověření uživatele. Také proto, že jsem měl jinou strukturu DB pro uživatele a tím pádem bych musel psát vlastního ASP.NET providera, jsem se rozhodl pro využití UserNamePasswordValidator. Odvodil jsem si vlastní třídu a přepsal metodu Validate - jednoduchý příklad je v MSDN.
Největší problém byl s konfigurací, aby validátor správně fungoval a nepoužívalo se ověřování Windows. Nastavení WCF služby:
<system.serviceModel>
<services>
<service name="ServiceLibrary.Service" behaviorConfiguration="CoopServiceBehavior">
<!-- Service Endpoints -->
<endpoint address="" binding="basicHttpBinding" bindingConfiguration="MyBinding" contract="ServiceLibrary.IService"/>
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>
</service>
</services>
<bindings>
<basicHttpBinding>
<binding name="MyBinding">
<security mode="TransportWithMessageCredential">
<message clientCredentialType="UserName" />
</security>
</binding>
</basicHttpBinding>
</bindings>
<behaviors>
<serviceBehaviors>
<behavior name="CoopServiceBehavior">
<serviceMetadata httpGetEnabled="true"/>
<serviceDebug includeExceptionDetailInFaults="false"/>
<serviceCredentials>
<serviceCertificate findValue="CN=zeleny5" storeName="CertificateAuthority" x509FindType="FindByIssuerName"/>
<userNameAuthentication userNamePasswordValidationMode="Custom" customUserNamePasswordValidatorType="ServiceLibrary.CustomUserNameValidator, ServiceLibrary"/>
</serviceCredentials>
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
Na IISku si vygenerujete certifikát, který by měl být podepsaný nějakou certifikační autoritou (CA). Na klientovi musí být certifikát CA nainstalován mezi důvěryhodnými kořenovými certifikačními úřady. Jinak bude při komunikaci vyvolána chyba. Dále je potřeba na serveru udělit práva na čtení k certifikátu. Na adresáři C:\ProgramData\Microsoft\Crypto\RSA\MachineKeys nastavte práva na čtení pro Network Service, pod kterým běží služba v IISku (nastaveno v aplikačním poolu). Může to hlásit chyby typu Access Denied, ale to nevadí, protože to stejně bude fungovat ;-) Na webu, kde poběží služba, je potřeba definovat vazbu na HTTPS s vygenerovaným a podepsaným certifikátem. Není potřeba nastavovat vyžadování SSL.
Když je vše správně nakonfigurované, tak by to mělo fungovat... klient se připojí ke službě, ta ho ověří a dovolí mu vyvolat požadovanou metodu. V opačném případě dojde k chybě - v závislosti na implementaci v metodě Validate.
<system.serviceModel>
<services>
<service name="ServiceLibrary.Service" behaviorConfiguration="CoopServiceBehavior">
<!-- Service Endpoints -->
<endpoint address="" binding="basicHttpBinding" bindingConfiguration="MyBinding" contract="ServiceLibrary.IService"/>
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>
</service>
</services>
<bindings>
<basicHttpBinding>
<binding name="MyBinding">
<security mode="TransportWithMessageCredential">
<message clientCredentialType="UserName" />
</security>
</binding>
</basicHttpBinding>
</bindings>
<behaviors>
<serviceBehaviors>
<behavior name="CoopServiceBehavior">
<serviceMetadata httpGetEnabled="true"/>
<serviceDebug includeExceptionDetailInFaults="false"/>
<serviceCredentials>
<serviceCertificate findValue="CN=zeleny5" storeName="CertificateAuthority" x509FindType="FindByIssuerName"/>
<userNameAuthentication userNamePasswordValidationMode="Custom" customUserNamePasswordValidatorType="ServiceLibrary.CustomUserNameValidator, ServiceLibrary"/>
</serviceCredentials>
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
Na IISku si vygenerujete certifikát, který by měl být podepsaný nějakou certifikační autoritou (CA). Na klientovi musí být certifikát CA nainstalován mezi důvěryhodnými kořenovými certifikačními úřady. Jinak bude při komunikaci vyvolána chyba. Dále je potřeba na serveru udělit práva na čtení k certifikátu. Na adresáři C:\ProgramData\Microsoft\Crypto\RSA\MachineKeys nastavte práva na čtení pro Network Service, pod kterým běží služba v IISku (nastaveno v aplikačním poolu). Může to hlásit chyby typu Access Denied, ale to nevadí, protože to stejně bude fungovat ;-) Na webu, kde poběží služba, je potřeba definovat vazbu na HTTPS s vygenerovaným a podepsaným certifikátem. Není potřeba nastavovat vyžadování SSL.
Když je vše správně nakonfigurované, tak by to mělo fungovat... klient se připojí ke službě, ta ho ověří a dovolí mu vyvolat požadovanou metodu. V opačném případě dojde k chybě - v závislosti na implementaci v metodě Validate.
úterý 21. září 2010
WCF RIA Services - update datového zdroje
Hraju si teď trochu s WCF RIA Services. Vcelku zajímavá technologie, ale má i své nedokonalosti. Jako první a asi i dost podstatná je nemožnost změny datového modelu nějakým hezkým automatickým způsobem ;-)
O co jde? Mám vytvořený databázový model, entity model a domain service. Někdy v průběhu vývoje zjistím, že je potřeba udělat nějaké změny a rozšíření v DB. Není problém udělat update v databázi. Entity model lze také jednoduše upravit (v edmx souboru přes pravé tlačítko myši). Jenže co s domain service? Tady jsem zatím narazil. Žádná automatika není (nebo jo? :-O). Trochu jsem po tom pátrat a našel jsem jen takový work around. Vytvořit nový dočasný domain service pouze pro novou tabulku. Jeho obsah následně přenést do původního domain service a dočasný smazat. Stejným způsobem přenést i informace o metadatech. V případě změny struktury tabulky se to musí udělat podobně, ale stačí jen takto upravit metadata.
Řešení zatím nic moc, snad se dočkáme v další verzi nějakého pokroku v tomto směru. Na druhou stranu ani ta automatika nebude úplně dokonalá, protože v případě doplnění domain service o vlastní metody a nepoužití partial class, by mohlo dojít ke ztrátě těchto metod.
O co jde? Mám vytvořený databázový model, entity model a domain service. Někdy v průběhu vývoje zjistím, že je potřeba udělat nějaké změny a rozšíření v DB. Není problém udělat update v databázi. Entity model lze také jednoduše upravit (v edmx souboru přes pravé tlačítko myši). Jenže co s domain service? Tady jsem zatím narazil. Žádná automatika není (nebo jo? :-O). Trochu jsem po tom pátrat a našel jsem jen takový work around. Vytvořit nový dočasný domain service pouze pro novou tabulku. Jeho obsah následně přenést do původního domain service a dočasný smazat. Stejným způsobem přenést i informace o metadatech. V případě změny struktury tabulky se to musí udělat podobně, ale stačí jen takto upravit metadata.
Řešení zatím nic moc, snad se dočkáme v další verzi nějakého pokroku v tomto směru. Na druhou stranu ani ta automatika nebude úplně dokonalá, protože v případě doplnění domain service o vlastní metody a nepoužití partial class, by mohlo dojít ke ztrátě těchto metod.
pátek 21. května 2010
Chyba WCF při velkém počtu záznamů
Nedávno jsem řešil zajímavou chybu ve WCF. Při zobrazení jedné statistiky nahodile padala služba. V logu byla následující chyba:
Maximální počet položek, které lze serializovat nebo převést ze sériového tvaru v grafu objektů, je 65536. Změňte graf objektů nebo zvyšte kvótu MaxItemsInObjectGraph.
Hned bylo jasné, že počet přenášených záznamů je moc velký. Zkoušel jsem měnit konfiguraci dle MSDN, ale nakonec to nebylo tak jednoduché, jak jsem si myslel. Pro ukázku, jak to lze řešit. Důležité je nastavit parametr jak na serveru, tak i na klientovi.
Ukázková konfigurace serveru:
A odpovídající konfigurace pro klienta:
Na klientovy je potřeba definovat atribut behaviorConfiguration pro všechny endpointy.
Maximální počet položek, které lze serializovat nebo převést ze sériového tvaru v grafu objektů, je 65536. Změňte graf objektů nebo zvyšte kvótu MaxItemsInObjectGraph.
Hned bylo jasné, že počet přenášených záznamů je moc velký. Zkoušel jsem měnit konfiguraci dle MSDN, ale nakonec to nebylo tak jednoduché, jak jsem si myslel. Pro ukázku, jak to lze řešit. Důležité je nastavit parametr jak na serveru, tak i na klientovi.
Ukázková konfigurace serveru:
<system.serviceModel>
<services>
<service behaviorConfiguration="Umea.se.EventReactor.ServiceTier.ServiceViewEventBehavior" name="Umea.se.EventReactor.ServiceTier.ServiceViewEvent">
<endpoint address="" binding="wsHttpBinding" contract="Umea.se.EventReactor.ServiceTier.IServiceViewEvent">
<identity>
<dns value="localhost" />
</identity>
</endpoint>
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="Umea.se.EventReactor.ServiceTier.ServiceViewEventBehavior">
<serviceMetadata httpGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="false" />
<dataContractSerializer maxItemsInObjectGraph="2147483647" />
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
A odpovídající konfigurace pro klienta:
<system.serviceModel>
<client>
<endpoint address="http://localhost:3379/ServiceViewEvent.svc" behaviorConfiguration="ServiceViewEventBehavior" binding="wsHttpBinding" bindingConfiguration="WSHttpBinding_IServiceViewEvent" contract="ServiceReferenceViewEvent.IServiceViewEvent" name="WSHttpBinding_IServiceViewEvent">
<identity>
<dns value="localhost" />
</identity>
</endpoint>
</client>
<behaviors>
<endpointBehaviors>
<behavior name="ServiceViewEventBehavior">
<dataContractSerializer maxItemsInObjectGraph="2147483647"/>
</behavior>
</endpointBehaviors>
</behaviors>
</system.serviceModel>
Na klientovy je potřeba definovat atribut behaviorConfiguration pro všechny endpointy.
Přihlásit se k odběru:
Příspěvky (Atom)