azure - 使用资源令牌的 CosmosDb Rest SQL 查询
问题描述
我有一个 Xamarin Forms 移动客户端,我想直接与 Cosmosdb 对话,我不想依赖整个 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}");
}
}
}
解决方案
我遇到了预期的行为,还是应该能够使用 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
。
推荐阅读
- android - 通过在颤动中选择一个值未更新 DropdownButton
- mysql - 如何使用 mysql 5.7.27 获取中间值(在 "" 之间)
- node.js - MongoDB - addToSet 不向数组添加元素
- time - 当前 UTC 时间的 API
- html - VSCode 为每个元素添加一个新行
- networkx - 如何在 NetworkX 中为多源 Dijkstra(或任何)最短路径计算设置初始条件
- javascript - Javascript/Typescript 相当于插件系统的 Python 入口点
- php - Magento 2 - 向 magento 类别 url 添加参数(每个类别的自定义 url)
- sql - 如何计算 SQL 数据库中两条线之间的距离?
- java - Java Spring 错误:非空属性以多对一关系引用空值或瞬态值