WCF 身份验证 - 验证消息的安全性时出错

Posted

技术标签:

【中文标题】WCF 身份验证 - 验证消息的安全性时出错【英文标题】:WCF Authentication - An error occurred when verifying security for the message 【发布时间】:2014-07-28 17:53:21 【问题描述】:

我在使用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:安全处理器无法在消息中找到安全标头。这可能是因为消息是不安全的错误,或者是因为通信双方之间存在绑定不匹配。如果服务配置了安全性并且客户端未使用安全性,则可能会发生这种情况。

【问题讨论】:

您的客户端 web/app.config 是什么样的?那里应该有一些证书值用于消息安全绑定。另外,您是否正确获得了服务器设置上证书存储的权限? (我认为它通常是“我的”证书存储。这听起来与我过去遇到的问题非常相似,而且是一个难以解决的问题。我在打字时试图回忆它。它可能会在接下来的 10 天内形成我考虑了一下。 对不起,我没有看到服务器绑定是 TransportWithMessage。但是,我确实注意到您的客户端只使用了与服务器不同的传输绑定? 【参考方案1】:

您指定客户端使用BasicHttpSecurityMode.Transport,而服务需要BasicHttpSecurityMode.TransportWithMessageCredential。这是一个问题,因为服务正在 SOAP 消息头中查找客户端凭据,而客户端不会使用这种方式配置的绑定发送它们。

因此,这就是您所看到的消息标题中不存在用户名/密码对的原因。因此,事件查看器认为通信方之间存在绑定不匹配是正确的。

还将客户端上的ClientCredentialType 设置为BasicHttpMessageCredentialType.UserName 以获得Message 级别的安全性。默认情况下,BasicHttpBinding 使用 None,它们是匿名客户端。

这是描述上述变化的代码 sn-p:

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

【讨论】:

技术上你是对的,所以我给出了答案。我正在使用 Xamarin,不幸的是,底层类不支持 TransportWithMessageCredential。【参考方案2】:

这也可能是由于客户端和服务器之间的时间不同步造成的。如果证书或签名令牌因时间原因无效,则可能会返回相同的An error occurred when verifying security for the message. 消息。

【讨论】:

【参考方案3】:

只需在添加服务引用后编辑 .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;

【讨论】:

以上是关于WCF 身份验证 - 验证消息的安全性时出错的主要内容,如果未能解决你的问题,请参考以下文章

验证邮件的安全性时出错

WCF - Windows 身份验证 - 安全设置需要匿名

带有客户端证书身份验证的 Wcf 不适用于 soapui

WCF 客户端使用 WS-Security UsernameToken PasswordDigest 身份验证方案使用 Axis 2 Web 服务时出错

在声明身份验证的 SharePoint 2010 中托管的 WCF 服务中的模拟

WCF 身份验证错误