具有 WS-Security 的 WCF 服务仅需要签名时间戳 [英] WCF Service with WS-Security requires Signed Timestamp only

查看:27
本文介绍了具有 WS-Security 的 WCF 服务仅需要签名时间戳的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我需要向第三方提供一项服务,该服务将发送带有签名时间戳的 soap 消息.

I need to provide a service to a third-party that will be sending soap messages with a signed Timestamp.

如何配置我的服务以支持此功能?

How can I configure my service to support this?

更新我已经设法接近我们所追求的 Soap 消息的格式,但 WCF 坚持对用户名和时间戳令牌进行签名,有没有办法修改绑定只签署时间戳?

UPDATE I've managed to get close to the format of the Soap message that we're after but WCF insists on signing both the username and the timestamp tokens, Is there a way to modify the binding to only sign the timestamp?

进一步更新以下是我们的要求:

  • 必须对 Timestamp 元素进行签名.
  • 用于签名的证书上的 CN 名称必须与 UsernameToken 元素中给出的用户名相匹配.
  • 用于签名的证书必须在 BinarySecurityToken 元素中发送.
  • KeyInfo 元素必须只包含 SecurityTokenReference 元素,该元素必须用于引用 BinarySecurityToken.
  • 必须指定规范化算法.
  • 必须指定 SignatureMethod,并且必须是 SHA-1 或 SHA-2 算法.
  • 应该使用分离的签名.

有什么建议吗?

当前配置

客户端绑定

<bindings>
  <wsHttpBinding>
    <binding name="WSBC">
      <security mode="TransportWithMessageCredential">
        <transport clientCredentialType="Certificate" proxyCredentialType="None"></transport>
        <message clientCredentialType="UserName" negotiateServiceCredential="false" establishSecurityContext="false" />
      </security>
    </binding>
  </wsHttpBinding>
</bindings>

客户端端点

<client>
  <endpoint address="https://localhost/WcfTestService/Service2.svc"
  behaviorConfiguration="CCB" binding="wsHttpBinding"
  bindingConfiguration="WSBC"
  contract="ServiceReference2.IService2"
  name="wsHttpBinding_IService2" />
</client>

客户行为

<behaviors>
  <endpointBehaviors>
    <behavior name="MBB">
      <clientCredentials>
        <clientCertificate  findValue="03 58 d3 bf 4b e7 67 2e 57 05 47 dc e6 3b 52 7f f8 66 d5 2a"
                            storeLocation="LocalMachine"
                            storeName="My"
                            x509FindType="FindByThumbprint" />
        <serviceCertificate>
          <defaultCertificate findValue="03 58 d3 bf 4b e7 67 2e 57 05 47 dc e6 3b 52 7f f8 66 d5 2a"
                              storeLocation="LocalMachine"
                              storeName="My"
                              x509FindType="FindByThumbprint"  />
        </serviceCertificate>
      </clientCredentials>
    </behavior>
  </endpointBehaviors>
</behaviors>

服务绑定

<bindings>
  <wsHttpBinding>
    <binding name="ICB">
      <security mode="TransportWithMessageCredential">
        <transport clientCredentialType="Certificate" proxyCredentialType="None"></transport>
        <message    clientCredentialType="UserName" 
                    negotiateServiceCredential="false"
                    establishSecurityContext="false" />
      </security>
    </binding>
  </wsHttpBinding>
</bindings>

服务端点

<service name="WcfTestService.Service2" behaviorConfiguration="SCB">
    <endpoint     address="" binding="wsHttpBinding" contract="WcfTestService.IService2"
    bindingConfiguration="ICB" name="MS" />
</service>

服务行为

<behaviors>
  <serviceBehaviors>
    <behavior name="SCB">
      <serviceCredentials>
        <serviceCertificate     findValue="4d a9 d8 f2 fb 4e 74 bd a7 36 d7 20 a8 51 e2 e6 ea 7d 30 08"
                                storeLocation="LocalMachine"
                                storeName="TrustedPeople"   
                                x509FindType="FindByThumbprint" />
        <userNameAuthentication 
            userNamePasswordValidationMode="Custom" 
            customUserNamePasswordValidatorType="WcfTestService.UsernameValidator, WcfTestService" />
        <clientCertificate>
          <authentication certificateValidationMode="None" revocationMode="NoCheck" />
        </clientCertificate>
      </serviceCredentials>
      <serviceMetadata httpGetEnabled="true" />
      <serviceDebug includeExceptionDetailInFaults="false" />
    </behavior>
  </serviceBehaviors>
</behaviors>

推荐答案

SO 上有很多这样的问题,但没有一个有明确的答案,所以在花了很多时间之后我离开了我的答案回答这个 8 年前的问题,希望对某人有所帮助.

There are a bunch of questions like this on SO but none of them have definitive answers, so after spending way to much time on this I'm leaving my answer to this 8 year old question in hopes it'll help somebody.

我必须将带有密码摘要和签名时间戳(仅签署时间戳)的 SOAP 消息发送到黑盒服务器,我认为它是 Axis2.我使用了不同的安全配置和 SignedXml 类的派生变体,并成功地使我的消息看起来有些正确,但始终无法生成有效的签名.根据微软的说法,WCF 不像非 WCF 服务器那样规范化,WCF 遗漏了一些命名空间并以不同的方式重命名命名空间前缀,所以我永远无法让我的签名匹配.

I had to send a SOAP message with a Password Digest and signed Timestamp (only sign the Timestamp) to a black box server, I think it was Axis2. I monkeyed around with different security configurations and derived variations of the SignedXml class and succeeded in getting my message to look somewhat correct but was never able to produce a valid signature. According to Microsoft, WCF doesn't canonicalize the same way non-WCF servers do and WCF leaves out some namespaces and renames namespace prefixes differently so I could never get my signatures to match up.

所以经过大量的反复试验,这是我的 DIY 方法:

So after a ton of trial and error here's my DIY way of doing it:

  1. 定义负责创建整个安全标头的自定义 MessageHeader.
  2. 定义自定义 MessageInspector 以重命名命名空间、添加缺少的命名空间,并将我的自定义安全标头添加到请求标头中

以下是我需要生成的请求示例:

Here's an example of the request I needed to produce:

<soapenv:Envelope xmlns:ns1="http://somewebsite.com/" xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="https://anotherwebsite.com/xsd">
<soapenv:Header>
    <wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
        <wsse:UsernameToken wsu:Id="UsernameToken-1">
            <wsse:Username>username</wsse:Username>
            <wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest">aABCDiUsrOy8ScJkdABCD/ZABCD=</wsse:Password>
            <wsse:Nonce EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary">ABCDxZ8IABCDg/pTK6E0Q==</wsse:Nonce>
            <wsu:Created>2019-03-07T21:31:00.281Z</wsu:Created>
        </wsse:UsernameToken>
        <wsse:BinarySecurityToken EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary" ValueType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3" wsu:Id="X509-1">...</wsse:BinarySecurityToken>
        <wsu:Timestamp wsu:Id="TS-1">
            <wsu:Created>2019-03-07T21:31:00Z</wsu:Created>
            <wsu:Expires>2019-03-07T21:31:05Z</wsu:Expires>
        </wsu:Timestamp>
        <ds:Signature Id="SIG-1" xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
            <ds:SignedInfo>
                <ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#">
                    <ec:InclusiveNamespaces PrefixList="ns1 soapenv xsd" xmlns:ec="http://www.w3.org/2001/10/xml-exc-c14n#"/>
                </ds:CanonicalizationMethod>
                <ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
                <ds:Reference URI="#TS-1">
                    <ds:Transforms>
                        <ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#">
                            <ec:InclusiveNamespaces PrefixList="wsse ns1 soapenv xsd" xmlns:ec="http://www.w3.org/2001/10/xml-exc-c14n#"/>
                        </ds:Transform>
                    </ds:Transforms>
                    <ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
                    <ds:DigestValue>ABCDmhUOmjhBRPabcdB1wni53mabcdOzRMo3ABCDVbw=</ds:DigestValue>
                </ds:Reference>
            </ds:SignedInfo>
            <ds:SignatureValue>...</ds:SignatureValue>
            <ds:KeyInfo Id="KI-1">
                <wsse:SecurityTokenReference wsu:Id="STR-1">
                    <wsse:Reference URI="#X509-1" ValueType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3"/>
                </wsse:SecurityTokenReference>
            </ds:KeyInfo>
        </ds:Signature>
    </wsse:Security>
</soapenv:Header>
<soapenv:Body>
    ...
</soapenv:Body>

这就是 XML 的意思:

So this is what the XML is saying:

  1. 需要创建带有 nonce 的密码摘要.
  2. 需要包含 BinarySecurityToken 的 Base64 表示.
  3. 时间戳需要通过 xml-exc-c14n 规范进行规范化(只是该部分被拉出并重新格式化),确保在标头中包含命名空间 wsse、ns1、soapenv 和 xsd.
  4. 该时间戳部分需要经过 SHA256 哈希处理并添加到 SignedInfo 部分的 DigestValue 字段中.
  5. 带有新 DigestValue 的 SignedInfo 部分需要规范化,确保包含命名空间 ns1、soapenv 和 xsd.
  6. 签名信息需要经过 SHA256 哈希处理,然后进行 RSA 加密,并将结果添加到 SignatureValue 字段中.

自定义消息标题

通过注入自定义消息头,我可以将我想要的任何 xml 写入请求的头中.这篇文章为我指明了正确的方向 https://stackoverflow.com/a/39090724/6077517

By injecting a custom message header I can write out any xml I want into the header of the request. This post pointed me in the right direction https://stackoverflow.com/a/39090724/6077517

这是我使用的标题:

class CustomSecurityHeader : MessageHeader
{
    // This is data I'm passing into my header from the MessageInspector 
    // that will be used to create the security header contents
    public HeaderData HeaderData { get; set; }

    // Name of the header
    public override string Name
    {
        get { return "Security"; }
    }

    // Header namespace
    public override string Namespace
    {
        get { return "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"; }
    }

    // Additional namespace I needed
    public string wsuNamespace
    {
        get { return "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"; }
    }

    // This is where the start tag of the header gets written
    // add any required namespaces here
    protected override void OnWriteStartHeader(XmlDictionaryWriter writer, MessageVersion messageVersion)
    {
        writer.WriteStartElement("wsse", Name, Namespace);
        writer.WriteXmlnsAttribute("wsse", Namespace);
        writer.WriteXmlnsAttribute("wsu", wsuNamespace);
    }

    // This is where the header content will be written into the request
    protected override void OnWriteHeaderContents(XmlDictionaryWriter writer, MessageVersion messageVersion)
    {
        XmlDocument xmlDoc = MyCreateSecurityHeaderFunction(HeaderData); // My function that creates the security header contents.
        var securityElement = doc.FirstChild; // This is the "<security.." portion of the xml returned
        foreach(XmlNode node in securityElement.ChildNodes)
        {
            writer.WriteNode(node.CreateNavigator(), false);
        }
        return;
    }
}

消息检查器

为了将标头放入请求中,我覆盖了 MessageInspector 类.这几乎可以让您在插入标头和传输消息之前更改有关请求的任何内容.

To get the header into the request I override the MessageInspector class. This pretty much lets you change anything about the request that you want before headers are inserted and message transmitted.

这里有一篇关于它的好文章,它使用此方案将用户名密码随机数添加到消息中:https://weblog.west-wind.com/posts/2012/nov/24/wcf-wssecurity-and-wse-nonce-authentication

There's a good article about it here that uses this scheme to add a Username Password Nonce to a message: https://weblog.west-wind.com/posts/2012/nov/24/wcf-wssecurity-and-wse-nonce-authentication

您必须创建一个自定义 EndpointBehavior 来注入检查器.

You have to create a custom EndpointBehavior to inject the inspector.

public class CustomInspectorBehavior : IEndpointBehavior
{
    // Data I'm passing to my EndpointBehavior that will be used to create the security header
    public HeaderData HeaderData
    {
        get { return this.messageInspector.HeaderData; }
        set { this.messageInspector.HeaderData = value; }
    }

    // My custom MessageInspector class
    private MessageInspector messageInspector = new MessageInspector();

    public void AddBindingParameters(ServiceEndpoint endpoint, System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
    {
    }

    public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
    {
    }

    public void Validate(ServiceEndpoint endpoint)
    {
    }

    public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
    {
        // Add the custom message inspector here
        clientRuntime.MessageInspectors.Add(messageInspector);
    }
}

这是我的消息检查器的代码:

And here's the code for my message inspector:

public class MessageInspector : IClientMessageInspector
{
    // Data to be used to create the security header
    public HeaderData HeaderData { get; set; }

    public void AfterReceiveReply(ref System.ServiceModel.Channels.Message reply, object correlationState)
    {
        var lastResponseXML = reply.ToString(); // Not necessary but useful for debugging if you want to see the response.
    }

    public object BeforeSendRequest(ref System.ServiceModel.Channels.Message request, System.ServiceModel.IClientChannel channel)
    {
        // This might not be necessary for your case but I remove a bunch of unnecessary WCF-created headers from the request.
        List<string> removeHeaders = new List<string>() { "Action", "VsDebuggerCausalityData", "ActivityId" };
        for (int h = request.Headers.Count() - 1; h >= 0; h--)
        {
            if (removeHeaders.Contains(request.Headers[h].Name))
            {
                request.Headers.RemoveAt(h);
            }
        }

        // Make changes to the request.
        // For this case I'm adding/renaming namespaces in the header.
        var container = XElement.Parse(request.ToString()); // Parse request into XElement
        // Change "s" namespace to "soapenv"
        container.Add(new XAttribute(XNamespace.Xmlns + "soapenv", "http://schemas.xmlsoap.org/soap/envelope/"));
        container.Attributes().Where(a => a.Name.LocalName == "s").Remove();
        // Add other missing namespace
        container.Add(new XAttribute(XNamespace.Xmlns + "ns1", "http://somewebsite.com/"));
        container.Add(new XAttribute(XNamespace.Xmlns + "xsd", "http://anotherwebsite.com/xsd"));
        requestXml = container.ToString();

        // Create a new message out of the updated request.
        var ms = new MemoryStream();
        var sr = new StreamWriter(ms);
        var writer = new StreamWriter(ms);
        writer.Write(requestXml);
        writer.Flush();
        ms.Position = 0;

        var reader = XmlReader.Create(ms);
        request = Message.CreateMessage(reader, int.MaxValue, request.Version);

        // Add my custom security header
        // This is responsible for writing the security headers to the message
        CustomSecurityHeader header = new CustomSecurityHeader();
        // Pass data required to build security header
        header.HeaderData = new HeaderData()
        {
            Certificate = this.HeaderData.Certificate,
            Username = this.HeaderData.Username,
            Password = this.HeaderData.Password
            // ... Whatever else might be needed
        };

        // Add custom header to request headers
        request.Headers.Add(header);

        return request;
    }
}

向客户端代理添加消息检查器

我保持绑定非常简单,因为我自己添加了所有安全性内容并且不希望添加任何意外的标头.

I kept my binding pretty simple since I'm adding all the security stuff myself and didn't want any unexpected headers added.

// IMPORTANT - my service required TLS 1.2, add this to make that happen
System.Net.ServicePointManager.SecurityProtocol = System.Net.SecurityProtocolType.Tls12;

// Encoding
var encoding = new TextMessageEncodingBindingElement();
encoding.MessageVersion = MessageVersion.Soap11;

// Transport
var transport = new HttpsTransportBindingElement();

CustomBinding binding = new CustomBinding();
binding.Elements.Add(encoding);
binding.Elements.Add(transport);

var myProxy = new MyProxyClass(binding, new EndpointAddress(endpoint));

// Add message inspector behavior to alter security header.
// data contains info to create the header such as username, password, certificate, etc.
MessageInspector = new CustomInspectorBehavior() { HeaderData = data }; 
myProxy.ChannelFactory.Endpoint.EndpointBehaviors.Add(MessageInspector);

创建安全标头 XML

这有点难看,但我最终做的是为安全标头的规范化部分创建 XML 模板,填充值,对 SignedInfo 部分进行适当的散列和签名,然后将这些部分组合成一个完整的安全标头.我更愿意在代码中构建它们,但 XmlDocument 不会维护我添加的属性的顺序,这会弄乱我的规范化 XML 和我的签名,所以我保持简单.

This is kind of ugly but what I ended up doing was to create XML templates of canonicalized sections of the security header, filling in the values, hashing and signing the SignedInfo section appropriately, then combining the pieces into a full security header. I would have preferred to build them up in code but XmlDocument wouldn't maintain the order of the attributes I was adding which was messing up my canonicalized XML and my signature, so I kept it simple.

为了确保我的部分正确规范化,我使用了一个名为 SC14N 的工具 https://www.cryptosys.net/sc14n/index.html.我输入了一个示例 XML 请求和一个对我想要规范化的部分的引用以及任何包含的命名空间,它返回了适当的 XML.我将它返回的 XML 保存到模板中,用稍后可以替换的标签替换值和 ID.我为 Timestamp 部分创建了一个模板,为 SignedInfo 部分创建了一个模板,并为整个 Security 标头部分创建了一个模板.

To make sure my sections were canonicalized correctly, I used a tool called SC14N https://www.cryptosys.net/sc14n/index.html. I entered in a sample XML request and a reference to the section I wanted canonicalized along with any included namespaces and it returned the appropriate XML. I saved the XML it returned into a template replacing the values and ID's with tags I could replace later. I created a template for the Timestamp section, a template for the SignedInfo section, and a template for the entire Security header section.

间距当然很重要,因此请确保 xml 保持未格式化,如果您正在加载 XmlDocument,确保将 PreserveWhitespace 设置为 true 总是一个好主意:

Spacing is of course important, so make sure the xml remains unformatted and if you're loading an XmlDocument it's always a good idea to make sure to set PreserveWhitespace to true:

XmlDocument doc = new XmlDocument() { PreserveWhitespace = true;}

所以现在我将我的模板保存在资源中,当我需要签署我的时间戳时,我将时间戳模板加载到一个字符串中,用正确的时间戳 ID、创建和过期字段替换标签,所以我有类似的东西这(具有适当的命名空间,当然没有换行符):

So now I have my templates saved in resources, when I need to sign my Timestamp, I load the timestamp template into a string, replace the tags with the proper Timestamp ID, Created, and Expires fields, so I have something like this (with proper namespaces and without line breaks of course):

<wsu:Timestamp xmlns:ns1="..." xmlns:soapenv="..." xmlns:wsse=".." xmlns:wsu=".." wsu:Id="TI-3">
    <wsu:Created>2019-05-07T21:31:00Z</wsu:Created>
    <wsu:Expires>2019-05-07T21:36:00Z</wsu:Expires>
</wsu:Timestamp>

然后获取哈希:

// Get hash of timestamp.
SHA256Managed shHash = new SHA256Managed();
var fileBytes = System.Text.Encoding.UTF8.GetBytes(timestampXmlString);
var hashBytes = shHash.ComputeHash(fileBytes);
var digestValue = Convert.ToBase64String(hashBytes);

接下来我需要我的 SignedInfo 部分的模板.我从我的资源中提取它,并替换适当的标签(在我的例子中是时间戳参考 ID 和上面计算的时间戳digestValue),然后我得到该 SignedInfo 部分的哈希:

Next I need a template of my SignedInfo section. I pull that from my resources, and replace the appropriate tags (in my case the timestamp reference ID and the timestamp digestValue calculated above), then I get the hash of that SignedInfo section:

// Get hash of the signed info
SHA256Managed shHash = new SHA256Managed();
fileBytes = System.Text.Encoding.UTF8.GetBytes(signedInfoXmlString);
hashBytes = shHash.ComputeHash(fileBytes);
var signedInfoHashValue = Convert.ToBase64String(hashBytes);

然后我对签名信息的散列进行签名以获得签名:

Then I sign the hash of the signed info to get the signature:

using (var rsa = MyX509Certificate.GetRSAPrivateKey())
{
    var signatureBytes = rsa.SignHash(hashBytes, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
    SignatureValue = Convert.ToBase64String(signatureBytes); // This is my signature!
}

如果失败,请确保您的证书设置正确,它还应该有一个私钥.如果您运行的是旧版本的框架,您可能需要跳过一些步骤才能获得 RSA 密钥.请参阅 https://stackoverflow.com/a/38380835/6077517

If this fails make sure your certificate is setup correctly, it should also have a private key. If you're running an older version of the framework you may have to jump through some hoops to get the RSA key. See https://stackoverflow.com/a/38380835/6077517

用户名密码摘要随机数

我不必签署用户名,但我必须计算密码摘要.它被定义为 Base64(SHA1(Nonce + CreationTime + Password)).

I didn't have to sign the username but I had to calculate the password digest. It's defined as Base64( SHA1(Nonce + CreationTime + Password) ).

    // Create nonce
    SHA1CryptoServiceProvider sha1Hasher = new SHA1CryptoServiceProvider();
    var nonce = Guid.NewGuid().ToString("N");
    var nonceHash = sha1Hasher.ComputeHash(Encoding.UTF8.GetBytes(nonce));
    var NonceValue = Convert.ToBase64String(nonceHash);

    var NonceCreatedTime = DateTimeOffset.UtcNow.ToString("yyyy-MM-ddThh:mm:ss.fffZ");

    // Create password digest Base64( SHA1(Nonce + Created + Password) )
    var nonceBytes = Convert.FromBase64String(NonceValue); // Important - convert from Base64
    var createdBytes = Encoding.UTF8.GetBytes(NonceCreatedTime);
    var passwordBytes = Encoding.UTF8.GetBytes(Password);

    var concatBytes = new byte[nonceBytes.Length + createdBytes.Length + passwordBytes.Length];
    System.Buffer.BlockCopy(nonceBytes, 0, concatBytes, 0, nonceBytes.Length);
    System.Buffer.BlockCopy(createdBytes, 0, concatBytes, nonceBytes.Length, createdBytes.Length);
    System.Buffer.BlockCopy(passwordBytes, 0, concatBytes, nonceBytes.Length + createdBytes.Length, passwordBytes.Length);

    // Hash the combined buffer
    var hashedConcatBytes = sha1Hasher.ComputeHash(concatBytes);
    var PasswordDigest = Convert.ToBase64String(hashedConcatBytes);

就我而言,有一个额外的问题,即密码需要进行 SHA1 哈希处理.如果您在 SoapUI 中设置 WS-Security 用户名,这就是 SoapUI 所说的PasswordDigest Ext".请记住,如果您仍然遇到身份验证问题,我花了很长时间才意识到我需要先对密码进行哈希处理.

In my case there was an extra gotcha that the Password needed to be SHA1 hashed. That's what SoapUI calls "PasswordDigest Ext" if you're setting up a WS-Security Username in SoapUI. Keep that in mind if you're still having authentication problems, I spent way to much time before realizing that I needed to hash my password first.

还有一件事我不知道该怎么做,这里是如何从您的 X509 证书中获取 Base64 二进制安全令牌值:

One more thing I didn't know how to do, here's how to get the Base64 Binary Security Token Value from your X509 certificate:

var bstValue = Convert.ToBase64String(myCertificate.Export(X509ContentType.Cert));

最后,我从资源中提取我的 Security 标头模板并替换我收集或计算的所有相关值:UsernameTokenId、Username、Password Digest、Nonce、UsernameToken 创建时间、Timestamp 字段、BinarySecurityToken 和 BinarySecurityTokenID(确保此 ID 也是在 KeyInfo 部分中引用)、时间戳摘要、ID,最后是我的签名.关于 ID 的说明,我认为这些值并不重要,只要它们在文档中是唯一的,只要确保它们是相同的 ID,如果它们在请求中的其他地方被引用,请查找#"签名.

Finally I pull my Security header template from resources and replace all the relevant values that I collected or calculated: UsernameTokenId, Username, Password Digest, Nonce, UsernameToken Created time, Timestamp fields, BinarySecurityToken and BinarySecurityTokenID (make sure this ID is also referenced in the KeyInfo section), Timestamp Digest, ID's and finally my Signature. A note on ID's, I don't think the values matter as long as they're unique in the document, just make sure they're the same ID's if they're being referenced elsewhere in the request, look for the '#' sign.

已编译的 XML 安全标头字符串被加载到 XmlDocument 中(记住保留空格)并传递给自定义 MessageHeader 以在 CustomHeader.OnWriteHeaderContents 中序列化(参见上面的 CustomHeader).

The compiled security header string of XML is what gets loaded into an XmlDocument (remember to preserve whitespace) and passed to the custom MessageHeader to be serialized out in the CustomHeader.OnWriteHeaderContents (see CustomHeader above).

哇.希望这会为某人节省大量工作,为打字错误或无法解释的步骤道歉.如果有人想出一个办法,我很想看到一个优雅的纯 WCF 实现.

Whew. Hopefully this will save somebody a lot of work, apologies for typos or unexplained steps. I would LOVE to see an elegant pure-WCF implementation of all this if anybody has figured one out.

这篇关于具有 WS-Security 的 WCF 服务仅需要签名时间戳的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆