使用资源令牌的 Azure Cosmos DB REST API 给出 403 Forbidden

问题描述 投票:0回答:1

在 cosmos db 中创建资源令牌,当使用该资源令牌进行 REST API 调用时,CosmosDb 会给出 403 Unauthorized 错误。 相同的资源令牌在 CosmosDb 中使用时不需要 sdk,它可以工作。 所以我执行 REST API 调用的方式肯定有问题。 这是代码。

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; }
    }
}

这是输出

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"}

任何人都应该能够运行本地 cosmos db 模拟器的代码。

azure-cosmosdb azure-cosmosdb-sqlapi
1个回答
0
投票

当调用rest api端点时,授权标头不仅仅是资源令牌。详情请参考此链接:

https://learn.microsoft.com/en-us/rest/api/cosmos-db/access-control-on-cosmosdb-resources#resource-tokens

它应该是这样的格式: 类型=资源&ver=1.0&sig=5mDuQBYA0kb70WDJoTUzSBMTG3owkC0/cEN4fqa18/s=

© www.soinside.com 2019 - 2024. All rights reserved.