web-dev-qa-db-fra.com

Authentification WCF - Une erreur s'est produite lors de la vérification de la sécurité du message

J'ai un problème de connexion à mon service WCF avec clientCredentialType="UserName".

Lorsque j'exécute le code ci-dessous, j'obtiens une erreur

FaultException: une erreur s'est produite lors de la vérification de la sécurité du message.

Lorsque je joue avec certaines des valeurs de liaison, j'obtiens également Access is denied..

Fiddler dit qu'il n'y a pas d'en-tête d'autorisation et que je ne trouve pas non plus le nom d'utilisateur ou le mot de passe dans la demande.

Voici des extraits de ma config:

  <system.webServer>
    <modules runAllManagedModulesForAllRequests="true"/>
  </system.webServer>
    <services>
      <service name="InventoryServices.MobileAPI"  behaviorConfiguration="customBehaviour">
        <endpoint address=""
                  binding="basicHttpBinding"
                  bindingConfiguration="secureHttpBinding"
                  contract="InventoryServices.IMobileAPI"/>

        <endpoint address="mex"
                  binding="mexHttpsBinding"
                  contract="IMetadataExchange" />
      </service>
    </services>
    <behaviors>
      <serviceBehaviors>
        <behavior name="customBehaviour">
          <serviceSecurityAudit auditLogLocation="Application" serviceAuthorizationAuditLevel="Failure" messageAuthenticationAuditLevel="Failure" suppressAuditFailure="true" />
          <!-- To avoid disclosing metadata information, set the value below to false and remove the metadata endpoint above before deployment -->
          <serviceMetadata httpsGetEnabled="true"/>
          <!-- To receive exception details in faults for debugging purposes, set the value below to true.  Set to false before deployment to avoid disclosing exception information -->
          <serviceDebug includeExceptionDetailInFaults="true"/>
          <serviceCredentials>
            <userNameAuthentication userNamePasswordValidationMode="Custom"
               customUserNamePasswordValidatorType="InventoryLibrary.Helpers.UserAuthentication,InventoryLibrary"/>
          </serviceCredentials>
        </behavior>
      </serviceBehaviors>
    </behaviors>
    <serviceHostingEnvironment multipleSiteBindingsEnabled="true" />
    <bindings>
      <basicHttpBinding>
        <binding name="secureHttpBinding">
          <security mode="TransportWithMessageCredential">
            <transport clientCredentialType="Basic" proxyCredentialType="Basic" realm="MyRealm"/>
            <message clientCredentialType="UserName" algorithmSuite="Default"  />
          </security>
        </binding>
      </basicHttpBinding>
    </bindings>

Mon validateur de nom d'utilisateur/mot de passe ressemble à ceci:

  public class UserAuthentication : UserNamePasswordValidator {
        public override void Validate(string userName, string password) {

            EntitiesContext db = new EntitiesContext();
            db.Logs.Add(new DomainModels.Log() {
                DateLogged = DateTime.Now,
                Message = "hit auth",
                Type = DomainModels.LogType.Info
            });
            db.SaveChanges();

            try {

                if (userName == "test" && password == "test123") {
                    Console.WriteLine("Authentic User");
                }
            }
            catch (Exception ex) {
                throw new FaultException("Unknown Username or Incorrect Password");
            }
        }
    }

Je l'ai comme un simple test sur mon service:

[OperationContract]
[XmlSerializerFormat]
void Test();

[PrincipalPermission(SecurityAction.Demand, Name = "test")]
public void Test() {

}

J'ai un certificat SSL auto-signé sur mon serveur et je peux accéder à mes services/métadonnées.

Ensuite, j'ai ajouté une référence de service dans une application console et j'essaie de me connecter au service avec ce code ci-dessous:

class Program {
    static void Main(string[] args) {

        Stuff.InitiateSSLTrust();

        BasicHttpBinding binding = new BasicHttpBinding();
        binding.Security.Mode = BasicHttpSecurityMode.Transport;
        binding.Security.Transport.Realm = "MyRealm";

        ServiceReference1.MobileAPIClient serviceProxy = new ServiceReference1.MobileAPIClient(binding, new EndpointAddress("https://xx.xx.xx.xx/InventoryServices.MobileApi.svc"));

        serviceProxy.ClientCredentials.UserName.UserName = "test";
        serviceProxy.ClientCredentials.UserName.Password = "test123";

        try {

            var a = serviceProxy.Login("a", "b");
        }
        catch (Exception ex) {
            var ex2 = ex;
        }
    }
}

public class Stuff {
    public static void InitiateSSLTrust() {
        try {
            //Change SSL checks so that all checks pass
            ServicePointManager.ServerCertificateValidationCallback =
                new RemoteCertificateValidationCallback(
                    delegate { return true; }
                );
        }
        catch (Exception ex) {
        }
    }
}

J'ai vérifié l'Observateur d'événements sur le serveur et cette erreur apparaît avec chaque demande:

MessageSecurityException: le processeur de sécurité n'a pas pu trouver d'en-tête de sécurité dans le message. Cela peut être dû au fait que le message est une erreur non sécurisée ou à cause d'une incompatibilité de liaison entre les parties en communication. Cela peut se produire si le service est configuré pour la sécurité et que le client n'utilise pas la sécurité.

14
Smithy

Vous spécifiez le côté client à utiliser BasicHttpSecurityMode.Transport alors que le service attend BasicHttpSecurityMode.TransportWithMessageCredential. Il s'agit d'un problème car le service recherche les informations d'identification du client dans l'en-tête de message SOAP et le client ne les enverra pas avec la liaison configurée de cette façon.

Par conséquent, c'est pourquoi la paire nom d'utilisateur/mot de passe n'est pas présente dans l'en-tête du message comme vous en êtes témoin. Ainsi, l'observateur d'événements avait raison de dire qu'il y avait un décalage contraignant entre les parties communicantes.

Définissez également ClientCredentialType sur le client sur BasicHttpMessageCredentialType.UserName pour Message niveau de sécurité. Par défaut, BasicHttpBinding utilise None qui sont des clients anonymes.

Voici un extrait de code décrivant les modifications ci-dessus:

var basicHttpBinding = new BasicHttpBinding(
                              BasicHttpSecurityMode.TransportWithMessageCredential);
basicHttpBinding.Security.Message.ClientCredentialType = 
                                     BasicHttpMessageCredentialType.UserName;
12
Derek W