Xamarin.forms 为什么我在使用DocumentClient时会遇到未经授权的异常,该客户端的权限是从我的Azure功能应用程序返回的?

Xamarin.forms 为什么我在使用DocumentClient时会遇到未经授权的异常,该客户端的权限是从我的Azure功能应用程序返回的?,xamarin.forms,azure-functions,azure-cosmosdb,Xamarin.forms,Azure Functions,Azure Cosmosdb,受马特·苏库普在上节课的启发,我正试图做到这一点 我有一个Xamarin表单应用程序,它使用AD B2C对用户进行身份验证。然后,该应用程序从Azure功能请求该用户对Cosmos DB的权限列表。然后将返回的权限传递给DocumentClient构造函数 但是,当我尝试将文档保存到Cosmos DB中时,我会收到一个Microsoft.Azure.Documents.UnauthorizedException,并显示消息“客户端对于请求的资源没有任何有效的令牌” 以下是Azure函数中的相关代

受马特·苏库普在上节课的启发,我正试图做到这一点

我有一个Xamarin表单应用程序,它使用AD B2C对用户进行身份验证。然后,该应用程序从Azure功能请求该用户对Cosmos DB的权限列表。然后将返回的权限传递给DocumentClient构造函数

但是,当我尝试将文档保存到Cosmos DB中时,我会收到一个
Microsoft.Azure.Documents.UnauthorizedException
,并显示消息“客户端对于请求的资源没有任何有效的令牌”

以下是Azure函数中的相关代码(为简洁起见进行了删节)。虽然目前我只请求一个权限(因此我可以检索访问令牌而不是权限列表),但应用程序将需要允许用户访问多个集合,因此需要此功能

public static class CommDiaryBroker
    {
        [FunctionName("CommDiaryBroker")]
        public static async Task<IActionResult> Run(
            [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequest req,
            [CosmosDB(databaseName:"commdiarydb",collectionName:"roster",ConnectionStringSetting="CommDiaryCosmosConnectionString")] DocumentClient client,
            ClaimsPrincipal claims, 
            ILogger log)
        {
            log.LogInformation("Requesting permissions for the commdiarydb database.");

            if(claims == null)
            {
                return new NotFoundResult();
            }

            var azpClaim = claims.Claims.SingleOrDefault(c => c.Type == "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier");

            var userId = azpClaim?.Value;

            log.LogInformation($"User Id: {userId}");

            if(string.IsNullOrEmpty(userId))
            {
                log.LogInformation("Returning not found result");
                return new NotFoundResult();
            }

            var dbName = "commdiarydb";
            var collectionname = "roster";

            List<Permission> tokens = await GetPartitionPermission(userId, client, dbName, collectionname, log);
            log.LogInformation($"Returning {tokens.Count} permissions");

            return new OkObjectResult(tokens);
        }

        static async Task<List<Permission>> GetPartitionPermission(string userId, DocumentClient client, string databaseId, string collectionId, ILogger log)
        {
            var permissionId = userId;
            Permission partitionPermission;

            Uri permissionUri = UriFactory.CreatePermissionUri(databaseId, userId, permissionId);
            Uri collectionUri = UriFactory.CreateDocumentCollectionUri(databaseId, collectionId);
            Uri userUri = UriFactory.CreateUserUri(databaseId, userId);

            try
            {
                partitionPermission = await client.ReadPermissionAsync(permissionUri);
                log.LogInformation($"Existing roster partition permission found: {partitionPermission.Id}");
            }
            catch(DocumentClientException ex)
            {
                if(ex.StatusCode == System.Net.HttpStatusCode.NotFound)
                {
                    log.LogInformation($"There wasn't an existing permission, so we create a user if one does not exist");
                    await CreateUserIfNotExistAsync(userId, client, databaseId, log);
                    Permission newPermission = new Permission
                    {
                        PermissionMode = PermissionMode.All,
                        Id = permissionId,
                        ResourceLink = collectionUri.ToString(),
                        ResourcePartitionKey = new PartitionKey(userId)    
                    };
                    partitionPermission = await client.CreatePermissionAsync(userUri, newPermission);
                }
                else
                {
                    log.LogInformation($"Something went wrong attempting to read a permission: {ex.Message}");
                }
            }

            
            
            FeedResponse<Permission> permFeed = await client.ReadPermissionFeedAsync(userUri);

            return permFeed.ToList();
        }

        static async Task CreateUserIfNotExistAsync(string userId, DocumentClient client, string databaseId, ILogger log)
        {
            try
            {
                await client.ReadUserAsync(UriFactory.CreateUserUri(databaseId, userId));
                log.LogInformation("Found existing user");
            }
            catch(DocumentClientException ex)
            {
                if(ex.StatusCode == System.Net.HttpStatusCode.NotFound)
                {
                    await client.CreateUserAsync(UriFactory.CreateDatabaseUri(databaseId), new User {Id = userId});
                    log.LogInformation("Attempted to create a user");
                }
            }
        }
这段代码深受和示例的启发

由于日志记录,我知道一个带有单个权限对象的列表正在返回到应用程序,并且权限的属性值似乎正确。似乎DocumentClient正在成功创建。在Azure门户中,我可以看到请求正在到达Cosmos DB

最初,在我的Azure Function应用程序中,我只是创建一个权限实例列表并返回它们。在参考了Azure Cosmos DB文档之后,我尝试从用户的权限提要中检索权限并返回该权限。无论哪种方式,错误都保持不变

我还确保我在应用程序代码中使用的分区键值与我创建权限时使用的分区键值相同,该分区键值将项目插入Cosmos DB集合

我已经阅读了Azure Cosmos DB和Azure Cosmos DB.NET SDK页面上的权限文档,以及其他博客和,但仍然不确定如何解决此问题

非常感谢您的任何建议或帮助

编辑:结果显示,返回的
权限
中的
resourcePartitionKey
值为空,可能是此错误的根源。但我还是不明白为什么会发生这种事

编辑2:我想出了一个解决办法

据我所知,基于此,
resourcePartitionKey
在我返回的
Permission
实例上为null的原因是HTTP响应中字符串的反序列化存在问题

因此,部分基于,我构建了一个小型的
PermissionSummary
类,以加快跨序列化边界的传输:

public class PermissionSummary
    {
        
        public string Id { get; set; }
        public string Token { get; set; }
        public string UserId { get; set; }
    }
在Azure函数中,我修改了
GetPartitionPermission()
方法以返回
任务

在方法的末尾,我从用户的权限提要编译了一个
PermissionSummary
实例列表:

List<PermissionSummary> results = new List<PermissionSummary>();
FeedResponse<Permission> permFeed = await client.ReadPermissionFeedAsync(userUri);
foreach(var permission in permFeed.ToList())
{
results.Add(new PermissionSummary
    {
        Id = permission.Id,
        Token = permission.Token,
        UserId = userId
    });
}

return results;
列表结果=新列表();
FeedResponse permFeed=wait client.ReadPermissionFeedAsync(userUri);
foreach(permFeed.ToList()中的var权限)
{
结果。添加(新权限摘要)
{
Id=permission.Id,
令牌=权限。令牌,
UserId=UserId
});
}
返回结果;
使用my Azure函数和Xamarin.Forms客户机项目中定义的
PermissionSummary
类,这些对象现在可以正确序列化和反序列化

但是,我不能将这些
PermissionSummary
实例直接传递给
DocumentClient
构造函数;它需要
权限
对象列表或访问令牌

因此,我修改了我的
InitialiseAsync()
方法,采用一个名为
permissionId
string
参数

此方法现在检索所有
PermissionSummary
实例,并根据传递给它的
permissionId
选择正确的实例。然后,它将访问令牌从该
PermissionSummary
传递到
DocumentClient
构造函数

这意味着,在尝试访问Cosmos DB的任何方法中,例如我的
UpdateRosterEntry()
方法,我现在需要为该权限构造权限id,该权限是允许登录用户访问该方法将操作的容器所必需的。这是可能的,因为我正在以标准方式构造权限ID

所以,一个似乎有效的解决办法。如果有人对能够序列化和反序列化
权限
对象有任何想法,并因此为我最初的问题提供答案,我仍然很乐意听到他们

List<PermissionSummary> results = new List<PermissionSummary>();
FeedResponse<Permission> permFeed = await client.ReadPermissionFeedAsync(userUri);
foreach(var permission in permFeed.ToList())
{
results.Add(new PermissionSummary
    {
        Id = permission.Id,
        Token = permission.Token,
        UserId = userId
    });
}

return results;