WCF身份验证 - 在验证消息安全性时发生错误

18

使用clientCredentialType="UserName"连接WCF服务时,我遇到了问题。

当我运行下面的代码时,出现了以下错误:

FaultException: 验证消息的安全性时发生错误。

当我尝试更改一些绑定值时,还会出现拒绝访问的错误。

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: 安全处理程序无法在消息中找到安全头。这可能是因为消息是一个未安全化的 fault,或者由于通信双方之间的绑定不匹配。如果服务配置为安全性,而客户端没有使用安全性,则可能会出现此问题。


你的客户端web/app.config长什么样子?那里应该有一些证书的值,用于消息安全绑定。此外,您是否已正确设置了服务器上证书存储的权限?(我认为通常是“My”证书存储。这听起来与我过去遇到的问题非常相似,而且很难解决。我正在打字时尝试回想它。在接下来的10分钟内可能会形成。) - brumScouse
对不起,我没有注意到服务器绑定是 TransportWithMessage。然而,我注意到您的客户端只使用了一个传输绑定,这与服务器上的绑定不同。 - brumScouse
3个回答

21
您正在指定客户端使用 BasicHttpSecurityMode.Transport,而服务期望使用 BasicHttpSecurityMode.TransportWithMessageCredential。这是一个问题,因为服务正在查找 SOAP 消息头中的客户端凭据,而配置为此方式的绑定将不会发送它们。
因此,这就是您目睹的用户名/密码对不存在于消息头中的原因。因此事件查看器正确地指出通信双方之间存在绑定不匹配的问题。
同时,在客户端上设置 ClientCredentialTypeBasicHttpMessageCredentialType.UserName,以实现消息级别安全性。 默认情况下,BasicHttpBinding 使用匿名客户端的 None
以下是描述上述更改的代码片段:
var basicHttpBinding = new BasicHttpBinding(
                              BasicHttpSecurityMode.TransportWithMessageCredential);
basicHttpBinding.Security.Message.ClientCredentialType = 
                                     BasicHttpMessageCredentialType.UserName;

从技术上讲,你是正确的,所以我会给出答案。我正在使用Xamarin,但基础类不支持TransportWithMessageCredential。 - Smithy

8

这也可能是由于客户端和服务器时间不同步引起的。如果基于时间的证书或签名令牌无效,则可能返回相同的验证消息安全性时发生错误。


0

在添加服务引用后,只需编辑.csproj文件,并将这些依赖项从4.4.*指向4.6.*即可。

<ItemGroup> <PackageReference Include="System.ServiceModel.Duplex" Version="4.6.*" /> 
    <PackageReference Include="System.ServiceModel.Http" Version="4.6.*" /> 
    <PackageReference Include="System.ServiceModel.NetTcp" Version="4.6.*" /> 
    <PackageReference Include="System.ServiceModel.Security" Version="4.6.*" /> 
</ItemGroup>

并添加这个

 binding.Security.Mode = System.ServiceModel.BasicHttpSecurityMode.TransportWithMessageCredential;
 binding.Security.Message.ClientCredentialType = BasicHttpMessageCredentialType.UserName;

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接