将应用服务向矢量数据库进行身份验证和授权

本文演示如何管理应用服务 .NET 应用程序与矢量数据库解决方案之间的连接。 其中介绍了如何对受支持的服务使用 Microsoft Entra 托管标识,以及如何安全地为其他服务存储连接字符串。

通过在应用程序中添加矢量数据库,可以为 AI 启用 [语义记忆或矢量存储](矢量存储)。 借助适用于 .NET 的 语义内核 SDK,可以使用你的首选矢量数据库解决方案轻松实现记忆存储和回忆。

先决条件

使用 Microsoft Entra 托管标识进行身份验证

如果某个矢量数据库服务支持 Microsoft Entra 身份验证,则可以将托管标识与应用服务配合使用来安全地访问矢量数据库,而无需手动预配或轮换任何机密。 有关支持 Microsoft Entra 身份验证的 Azure 服务列表,请参阅支持 Microsoft Entra 身份验证的 Azure 服务

将托管标识添加到应用服务

你的应用程序可以被授予两种类型的标识:

  • 系统分配的标识与你的应用程序相绑定,如果删除应用,标识也会被删除。 一个应用只能有一个系统分配的标识。
  • 用户分配的标识是可以分配给应用的独立 Azure 资源。 一个应用可以具有多个用户分配的标识。

添加系统分配的标识

  1. 导航到 Azure 门户中的应用页面,然后向下滚动到“设置”组。
  2. 选择“标识”。
  3. 在“系统分配”选项卡上,将“状态”切换为“开”,然后选择“保存”

运行 az webapp identity assign 命令以创建系统分配标识:

az webapp identity assign --name <appName> --resource-group <groupName>

添加用户分配的标识

若要将用户分配的标识添加到应用,请创建该标识,然后将其资源标识符添加到应用配置。

  1. 按照这些说明创建用户分配的托管标识资源。

  2. 在应用页面的左侧导航窗格中,向下滚动到“设置”组。

  3. 选择“标识”。

  4. 选择“用户分配的>添加”。

  5. 找到之前创建的标识,将其选中,然后选择“添加”。

    重要

    选择“添加”后,应用将重启。

  1. 创建用户分配的标识:

    az identity create --resource-group <groupName> --name <identityName>
    
  2. 将标识分配给应用:

    az webapp identity assign --resource-group <groupName> --name <appName> --identities <identityId>
    

将 Azure 角色添加到托管标识

  1. Azure 门户中,导航到要将矢量数据库访问权限授予到的范围。 该范围可以是“管理组”、“订阅”、“资源组”或特定的 Azure 资源
  2. 在左侧导航窗格中,选择“访问控制(IAM)”
  3. 依次选择“+ 添加”和“添加角色分配” 。
  4. 在“角色”选项卡上,选择用于授予对矢量数据库的读取访问权限的适当角色。
  5. “成员”选项卡上,选择托管标识。
  6. 在“查看 + 分配”选项卡上,选择“查看 + 分配”,以分配角色 。

资源范围

az role assignment create --assignee "<managedIdentityObjectID>" \
--role "<myVectorDbReaderRole>" \
--scope "/subscriptions/<subscriptionId>/resourcegroups/<resourceGroupName>/providers/<providerName>/<resourceType>/<resourceSubType>/<resourceName>"

资源组范围

az role assignment create --assignee "<managedIdentityObjectID>" \
--role "<myVectorDbReaderRole>" \
--scope "/subscriptions/<subscriptionId>/resourcegroups/<resourceGroupName>"

订阅范围

az role assignment create --assignee "<managedIdentityObjectID>" \
--role "<myVectorDbReaderRole>" \
--scope "/subscriptions/<subscriptionId>"

管理组范围

az role assignment create --assignee "<managedIdentityObjectID>" \
--role "<myVectorDbReaderRole>" \
--scope "/providers/Microsoft.Management/managementGroups/<managementGroupName>"

使用矢量数据库实现基于令牌的身份验证

以下代码示例需要这些附加库:

  1. 初始化 DefaultAzureCredential 对象以获取应用的托管标识:

    // Initialize a DefaultAzureCredential.
    // This credential type will try several authentication flows in order until one is available.
    // Will pickup Visual Studio or Azure CLI credentials in local environments.
    // Will pickup managed identity credentials in production deployments.
    TokenCredential credentials = new DefaultAzureCredential(
        new DefaultAzureCredentialOptions
        {
            // If using a user-assigned identity specify either:
            // ManagedIdentityClientId or ManagedIdentityResourceId.
            // e.g.: ManagedIdentityClientId = "myIdentityClientId".
        }
    );
    
  2. 为矢量数据库初始化一个 IMemoryStore 对象,然后使用它生成 ISemanticTextMemory

    // Retrieve the endpoint obtained from the Azure AI Search deployment.
    // Retrieve the endpoint and deployments obtained from the Azure OpenAI deployment.
    // Must use the deployment name not the underlying model name.
    IConfigurationRoot config = new ConfigurationBuilder().AddUserSecrets<Program>().Build();
    string searchEndpoint = config["AZURE_AISEARCH_ENDPOINT"]!;
    string openAiEndpoint = config["AZURE_OPENAI_ENDPOINT"]!;
    string embeddingModel = config["AZURE_OPENAI_EMBEDDING_NAME"]!;
    
    // The Semantic Kernel SDK provides a connector extension for Azure AI Search.
    // Initialize an AzureAISearchMemoryStore using your managed identity credentials.
    IMemoryStore memoryStore = new AzureAISearchMemoryStore(searchEndpoint, credentials);
    
    // Build a SemanticMemoryStore with Azure AI Search as the store.
    // Must also include a text embedding generation service.
    ISemanticTextMemory memory = new MemoryBuilder()
        .WithOpenAITextEmbeddingGeneration(embeddingModel, openAiEndpoint)
        .WithMemoryStore(memoryStore)
        .Build();
    
  3. 生成 Kernel 对象,然后使用 TextMemoryPlugin 导入 ISemanticTextMemory 对象:

    // Build a Kernel, include a chat completion service.
    string chatModel = config["AZURE_OPENAI_GPT_NAME"]!;
    Kernel kernel = Kernel
        .CreateBuilder()
        .AddAzureOpenAIChatCompletion(chatModel, openAiEndpoint, credentials)
        .Build();
    
    // Import the semantic memory store as a TextMemoryPlugin.
    // The TextMemoryPlugin enable recall via prompt expressions.
    kernel.ImportPluginFromObject(new TextMemoryPlugin(memory));
    
  4. 使用 Kernel 对象调用一个包含记忆回忆的提示:

    // Must configure the memory collection, number of memories to recall, and relevance score.
    // The {{...}} syntax represents an expression to Semantic Kernel.
    // For more information on this syntax see:
    // https://learn.microsoft.com/semantic-kernel/prompts/prompt-template-syntax
    string memoryCollection = config["AZURE_OPENAI_MEMORY_NAME"]!;
    string? result = await kernel.InvokePromptAsync<string>(
        "{{recall 'where did I grow up?'}}",
        new()
        {
            [TextMemoryPlugin.CollectionParam] = memoryCollection,
            [TextMemoryPlugin.LimitParam] = "2",
            [TextMemoryPlugin.RelevanceParam] = "0.79",
        }
    );
    Console.WriteLine($"Output: {result}");
    

使用 Key Vault 存储连接机密

如果某个矢量数据库不支持 Microsoft Entra 身份验证,则可以使用 Key Vault 来存储连接机密,并使用应用服务应用程序检索它们。 通过使用 Key Vault 来存储连接机密,可以与多个应用程序共享这些机密,并控制每个应用程序对单个机密的访问。

在执行这些步骤之前,请检索矢量数据库的连接字符串。 例如,请参阅将 Azure Cache for Redis 与 ASP.NET Core Web 应用配合使用

将连接字符串添加到 Key Vault

重要

在执行这些步骤之前,请确保已使用 Azure 门户创建 Key Vault

  1. Azure 门户中导航到你的密钥保管库。
  2. 在 Key Vault 左侧导航中,依次选择“对象”和“机密”
  3. 选择“+ 生成/导入”。
  4. 在“创建机密”屏幕上,选择以下值:
    • 上传选项:Manual
    • 名称:键入机密的名称。 机密名称在 Key Vault 中必须是唯一的。
    • 值:矢量数据库的连接字符串。
    • 让其他值保留默认设置。 选择创建
  5. 当收到表明机密已成功创建的消息时,就可以在应用程序中使用它了。

重要

在执行这些步骤之前,请确保已使用 Azure CLI 创建 Key Vault

  1. 通过基于角色的访问控制 (RBAC) 向用户帐户授予对密钥保管库的权限,使用 Azure CLI 命令 az role assignment create 分配角色:

    az role assignment create \
    --role "Key Vault Secrets User" \
    --assignee "<yourEmailAddress>" \
    --scope "/subscriptions/<subscriptionId>/resourceGroups/<resourceGroupName>/providers/Microsoft.KeyVault/vaults/<keyVaultName>"
    
  2. 使用 Azure CLI 命令 az keyvault secret set 将连接字符串添加到 Key Vault:

    az keyvault secret set \
    --vault-name "<keyVaultName>" \
    --name "<secretName>" \
    --value "<connectionString>"
    

授予应用服务对 Key Vault 的访问权限

  1. 将托管标识分配给应用服务
  2. Key Vault Secrets UserKey Vault Reader 角色添加到托管标识

从 Key Vault 实现连接字符串检索

若要使用以下代码示例,需要以下附加库:

这些代码示例使用 Redis 数据库,但你可以将其应用于支持连接字符串的任何矢量数据库。

  1. 初始化 DefaultAzureCredential 对象以获取应用的托管标识:

    // Initialize a DefaultAzureCredential.
    // This credential type will try several authentication flows in order until one is available.
    // Will pickup Visual Studio or Azure CLI credentials in local environments.
    // Will pickup managed identity credentials in production deployments.
    TokenCredential credentials = new DefaultAzureCredential(
        new DefaultAzureCredentialOptions
        {
            // If using a user-assigned identity specify either:
            // ManagedIdentityClientId or ManagedIdentityResourceId.
            // e.g.: ManagedIdentityClientId = "myIdentityClientId".
        }
    );
    
  2. 在生成配置时添加 Key Vault,这会将 Key Vault 机密映射到 IConfigurationRoot 对象:

    // User secrets let you provide connection strings when testing locally
    // For more info see: https://learn.microsoft.com/aspnet/core/security/app-secrets
    IConfigurationRoot config = new ConfigurationBuilder()
        .AddUserSecrets<Program>()
        .AddAzureKeyVault(new Uri("{vaultURI}"), credentials)
        .Build();
    
    // Retrieve the Redis connection string obtained from the Key Vault.
    string redisConnectionString = config["AZURE_REDIS_CONNECT_STRING"]!;
    
  3. 使用 Key Vault 中的矢量数据库连接字符串初始化 IMemoryStore 对象,然后使用该对象生成 ISemanticTextMemory

    // Use the connection string to connect to the database
    IDatabase database = (
        await ConnectionMultiplexer.ConnectAsync(redisConnectionString)
    ).GetDatabase();
    
    // The Semantic Kernel SDK provides a connector extension for Redis.
    // Initialize an RedisMemoryStore using your managed identity credentials.
    IMemoryStore memoryStore = new RedisMemoryStore(database);
    
    // Retrieve the endpoint and deployments obtained from the Azure OpenAI deployment.
    // Must use the deployment name not the underlying model name.
    string openAiEndpoint = config["AZURE_OPENAI_ENDPOINT"]!;
    string embeddingModel = config["AZURE_OPENAI_EMBEDDING_NAME"]!;
    
    // Build a SemanticMemoryStore with Azure AI Search as the store.
    // Must also include a text embedding generation service.
    ISemanticTextMemory memory = new MemoryBuilder()
        .WithOpenAITextEmbeddingGeneration(embeddingModel, openAiEndpoint)
        .WithMemoryStore(memoryStore)
        .Build();
    
  4. 生成 Kernel 对象,然后使用 TextMemoryPlugin 导入 ISemanticTextMemory 对象:

    // Build a Kernel, include a chat completion service.
    string chatModel = config["AZURE_OPENAI_GPT_NAME"]!;
    Kernel kernel = Kernel
        .CreateBuilder()
        .AddAzureOpenAIChatCompletion(chatModel, openAiEndpoint, credentials)
        .Build();
    
    // Import the semantic memory store as a TextMemoryPlugin.
    // The TextMemoryPlugin enable recall via prompt expressions.
    kernel.ImportPluginFromObject(new TextMemoryPlugin(memory));
    
  5. 使用 Kernel 对象调用一个包含记忆回忆的提示:

    // Must configure the memory collection, number of memories to recall, and relevance score.
    // The {{...}} syntax represents an expression to Semantic Kernel.
    // For more information on this syntax see:
    // https://learn.microsoft.com/semantic-kernel/prompts/prompt-template-syntax
    string memoryCollection = config["AZURE_OPENAI_MEMORY_NAME"]!;
    string? result = await kernel.InvokePromptAsync<string>(
        "{{recall 'where did I grow up?'}}",
        new()
        {
            [TextMemoryPlugin.CollectionParam] = memoryCollection,
            [TextMemoryPlugin.LimitParam] = "2",
            [TextMemoryPlugin.RelevanceParam] = "0.79",
        }
    );
    Console.WriteLine($"Output: {result}");
    

使用应用程序设置存储连接机密

如果某个矢量数据库不支持 Microsoft Entra 身份验证,则可以使用应用服务应用程序设置来存储连接机密。 通过使用应用程序设置,可以存储连接机密,而无需预配任何其他 Azure 资源。

在执行这些步骤之前,请检索矢量数据库的连接字符串。 例如,请参阅在 .NET Framework 中使用 Azure Cache for Redis

将连接字符串添加到应用程序设置

  1. Azure 门户中导航到你的应用的页面。
  2. 在应用的左侧菜单中,选择“配置”>“应用程序设置”。
    • 默认情况下,为了安全起见,应用程序设置的值隐藏在门户中。
    • 若要查看应用程序设置的隐藏值,请选择其“值”字段。
  3. 选择“新建连接设置”
  4. 在“添加/编辑连接字符串”屏幕上,选择以下值:
    • 名称:为该设置键入一个名称。 设置名称必须唯一。
    • 值:矢量数据库的连接字符串。
    • 类型:连接的类型,如果没有任何其他值适用,则为 Custom
    • 让其他值保留默认设置。 选择“确定”
  5. 在“配置”页中选择“保存”

使用 Azure CLI 命令 az webapp config connection-string set 添加或编辑应用设置:

az webapp config connection-string set \
--name "<appName>" \
--resource-group "<groupName>" \
--connection-string-type "<connectionType>" \
--settings <connectionName>='<connectionString>'

从应用设置实现连接字符串检索

若要使用以下代码示例,需要以下附加库:

这些代码示例使用 Redis 数据库,但你可以将其应用于支持连接字符串的任何矢量数据库。

  1. 在生成配置时添加环境变量,这会将连接字符串映射到 IConfigurationRoot 对象:

    // User secrets let you provide connection strings when testing locally
    // For more info see: https://learn.microsoft.com/en-us/aspnet/core/security/app-secrets
    IConfigurationRoot config = new ConfigurationBuilder()
        .AddUserSecrets<Program>()
        .AddEnvironmentVariables()
        .Build();
    
    // Retrieve the Redis connection string obtained from the app settings.
    // The connection string name should match the entry in application settings
    string redisConnectionString = config.GetConnectionString("AZURE_REDIS")!;
    
  2. 使用应用设置中的矢量数据库连接字符串初始化 IMemoryStore 对象,然后使用该对象生成 ISemanticTextMemory

    // Use the connection string to connect to the database
    IDatabase database = (
        await ConnectionMultiplexer.ConnectAsync(redisConnectionString)
    ).GetDatabase();
    
    // The Semantic Kernel SDK provides a connector extension for Redis.
    // Initialize an RedisMemoryStore using your managed identity credentials.
    IMemoryStore memoryStore = new RedisMemoryStore(database);
    
    // Retrieve the endpoint and deployments obtained from the Azure OpenAI deployment.
    // Must use the deployment name not the underlying model name.
    string openAiEndpoint = config["AZURE_OPENAI_ENDPOINT"]!;
    string embeddingModel = config["AZURE_OPENAI_EMBEDDING_NAME"]!;
    
    // Build a SemanticMemoryStore with Azure AI Search as the store.
    // Must also include a text embedding generation service.
    ISemanticTextMemory memory = new MemoryBuilder()
        .WithOpenAITextEmbeddingGeneration(embeddingModel, openAiEndpoint)
        .WithMemoryStore(memoryStore)
        .Build();
    
  3. 生成 Kernel 对象,然后使用 TextMemoryPlugin 导入 ISemanticTextMemory 对象:

    // Build a Kernel, include a chat completion service.
    string chatModel = config["AZURE_OPENAI_GPT_NAME"]!;
    Kernel kernel = Kernel
        .CreateBuilder()
        .AddAzureOpenAIChatCompletion(chatModel, openAiEndpoint, credentials)
        .Build();
    
    // Import the semantic memory store as a TextMemoryPlugin.
    // The TextMemoryPlugin enable recall via prompt expressions.
    kernel.ImportPluginFromObject(new TextMemoryPlugin(memory));
    
  4. 使用 Kernel 对象调用一个包含记忆回忆的提示:

    // Must configure the memory collection, number of memories to recall, and relevance score.
    // The {{...}} syntax represents an expression to Semantic Kernel.
    // For more information on this syntax see:
    // https://learn.microsoft.com/semantic-kernel/prompts/prompt-template-syntax
    string memoryCollection = config["AZURE_OPENAI_MEMORY_NAME"]!;
    string? result = await kernel.InvokePromptAsync<string>(
        "{{recall 'where did I grow up?'}}",
        new()
        {
            [TextMemoryPlugin.CollectionParam] = memoryCollection,
            [TextMemoryPlugin.LimitParam] = "2",
            [TextMemoryPlugin.RelevanceParam] = "0.79",
        }
    );
    Console.WriteLine($"Output: {result}");