web-dev-qa-db-ja.com

WCF認証-メッセージのセキュリティを確認するときにエラーが発生しました

clientCredentialType="UserName"を使用してWCFサービスへの接続に問題があります。

以下のコードを実行するとエラーが発生します

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:セキュリティプロセッサは、メッセージ内のセキュリティヘッダーを見つけることができませんでした。これは、メッセージが保護されていない障害であるか、通信パーティ間でバインディングの不一致があるためです。これは、サービスがセキュリティ用に構成されており、クライアントがセキュリティを使用していない場合に発生する可能性があります。

14
Smithy

サービスがBasicHttpSecurityMode.Transportを想定しているのに対し、BasicHttpSecurityMode.TransportWithMessageCredentialを使用するようにクライアント側を指定しています。これは問題です。サービスはSOAPメッセージヘッダーでクライアント資格情報を探しており、クライアントはこの方法で構成されたバインディングでそれらを送信しないためです。

したがって、これが、目撃しているときにメッセージヘッダーにユーザー名とパスワードのペアが存在しない理由です。そのため、イベントビューアーは、通信する当事者間で拘束力のある不一致があったことを認識しました。

また、ClientCredentialTypeレベルのセキュリティのために、クライアントのMessageBasicHttpMessageCredentialType.UserNameに設定します。デフォルトでは、BasicHttpBindingは匿名クライアントであるNoneを使用します。

上記の変更を説明するコードスニペットを次に示します。

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