Создание токена SAS служебной шины и использование ретранслятора в WinRT

У меня есть Service Bus Relay (WCF SOAP), который я хочу использовать в своем приложении для Магазина Windows. Я написал код для создания токена, а также клиента, который находится ниже.

Проблема в том, что я получаю 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;
        }
    }

Я успешно использовал Relay, используя Http (через Fiddler), скопировав неиспользованный токен, созданный Micorosft.ServiceBus, в консольное приложение.

1 ответ

Я нашел решение, в котором оба метода были неправильными.

Метод CreateSasToken:

Незначительное изменение включало установку переменной hashKey как byte[], а не string. Эта строка: 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, который представлял собой сериализованный тип DataContract WCF, мне нужно было сделать несколько вещей, чтобы сделать конверт 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, возвращенную методом, у которого был мой объект ответа.

Другие вопросы по тегам