1

我有一个要在我的 Windows 应用商店应用程序中使用的服务总线中继 (WCF SOAP)。我已经编写了创建令牌的代码以及下面的客户端。

问题是我得到一个 AuthorizationFailedFault 返回一个错误字符串“InvalidSignature:令牌的签名无效。” 我想不通。

我的创建令牌方法:

private static string CreateSasToken()
{
    TimeSpan sinceEpoch = DateTime.UtcNow - new DateTime(1970,1, 1);
    var expiry = Convert.ToString((int)sinceEpoch.TotalSeconds + 3600);
    string stringToSign = webUtility.UrlEncode(ServiceUri.AbsoluteUri) + "\n" + expiry;

    string hashKey = Encoding.UTF8.GetBytes(Secret).ToString();

    MacAlgorithmProvider macAlgorithmProvider = MacAlgorithmProvider.OpenAlgorithm(MacAlgorithmNames.HmacSha256);
    BinaryStringEncoding encoding = BinaryStringEncoding.Utf8;

    var messageBuffer = CryptographicBuffer.ConvertStringToBinary(stringToSign,encoding);
    IBuffer keyBuffer = CryptographicBuffer.ConvertStringToBinary(hashKey,encoding);

    CryptographicKey hmacKey = macAlgorithmProvider.CreateKey(keyBuffer);
    IBuffer signedMessage = CryptographicEngine.Sign(hmacKey, messageBuffer);

    string signature = CryptographicBuffer.EncodeToBase64String(signedMessage);

    var sasToken = String.Format(CultureInfo.InvariantCulture,
        "SharedAccessSignature sr={0}&sig={1}&se={2}&skn={3}",
        WebUtility.UrlEncode(ServiceUri.AbsoluteUri),
        WebUtility.UrlEncode(signature), expiry, Issuer);

    return sasToken;
}

我的客户类:

    public partial class ServiceClient
    {
        public async Task<string> GetDataUsingDataContract(string item, string sasToken)
        {

            HttpClient client = new HttpClient();

            client.DefaultRequestHeaders.Add("ServiceBusAuthorization",sasToken);
            client.DefaultRequestHeaders.Add("SOAPAction",".../GetDataUsingDataContract");
            client.DefaultRequestHeaders.Add("Host", "xxxxxxxxxxx.servicebus.windows.net");

            HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post,ServiceUri);

            var content =new StringContent(@"<s:Envelope
                xmlns:s=""http://schemas.xmlsoap.org/soap/envelope/"">
                <s:Header></s:Header><s:Body>"+ item +@"</s:Body>
                </s:Envelope>",System.Text.Encoding.UTF8,"application/xml");
            request.Content = content;

            HttpResponseMessage wcfResponse = client.SendAsync(request).Result;
            HttpContent stream = wcfResponse.Content;

            var response = stream.ReadAsStringAsync();
            var returnPacket = response.Result;

            return returnPacket;
        }
    }

通过在控制台应用程序中复制由 Micorosft.ServiceBus 创建的未过期令牌,我已成功使用 Http(通过 Fiddler)使用中继。

4

1 回答 1

1

我想出了一个解决方案,这两种方法都是错误的。

CreateSasToken 方法:

一个小的更改涉及将 hashKey 变量设置为 byte[] 而不是字符串。此行: string hashKey = Encoding.UTF8.GetBytes(Secret).ToString(); 更改为: var hashKey = Encoding.UTF8.GetBytes(Secret);

这种变化意味着我需要使用不同的方法来设置 keyBuffer。此行: IBuffer keyBuffer = CryptographicBuffer.ConvertStringToBinary(hashKey,encoding); 更改为: IBuffer keyBuffer = CryptographicBuffer.CreateFromByteArray(hashKey);

所以新的 CreateSasToken 方法是:

    private static string GetSasToken()
    {
        TimeSpan sinceEpoch = DateTime.UtcNow - new DateTime(1970, 1, 1);
        var expiry = Convert.ToString((int)sinceEpoch.TotalSeconds + 3600);
        string stringToSign = WebUtility.UrlEncode(ServiceUri.AbsoluteUri) + "\n" + expiry;

        var hashKey = Encoding.UTF8.GetBytes(Secret);

        MacAlgorithmProvider macAlgorithmProvider =
            MacAlgorithmProvider.OpenAlgorithm(MacAlgorithmNames.HmacSha256);
        const BinaryStringEncoding encoding = BinaryStringEncoding.Utf8;
        var messageBuffer = CryptographicBuffer.ConvertStringToBinary(stringToSign,
            encoding);

        IBuffer keyBuffer = CryptographicBuffer.CreateFromByteArray(hashKey);
        CryptographicKey hmacKey = macAlgorithmProvider.CreateKey(keyBuffer);
        IBuffer signedMessage = CryptographicEngine.Sign(hmacKey, messageBuffer);

        string signature = CryptographicBuffer.EncodeToBase64String(signedMessage);

        var sasToken = String.Format(CultureInfo.InvariantCulture,
            "SharedAccessSignature sr={0}&sig={1}&se={2}&skn={3}",
            WebUtility.UrlEncode(ServiceUri.AbsoluteUri),
            WebUtility.UrlEncode(signature),
            expiry, Issuer);

        return sasToken;
    }

服务客户端类

这里有几点需要注意。

  1. 为了使请求生效,必须将 SAS 令牌作为 AuthenticationValueHeader 对象的参数添加到标头中。因此,我将以下方法添加到我的助手类 (ServiceBusHelper) 中,该类将 Key、KeyName 和 SasToken 作为属性,并将 CreateSasToken 作为方法。

    public static AuthenticationHeaderValue CreateBasicHeader()
    {
        return new AuthenticationHeaderValue("Basic", SasToken);
    }
    
  2. 必须以特殊方式创建 HttpRequestMessage Content 属性。获取传入的 item 参数,这是一个序列化的 WCF DataContract 类型,我需要做一些事情来制作 SOAP 信封。这里没有详细介绍它们,而是整个类(仅一种方法)。我将评论代码以立即处理响应。

    public partial class SalesNotifyServiceClient
    {
        public async Task<string> GetDataUsingDataContract(string item)
        {
            string returnPacket = "";
            string element = "";
            try
            {
                HttpClient client = new HttpClient();
    
                client.DefaultRequestHeaders.Add("ServiceBusAuthorization",
                    ServiceBusHelper.CreateBasicHeader().Parameter);
                client.DefaultRequestHeaders.Add("SOAPAction",
                    ".../GetDataUsingDataContract");
                client.DefaultRequestHeaders.Add("Host",
                    "xxxxxxxxxx.servicebus.windows.net");
    
                HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post,
                    ServiceBusHelper.ServiceUri);
    
                //Creating the request.Content
                var encodedItem = item.Replace("<", "&lt;").Replace(">", "&gt;");
    
                var strRequest =
                    @"<s:Envelope xmlns:s=""http://schemas.xmlsoap.org/soap/envelope/"">
                    <s:Header></s:Header><s:Body><GetDataUsingDataContract xmlns=
                    ""http://www.xxxxxxxxxx.com/servicemodel/relay""><item>" +
                    encodedItem + 
                    @"</item></GetDataUsingDataContract></s:Body></s:Envelope>";
    
                var content = new StringContent(strRequest,
                    System.Text.Encoding.UTF8, "application/xml");
    
                request.Content = content;
    
                HttpResponseMessage wcfResponse = client.SendAsync(request).Result;
                HttpContent stream = wcfResponse.Content;
    
                var response = await stream.ReadAsStringAsync();
    
                //Handling the response
                XDocument doc;
                using (StringReader s = new StringReader(response))
                {
                    doc = XDocument.Load(s);
                }
    
                if (doc.Root != null)
                {
                    element = doc.Root.Value;
                }
    
                returnPacket = element;
            }
            catch (Exception e)
            {
                var message = e.Message;
            }
    
            return returnPacket;
        }
    }
    
  3. 为了获得 DataContract 对象,我必须对响应字符串做一些事情。正如您在//Handling the response上面的评论中看到的,使用 StringReader 我将返回的 SOAP 信封作为字符串加载到 XDocument 中,根值是我的序列化 DataContract 对象。然后我反序列化从该方法返回的 returnPacket 变量有我的响应对象。

于 2015-05-03T20:49:10.890 回答