【问题标题】:CosmosDb Rest SQL Query using Resource tokens使用资源令牌的 CosmosDb Rest SQL 查询
【发布时间】:2019-05-07 13:43:34
【问题描述】:

我有一个想要与 Cosmosdb 交谈的 Xamarin Forms 移动客户端 直接,我不想依赖 - 并且有开销 of - 整个 DocumentDb SDK。

由于我使用的是不受信任的客户端,因此我使用 resource tokens 验证。一切都是分区的。

出于测试目的,我已经复制了我正在尝试做的事情 REST SQL 和 DocumentClient 调用。

我已通过发出Get 成功检索到单个文档 使用 REST 和资源令牌调用。这也适用于 DocumentClient 方法。

到目前为止,一切都很好。

当我尝试实际执行 query 时,使用 DocumentClient 和资源令牌。

使用完全相同的查询和完全相同的资源标记 REST 调用产生Forbidden 结果。

The permission mode provided in the authorization token doesn't provide sufficient permissions

我在某处读到(现在找不到)你需要一位大师 使用 REST 调用进行查询的令牌。

在我发布一堆代码并编写它之前,我正在体验 预期的行为还是我实际上应该能够使用 REST 进行查询 来电?

提前致谢。

** 更新 #2 与 GITHUB 存储库的链接**

https://github.com/nhwilly/DocumentClientVsRest.git

使用代码示例更新

using System;
using System.Diagnostics;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using System.Web;
using Microsoft.Azure.Documents;
using Microsoft.Azure.Documents.Client;
using Newtonsoft.Json;

namespace DocClientVsRestCallTest
{
    /// <summary>
    /// The purpose of this console app is to determine why I can't get a REST call
    /// to work on a read only resource token for Azure CosmosDb.  A direct comparison
    /// using identical paths and tokens should work.  I have an issue for sure, but I
    /// can't find it.  :(
    ///
    /// To run this, you need to have already created a partitioned CosmosDb collection.
    /// </summary>
    class Program
    {
        public static string dbServicePath = $"https://[YOUR-COSMOS-ACCOUNT-NAME].documents.azure.com";
        public static string databaseId = "[YOUR-DATABASE-ID]";
        public static string collectionId = "[YOUR-COLLECTION-ID]";
        public static string datetime = DateTime.UtcNow.ToString("R");
        public static string version = "2018-06-18";
        public static string resourceId = $"dbs/{databaseId}/colls/{collectionId}";
        public static string urlPath = $"{dbServicePath}/{resourceId}/docs";
        public static string partitionKey = $"TestPartition";
        public static string documentId = $"TestDocumentId";
        public static string queryString = $"select * from c where c.id='{documentId}' and c.partitionId ='{partitionKey}'";
        public static string userId = $"TestUser";
        public static string permissionToken = string.Empty;

        // the master key is supplied to create permission tokens and simulate the server only.
        public static string masterKey = $"[YOUR-MASTER-KEY]";

        static void Main(string[] args)
        {
            Debug.WriteLine("Starting...");

            // let's make sure we get a readonly token for the user/partition in question.
            permissionToken =
                Task.Run(async () => await GetPermissionToken()).GetAwaiter().GetResult();


            QueryUsingSdk();

            Task.Run(async () => await QueryUsingRest()).ConfigureAwait(false);

            Task.Run(async ()=> await CleanUp()).ConfigureAwait(false);

            Console.WriteLine("finished...");
            Console.ReadKey();
        }

        static async Task QueryUsingRest()
        {
            Uri uri = new Uri(urlPath);
            HttpClient client = new HttpClient();

            var encodedToken =
                HttpUtility.UrlEncode(permissionToken);

            string partitionAsJsonArray =
                JsonConvert.SerializeObject(new[] { partitionKey });

            client.DefaultRequestHeaders.Add("x-ms-date", datetime);
            client.DefaultRequestHeaders.Add("x-ms-documentdb-isquery", "True");
            client.DefaultRequestHeaders.Add("x-ms-documentdb-query-enablecrosspartition", "False");
            client.DefaultRequestHeaders.Add("x-ms-documentdb-query-iscontinuationexpected", "False");
            client.DefaultRequestHeaders.Add("x-ms-documentdb-partitionkey", partitionAsJsonArray);
            client.DefaultRequestHeaders.Add("authorization", encodedToken);
            client.DefaultRequestHeaders.Add("Cache-Control", "no-cache");
            client.DefaultRequestHeaders.Add("x-ms-version", version);
            client.DefaultRequestHeaders.Accept
                .Add(new MediaTypeWithQualityHeaderValue("application/json"));

            var content =
                new StringContent(JsonConvert.SerializeObject(new { query = queryString }), Encoding.UTF8, "application/query+json");

            HttpResponseMessage response =
               await client.PostAsync(urlPath, content).ConfigureAwait(false);

            if (!response.IsSuccessStatusCode)
            {
                await DisplayErrorMessage(response).ConfigureAwait(false);
            }
            else
            {
                Debug.WriteLine($"Success {response.StatusCode}!");
                var jsonString =
                    await response.Content.ReadAsStringAsync().ConfigureAwait(false);
            }

        }


        static void QueryUsingSdk()
        {

            var docClient =
                new DocumentClient(new Uri(dbServicePath), permissionToken);

            var feedOptions =
                new FeedOptions { PartitionKey = new PartitionKey(partitionKey) };

            var result =
                docClient
                    .CreateDocumentQuery(UriFactory.CreateDocumentCollectionUri(databaseId, collectionId), queryString,
                        feedOptions)
                    .ToList().First();


            Debug.WriteLine($"SDK result: {result}");
        }


        /// <summary>
        /// This method simulates what would happen on the server during an authenticated
        /// request.  The token (and other permission info) would be returned to the client.
        /// </summary>
        /// <returns></returns>
        static async Task<string> GetPermissionToken()
        {
            string token = string.Empty;
            try
            {
                var docClient =
                    new DocumentClient(new Uri(dbServicePath), masterKey);

                var userUri =
                        UriFactory.CreateUserUri(databaseId, userId);

                // delete the user if it exists...
                try
                {
                    await docClient.DeleteUserAsync(userUri).ConfigureAwait(false);
                }
                catch (Exception e)
                {
                    Debug.WriteLine($"Delete user error: {e.Message}");

                }

                // create the user
                var dbUri =
                    UriFactory.CreateDatabaseUri(databaseId);

                await docClient.CreateUserAsync(dbUri, new User { Id = userId }).ConfigureAwait(false);

                // create the permission
                var link =
                    await docClient
                        .ReadDocumentCollectionAsync(UriFactory.CreateDocumentCollectionUri(databaseId, collectionId))
                        .ConfigureAwait(false);

                var resourceLink =
                    link.Resource.SelfLink;

                var permission =
                    new Permission
                    {
                        Id = partitionKey,
                        PermissionMode = PermissionMode.Read,
                        ResourceLink = resourceLink,
                        ResourcePartitionKey = new PartitionKey(partitionKey)
                    };


                await docClient.CreatePermissionAsync(userUri, permission).ConfigureAwait(false);

                // now create a document that should be returned when we do the query
                var doc = new { id = documentId, partitionId = partitionKey, message = "Sample document for testing" };
                try
                {
                    await docClient.DeleteDocumentAsync(UriFactory.CreateDocumentUri(databaseId, collectionId,
                        documentId), new RequestOptions { PartitionKey = new PartitionKey(partitionKey) }).ConfigureAwait(false);


                }
                catch (Exception e)
                {
                    Debug.WriteLine($"Test document not found to delete - this is normal.");
                }

                try
                {
                    var document = await docClient
                        .CreateDocumentAsync(UriFactory.CreateDocumentCollectionUri(databaseId, collectionId), doc)
                        .ConfigureAwait(false);
                }
                catch (Exception e)
                {
                    Debug.WriteLine($"Create document message: {e.Message}");
                }

                // now read the permission back as it would happen on the server
                var result = await docClient.ReadPermissionFeedAsync(userUri).ConfigureAwait(false);
                if (result.Count > 0)
                {
                    token = result.First(c => c.Id == partitionKey).Token;
                }


            }
            catch (Exception ex)
            {
                Debug.WriteLine($"Create and get permission failed: {ex.Message}");
            }

            if (string.IsNullOrEmpty(token))
            {
                Debug.WriteLine("Did not find token");
            }
            return token;
        }

        static async Task CleanUp()
        {
            var docClient =
                new DocumentClient(new Uri(dbServicePath), masterKey);

            var doc = new { id = documentId, partitionId = partitionKey, message = "Sample document for testing" };
            try
            {
                await docClient.DeleteDocumentAsync(UriFactory.CreateDocumentUri(databaseId, collectionId,
                    documentId), new RequestOptions { PartitionKey = new PartitionKey(partitionKey) }).ConfigureAwait(false);


            }
            catch (Exception e)
            {
                Debug.WriteLine($"Delete document message: {e.Message}");
            }

        }

        static async Task DisplayErrorMessage(HttpResponseMessage response)
        {
            var messageDefinition =
                new
                {
                    code = "",
                    message = ""
                };

            var jsonString =
                await response.Content.ReadAsStringAsync().ConfigureAwait(false);

            var message =
                JsonConvert.DeserializeAnonymousType(jsonString, messageDefinition);

            Debug.WriteLine($"Failed with {response.StatusCode} : {message.message}");

        }

    }
}

【问题讨论】:

    标签: azure xamarin.forms azure-cosmosdb


    【解决方案1】:

    我正在经历预期的行为,或者我实际上应该能够 使用 REST 调用进行查询?

    是的,您可以使用资源令牌在 rest 调用中查询文档。请参考我的示例 rest java 代码如下:

    import org.apache.commons.codec.binary.Base64;
    import org.json.JSONArray;
    import org.json.JSONObject;
    
    import javax.crypto.Mac;
    import javax.crypto.spec.SecretKeySpec;
    import java.io.BufferedReader;
    import java.io.InputStreamReader;
    import java.io.OutputStream;
    import java.net.HttpURLConnection;
    import java.net.URL;
    import java.net.URLEncoder;
    import java.text.SimpleDateFormat;
    import java.util.Calendar;
    import java.util.TimeZone;
    
    public class QueryDocumentsRest {
    
        private static final String account = "***";
    
        private static final String query = "select * from c";
    
        public static void main(String args[]) throws Exception {
    
            String urlString = "https://" + account + ".documents.azure.com/dbs/db/colls/coll/docs";
            HttpURLConnection connection = (HttpURLConnection) (new URL(urlString)).openConnection();
    
            JSONObject jsonObject = new JSONObject();
            jsonObject.put("query",query);
            JSONArray jsonArray = new JSONArray();
            jsonObject.put("parameters",jsonArray);
            byte[] data = (jsonObject.toString()).getBytes("UTF-8");
    
            connection.setRequestMethod("POST");
            connection.setRequestProperty("x-ms-version", "2017-02-22");
            connection.setRequestProperty("x-ms-documentdb-isquery", "true");
            //connection.setRequestProperty("x-ms-documentdb-query-enablecrosspartition", "true");
    
            connection.setRequestProperty("Content-Type", "application/query+json");
            System.out.println(data.length);
            connection.setRequestProperty("Content-Length", String.valueOf(data.length));
    
            SimpleDateFormat fmt = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss");
            fmt.setTimeZone(TimeZone.getTimeZone("GMT"));
            String date = fmt.format(Calendar.getInstance().getTime()) + " GMT";
            String auth = getAuthenticationString();
    
            connection.setRequestProperty("x-ms-date", date);
            connection.setRequestProperty("Authorization", auth);
    
            connection.setDoOutput(true);
            OutputStream os = connection.getOutputStream();
    
            os.write(data);
            os.flush();
            os.close();
    
            System.out.println("Response message : " + connection.getResponseMessage());
            System.out.println("Response code : " + connection.getResponseCode());
            System.out.println(connection.getHeaderField("x-ms-request-charge"));
    
    
            BufferedReader br = null;
            if (connection.getResponseCode() != 200) {
                br = new BufferedReader(new InputStreamReader((connection.getErrorStream())));
            } else {
                br = new BufferedReader(new InputStreamReader((connection.getInputStream())));
            }
            System.out.println("Response body : " + br.readLine());
        }
    
    
        private static String getAuthenticationString() throws Exception {
            String auth = "type=resource&ver=1&sig=***";
            auth = URLEncoder.encode(auth);
            System.out.println("authString:" + auth);
            return auth;
        }
    
    }
    

    我在测试过程中将权限模式设置为All

    【讨论】:

    • 谢谢。我想知道...我的权限设置为只读。我今天会尝试将其设置为 ALL 并让您知道。谢谢,杰。
    • @nhwilly 好的,等你回复。
    • 我使用 LinqPad 创建了两个相同的请求,一个使用 DocumentClient,另一个使用 REST。使用相同的令牌和相同的查询,我使用 Fiddler 查看对 CosmosDb 的 POST 请求。我检查了 RAW 内容,它们是逐字节相同的。一个成功,另一个失败。在这里不知所措......
    • @nhwilly 好吧,也许你需要添加你的测试代码让我们帮助你。
    • 我明天试试。谢谢。都是 c#。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-04-07
    • 2015-03-16
    • 1970-01-01
    • 2019-05-23
    • 1970-01-01
    相关资源
    最近更新 更多