Аутентификация WCF. Произошла ошибка при проверке безопасности для сообщения.

У меня проблема с подключением к моей службе WCF с помощью clientCredentialType="UserName".

Когда я запускаю код ниже, я получаю сообщение об ошибке

FaultException: при проверке безопасности для сообщения произошла ошибка.

При игре с некоторыми значениями привязки я также получаю Access is denied..

Fiddler говорит, что нет заголовка авторизации, и я не могу найти имя пользователя или пароль в запросе.

Вот выдержки из моего конфига:

  <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>

Мой валидатор имени пользователя/пароля выглядит так:

  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");
            }
        }
    }

У меня это как простой тест на моем сервисе:

[OperationContract]
[XmlSerializerFormat]
void Test();

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

}

У меня есть собственный сертификат SSL на моем сервере, и я могу получить доступ к своим сервисам/метаданным.

Затем я добавил ссылку на службу в консольном приложении и попытаюсь подключиться к службе с помощью этого кода ниже:

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) {
        }
    }
}

Я проверил средство просмотра событий на сервере, и эта ошибка появляется с каждым запросом:

MessageSecurityException: процессору безопасности не удалось найти заголовок безопасности в сообщении. Это может быть связано с тем, что сообщение является необеспеченной ошибкой или потому, что существует связующее несоответствие между общающимися сторонами. Это может произойти, если служба настроена для обеспечения безопасности, а клиент не использует безопасность.

Ответ 1

Вы указываете стороне клиента использовать BasicHttpSecurityMode.Transport, тогда как служба ожидает BasicHttpSecurityMode.TransportWithMessageCredential. Это проблема, потому что служба ищет учетные данные клиента в заголовке сообщения SOAP, и клиент не отправит их с привязкой, сконфигурированной таким образом.

Следовательно, поэтому пара имени пользователя/пароля отсутствует в заголовке сообщения, когда вы являетесь свидетелем. Таким образом, зритель событий был прав, что между связующими сторонами имело место несовпадение.

Также установите ClientCredentialType на клиенте BasicHttpMessageCredentialType.UserName для Message уровня безопасности. По умолчанию BasicHttpBinding использует None, которые являются анонимными клиентами.

Здесь фрагмент кода, описывающий вышеизложенные изменения:

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

Ответ 2

Это также может быть вызвано отсутствием синхронизации между клиентом и сервером. Если сертификат или подписанный токен являются недействительными по времени, то может быть возвращено то же сообщение An error occurred when verifying security for the message..