pondělí 15. listopadu 2010

Chyba WCF "An error occurred when verifying security for the message."

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ěří ;-)

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.