azure cosmosdb - Cosmos DB REST API using Resource Token gives 403 Forbidden - Stack Overflow

时间: 2025-01-06 admin 业界

A resource token is created in cosmos db and when that is used to do REST API Call, CosmosDb gives 403 Unauthorized error. The same resource token when used in CosmosDb donot sdk, it works. So definitely something is wrong in the way i am doing REST API call. Here is the code.

using Microsoft.Azure.Cosmos;
using System.Net.Http.Formatting;

namespace CosmosDb_Rest_DotNet
{
    internal class Program
    {
        static readonly string endpoint = "https://localhost:8081/";
        static readonly string masterKey = "C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==";
        static readonly Uri baseUri = new Uri(endpoint);        

        static readonly string databaseId = "SampleDb";
        static readonly string collectionId = "Lists";
        static readonly string documentId = "cbd77fac-f7f0-46fb-a2f3-f6b48c11670c";
        static readonly string partitionKey = "MyPartitionKey";
        static readonly string userId = "userId";

        static readonly string utc_date = DateTime.UtcNow.ToString("r");

        static void Main(string[] args)
        {
            Console.WriteLine(DateTime.UtcNow.ToString("r"));
            string resourceToken = "";

            using (var cosmosClient = new CosmosClient(endpoint, masterKey))
            {
                Database cosmosdb = cosmosClient.GetDatabase(databaseId);
                var listsContainer = cosmosdb.GetContainer(collectionId);


                //Create user
                try
                {
                    var result = cosmosdb.CreateUserAsync(userId).Result;
                }
                catch (Exception)
                {
                    //user already exists
                }
                var cosmosUser = cosmosdb.GetUser(userId);

                Console.WriteLine($"Created Cosmos User: {cosmosUser.Id}");

                //create permission and get ResourceToken. Resource Token permission is restricted to Partition Key
                var listsContainerPermissionProps = new PermissionProperties($"{listsContainer.Id}-{cosmosUser.Id}",
                                                                            PermissionMode.All,
                                                                            listsContainer,
                                                                            new PartitionKey(partitionKey));

                var listsContainerPermissionResponse = cosmosUser.UpsertPermissionAsync(listsContainerPermissionProps, (int)TimeSpan.FromMinutes(30).TotalSeconds).Result;

                resourceToken = listsContainerPermissionResponse.Resource.Token;

                Console.WriteLine("Successfully received Resource Token.");
            }

            Console.WriteLine("Using Resource Token in CosmosClient SDK to query document.");

            //TEST resourceToken using Cosmos SDK
            using (CosmosClient resoureceTokenCosmosClient = new CosmosClient(endpoint, resourceToken))
            {
                Database resourceTokenCosmosDb = resoureceTokenCosmosClient.GetDatabase(databaseId);
                var resourceTokenListsContainer = resourceTokenCosmosDb.GetContainer(collectionId);

                var query = new QueryDefinition("SELECT * FROM c where c.Aid = 'MyPartitionKey'");
                var queryResponse = resourceTokenListsContainer.GetItemQueryIterator<dynamic>(query, requestOptions: new QueryRequestOptions { PartitionKey = new PartitionKey("MyPartitionKey") });

                while (queryResponse.HasMoreResults)
                {
                    var items = queryResponse.ReadNextAsync().Result;

                    foreach (var item in items)
                    {
                        Console.WriteLine($"Received Item using CosmosClient with resource token: Name: {item.Name}, id: {item.id}");
                    }
                }
                Console.WriteLine("Resource Token Successfully worked and returned documents");
            }

            Console.WriteLine("Using same Resource Token in REST API Test");

            //REST Client TEST
            using (var client = new System.Net.Http.HttpClient())
            {
                string response = string.Empty;
                string authHeader = string.Empty;
                string verb = string.Empty;
                string resourceType = string.Empty;
                string resourceId = string.Empty;
                string resourceLink = string.Empty;

                client.DefaultRequestHeaders.Add("x-ms-date", DateTime.UtcNow.ToString("r"));
                client.DefaultRequestHeaders.Add("x-ms-version", "2020-07-15");
                client.DefaultRequestHeaders.Add("x-ms-documentdb-isquery", "True");

                try
                {
                    //EXECUTE a query
                    verb = "POST";
                    resourceType = "docs";
                    resourceLink = string.Format("dbs/{0}/colls/{1}/docs", databaseId, collectionId);

                    //url encode resource token
                    authHeader = Uri.EscapeDataString(resourceToken);

                    client.DefaultRequestHeaders.Remove("authorization");
                    client.DefaultRequestHeaders.Add("authorization", authHeader);                    

                    var qry = new SqlQuerySpec { query = "SELECT * FROM c where c.Aid = 'MyPartitionKey'" };
                    var r = client.PostWithNoCharSetAsync(new Uri(baseUri, resourceLink), qry).Result;

                    Console.WriteLine(r.Content.ReadAsStringAsync().Result);
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.ToString());
                }
            }
        }

        private static string GenerateMasterKeyAuthorizationSignature(string verb, string resourceId, string resourceType, string key, string keyType, string tokenVersion)
        {
            var hmacSha256 = new System.Security.Cryptography.HMACSHA256 { Key = Convert.FromBase64String(key) };

            string payLoad = string.Format(System.Globalization.CultureInfo.InvariantCulture, "{0}\n{1}\n{2}\n{3}\n{4}\n",
                    verb.ToLowerInvariant(),
                    resourceType.ToLowerInvariant(),
                    resourceId,
                    utc_date.ToLowerInvariant(),
                    ""
            );

            byte[] hashPayLoad = hmacSha256.ComputeHash(System.Text.Encoding.UTF8.GetBytes(payLoad));
            string signature = Convert.ToBase64String(hashPayLoad);

            return System.Web.HttpUtility.UrlEncode(String.Format(System.Globalization.CultureInfo.InvariantCulture, "type={0}&ver={1}&sig={2}",
                keyType,
                tokenVersion,
                signature));
        }
    }

    //This is used when executing a query via REST
    //DocumentDB expects a specific Content-Type for queries
    //When setting the Content-Type header it must not have a charset, currently. 
    //This custom class sets the correct Content-Type and clears the charset
    public class NoCharSetJsonMediaTypeFormatter : JsonMediaTypeFormatter
    {
        public override void SetDefaultContentHeaders(Type type, System.Net.Http.Headers.HttpContentHeaders headers, System.Net.Http.Headers.MediaTypeHeaderValue mediaType)
        {
            base.SetDefaultContentHeaders(type, headers, new System.Net.Http.Headers.MediaTypeHeaderValue("application/query+json"));
            headers.ContentType.CharSet = "";
        }
    }

    //A custom extension of HttpClient that adds a new PostWithNoCharSet async method
    //that uses the custom MediaTypeFormatter class to post with the correct Content-Type header
    public static class HttpClientExtensions
    {
        public static async Task<HttpResponseMessage> PostWithNoCharSetAsync<T>(this HttpClient client, Uri requestUri, T value) { return await client.PostAsync(requestUri, value, new NoCharSetJsonMediaTypeFormatter()); }
    }

    class SqlQuerySpec
    {
        public string query { get; set; }
    }
}

Here is the output

Created Cosmos User: userId
Successfully received Resource Token.
Using Resource Token in CosmosClient SDK to query document.
Received Item using CosmosClient with resource token: Name: Test Doc, id: cbd77fac-f7f0-46fb-a2f3-f6b48c11670c
Resource Token Successfully worked and returned documents
Using same Resource Token in REST API Test
{"code":"Forbidden","message":"Insufficient permissions provided in the authorization header for the corresponding request. Please retry with another authorization header.\r\nActivityId: 723c8bc1-2d11-463e-a5bc-7f3ef038e59f, Microsoft.Azure.Documents.Common/2.14.0"}

Anyone should be able to run the code for local cosmos db emulator.

最新文章