ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

GitHub Copilot SDK 与 Azure Managed Identity 集成实战:用 BYOK 模式安全接入 Microsoft Foundry 模型

GitHub Copilot SDK 与 Azure Managed Identity 集成实战:用 BYOK 模式安全接入 Microsoft Foundry 模型 GitHub Copilot SDK 与 Azure Managed Identity 集成实战用 BYOK 模式安全接入 Microsoft Foundry 模型【免费下载链接】copilot-sdkMulti-platform SDK for integrating GitHub Copilot Agent into apps and services项目地址: https://gitcode.com/GitHub_Trending/co/copilot-sdk本指南讲解如何在 GitHub Copilot SDK 的 BYOKBring Your Own Key模式下用 Azure Managed IdentityMicrosoft Entra ID替代长期静态 API Key通过 bearer token provider 回调向 Microsoft Foundry 模型发起认证请求。读完本文你将掌握六种主流语言.NET、Go、Java、Python、Rust、TypeScript下完整的接入配置、凭据链选择策略与运行时令牌获取原理可直接落地到 Azure 托管应用中。背景为什么需要 Managed IdentityGitHub Copilot SDK 的 BYOK 模式 允许你使用自己的模型提供商 API Key 接入 Copilot Agent 运行时绕过 GitHub Copilot 认证。标准 BYOK 依赖静态 API Key如apiKey字段或FOUNDRY_API_KEY环境变量但 Azure 部署中通常遵循最小权限与凭据轮换规范长期存在的 Key 往往不合规。Azure Managed IdentityMicrosoft Entra ID 托管身份正是为此设计的应用在 Azure 中运行时无需管理任何密钥由平台自动颁发身份凭证。GitHub Copilot SDK 被刻意设计为与 Azure Identity SDK 可组合——SDK 自身不引入任何 Azure 依赖而是提供一个bearer_token_provider回调接口由你自行用 Azure Identity SDK 的 API 实现令牌获取。回调按需向 SDK 运行时提供新鲜令牌令牌的缓存与刷新时机完全交给 Azure Identity SDK 处理。工作原理Microsoft Foundry 的 OpenAI 兼容端点https://resource-name.openai.azure.com/openai/v1/接受来自 Microsoft Entra ID 的 bearer token取代静态 API Key。SDK 通过 token provider 回调让运行时在每次出站模型请求前按需申请新鲜令牌。用 Python 举例完整流程为为当前环境配置DefaultAzureCredential在 BYOK provider 配置的bearer_token_provider字段传入回调回调内部使用DefaultAzureCredential获取https://ai.azure.com/.defaultscope 的令牌由 GitHub Copilot SDK 运行时通过回调按需请求新鲜令牌。时序如下从源码看回调的运行时行为从 Go SDK 源码可以印证上述流程的实现细节。go/types.go 定义了ProviderTokenArgs与BearerTokenProviderBearerTokenProvider是每个 provider 的回调函数签名为func(args ProviderTokenArgs) (string, error)返回的是不带Bearer前缀的原始令牌字符串回调从不被序列化到线缆协议中ProviderConfig.MarshalJSON会剥离回调仅在 SDK 发送给运行时CLI server的配置里设置hasBearerTokenProvider: true标志见 go/types.go当运行时需要令牌时通过会话级providerToken.getTokenRPC 回调 SDK 客户端go/session.go 中的providerTokenClientSessionAdapter处理该请求校验请求与 session 归属后从注册表中按provider_name找到对应回调并执行错误时返回 JSON-RPC 错误。ProviderTokenArgs携带两个字段ProviderName需要令牌的 BYOK provider 名称。对于会话级单一ProviderConfig隐式名称为default对于NamedProviderConfig条目则为NamedProviderConfig.NameSessionID触发令牌请求的会话 ID。客户端级共享回调为多个会话注册可据此解析所属会话按会话维度做令牌获取或缓存。BearerTokenProvider在 go/types.go 中被标注为Experimental属于托管身份 / bearer token provider 实验性接口未来 SDK 或 CLI 版本可能调整。Python SDK 中的定义与此一致python/copilot/session.py 将BearerTokenProvider声明为Callable[[ProviderTokenArgs], str | Awaitable[str]]同步或异步均可ProviderTokenArgs同样包含provider_name与session_id。测试用例验证的三条关键行为Go SDK 的端到端测试 go/internal/e2e/byok_bearer_token_provider_e2e_test.go 用捕获式RoundTripper拦截运行时出站请求验证了三个核心行为回调令牌被原样应用到请求头捕获到的出站请求Authorization头等于Bearer 回调返回值证明回调令牌确实到达了模型请求每次请求重新获取令牌、运行时不做缓存测试让回调每次返回不同令牌rotating-token-N断言两次出站请求携带不同的Authorization头证实缓存与刷新职责完全在回调或其包裹的 Azure Identity 库一侧按 provider 分发令牌测试注册 red/blue 两个 provider各自使用独立回调断言每个 provider 的轮次只使用自己的令牌访问自己的端点而非会话级单一凭据。各语言依赖安装按语言安装 Azure Identity 与 GitHub Copilot SDK 包.NETdotnet add package GitHub.Copilot.SDK dotnet add package Azure.CoreGogo get github.com/github/copilot-sdk/go go get github.com/Azure/azure-sdk-for-go/sdk/azidentityJavadependency groupIdcom.github/groupId artifactIdcopilot-sdk-java/artifactId version${copilot.sdk.version}/version /dependency dependency groupIdcom.azure/groupId artifactIdazure-identity/artifactId version${azure.identity.version}/version /dependencyPythonpip install github-copilot-sdk azure-identityRustcargo add github-copilot-sdk azure_identity azure_core cargo add tokio --features macros,rt-multi-threadTypeScriptnpm install github/copilot-sdk azure/identity使用 token provider 回调当希望 GitHub Copilot SDK 运行时通过你提供的回调按需请求新鲜令牌时使用本方案。令牌缓存与刷新时机由 Azure Identity SDK 负责。各语言实现如下代码中的FOUNDRY_RESOURCE_URL环境变量为你的 Microsoft Foundry 资源 URL形如https://my-resource.openai.azure.com.NETusing Azure.Core; using Azure.Identity; using GitHub.Copilot; DefaultAzureCredential credential new( DefaultAzureCredential.DefaultEnvironmentVariableName); await using CopilotClient client new(); string foundryUrl Environment.GetEnvironmentVariable(FOUNDRY_RESOURCE_URL)!; await using CopilotSession session await client.CreateSessionAsync(new SessionConfig { Model gpt-5.5, Provider new ProviderConfig { Type openai, BaseUrl ${foundryUrl}/openai/v1/, BearerTokenProvider async _ { AccessToken token await credential.GetTokenAsync( new TokenRequestContext([https://ai.azure.com/.default])); return token.Token; }, WireApi responses, }, }); AssistantMessageEvent? response await session.SendAndWaitAsync( new MessageOptions { Prompt Hello from Managed Identity! }); Console.WriteLine(response?.Data.Content);Gopackage main import ( context fmt log os time github.com/Azure/azure-sdk-for-go/sdk/azcore/policy github.com/Azure/azure-sdk-for-go/sdk/azidentity copilot github.com/github/copilot-sdk/go ) func main() { opts : azidentity.DefaultAzureCredentialOptions{RequireAzureTokenCredentials: true} credential, err : azidentity.NewDefaultAzureCredential(opts) if err ! nil { log.Fatal(err) } getBearerToken : func(args copilot.ProviderTokenArgs) (string, error) { token, err : credential.GetToken(context.Background(), policy.TokenRequestOptions{ Scopes: []string{https://ai.azure.com/.default}, }) if err ! nil { return , err } return token.Token, nil } client : copilot.NewClient(nil) if err : client.Start(context.Background()); err ! nil { log.Fatal(err) } defer client.Stop() foundryURL : os.Getenv(FOUNDRY_RESOURCE_URL) session, err : client.CreateSession(context.Background(), copilot.SessionConfig{ Model: gpt-5.5, Provider: copilot.ProviderConfig{ Type: openai, BaseURL: fmt.Sprintf(%s/openai/v1/, foundryURL), BearerTokenProvider: getBearerToken, WireAPI: responses, }, }) if err ! nil { log.Fatal(err) } defer session.Disconnect() ctx, cancel : context.WithTimeout(context.Background(), 60*time.Second) defer cancel() response, err : session.SendAndWait(ctx, copilot.MessageOptions{ Prompt: Hello from Managed Identity!, }) if err ! nil { log.Fatal(err) } if response ! nil { if data, ok : response.Data.(*copilot.AssistantMessageData); ok { fmt.Println(data.Content) } } }Javaimport com.azure.core.credential.TokenRequestContext; import com.azure.identity.AzureIdentityEnvVars; import com.azure.identity.DefaultAzureCredentialBuilder; import com.github.copilot.CopilotClient; import com.github.copilot.generated.AssistantMessageEvent; import com.github.copilot.rpc.BearerTokenProvider; import com.github.copilot.rpc.MessageOptions; import com.github.copilot.rpc.ProviderConfig; import com.github.copilot.rpc.SessionConfig; public class ManagedIdentityExample { public static void main(String[] args) throws Exception { var credential new DefaultAzureCredentialBuilder() .requireEnvVars(AzureIdentityEnvVars.AZURE_TOKEN_CREDENTIALS) .build(); BearerTokenProvider tokenProvider providerArgs - credential .getToken(new TokenRequestContext().addScopes(https://ai.azure.com/.default)) .map(accessToken - accessToken.getToken()) .toFuture(); String foundryUrl System.getenv(FOUNDRY_RESOURCE_URL); try (var client new CopilotClient()) { client.start().get(); var session client.createSession(new SessionConfig() .setModel(gpt-5.5) .setProvider(new ProviderConfig() .setType(openai) .setBaseUrl(foundryUrl /openai/v1/) .setBearerTokenProvider(tokenProvider) .setWireApi(responses))) .get(); AssistantMessageEvent response session .sendAndWait(new MessageOptions().setPrompt(Hello from Managed Identity!)) .get(); System.out.println(response.getData().content()); session.disconnect().get(); } } }Pythonimport asyncio import os from azure.identity.aio import DefaultAzureCredential from copilot import CopilotClient from copilot.session import PermissionHandler, ProviderConfig async def main(): credential DefaultAzureCredential(require_envvarTrue) async def get_bearer_token(_args) - str: token await credential.get_token(https://ai.azure.com/.default) return token.token foundry_url os.environ[FOUNDRY_RESOURCE_URL] client CopilotClient() await client.start() session await client.create_session( on_permission_requestPermissionHandler.approve_all, modelgpt-5.5, providerProviderConfig( typeopenai, base_urlf{foundry_url.rstrip(/)}/openai/v1/, bearer_token_providerget_bearer_token, wire_apiresponses, ), ) response await session.send_and_wait(Hello from Managed Identity!) print(response.data.content) await client.stop() await credential.close() asyncio.run(main())Rustuse std::sync::Arc; use azure_core::credentials::TokenCredential; use azure_identity::{DeveloperToolsCredential, ManagedIdentityCredential}; use github_copilot_sdk::{BearerTokenError, Client, ClientOptions, MessageOptions, ProviderTokenArgs}; use github_copilot_sdk::types::{ProviderConfig, SessionConfig}; fn credential_for_environment() - azure_core::ResultArcdyn TokenCredential { match std::env::var(AZURE_TOKEN_CREDENTIALS).as_deref() { Ok(ManagedIdentityCredential) Ok(ManagedIdentityCredential::new(None)?), _ Ok(DeveloperToolsCredential::new(None)?), } } #[tokio::main] async fn main() - Result(), Boxdyn std::error::Error { let credential credential_for_environment()?; let foundry_url std::env::var(FOUNDRY_RESOURCE_URL)?; let get_bearer_token { let credential credential.clone(); move |_args: ProviderTokenArgs| { let credential credential.clone(); async move { let token credential .get_token([https://ai.azure.com/.default], None) .await .map_err(|err| BearerTokenError::message(err.to_string()))?; Ok(token.token.secret().to_string()) } } }; let mut provider ProviderConfig::default(); provider.provider_type Some(openai.to_string()); provider.base_url format!({}/openai/v1/, foundry_url.trim_end_matches(/)); provider.bearer_token_provider Some(Arc::new(get_bearer_token)); provider.wire_api Some(responses.to_string()); let mut config SessionConfig::default(); config.model Some(gpt-5.5.to_string()); config.provider Some(provider); let client Client::start(ClientOptions::default()).await?; let session client.create_session(config).await?; session .send_and_wait(MessageOptions::new(Hello from Managed Identity!)) .await?; session.disconnect().await?; client.stop().await?; Ok(()) }TypeScriptimport { DefaultAzureCredential } from azure/identity; import { CopilotClient } from github/copilot-sdk; const credential new DefaultAzureCredential({ requiredEnvVars: [AZURE_TOKEN_CREDENTIALS], }); const getBearerToken async () { const tokenResponse await credential.getToken(https://ai.azure.com/.default); return tokenResponse.token; }; const client new CopilotClient(); const session await client.createSession({ model: gpt-5.5, provider: { type: openai, baseUrl: ${process.env.FOUNDRY_RESOURCE_URL}/openai/v1/, bearerTokenProvider: getBearerToken, wireApi: responses, }, }); const response await session.sendAndWait({ prompt: Hello from Managed Identity! }); console.log(response?.data.content); await client.stop();环境配置下表列出本方案所需的全部环境变量VariableDescriptionExampleAZURE_TOKEN_CREDENTIALS在Azure中运行时设为ManagedIdentityCredential。在本地运行时设为dev或某个开发者工具凭据名如AzureCliCredential。ManagedIdentityCredentialAZURE_CLIENT_ID可选。在Azure中使用ManagedIdentityCredential时设为 User-assigned Managed Identity 的 client ID未设置时 Azure 使用 System-assigned Managed Identity。11111111-2222-3333-4444-555555555555FOUNDRY_RESOURCE_URL你的 Microsoft Foundry 资源 URLhttps://my-resource.openai.azure.com不需要任何 API Key 环境变量——认证完全由 Azure Identity 凭据完成。在 .NET、Go、Java、Python、TypeScript 中DefaultAzureCredential自动支持以下凭据来源Managed IdentitySystem-assigned 或 User-assigned用于 Azure 托管的应用Azure CLIaz login用于本地开发环境变量AZURE_CLIENT_ID、AZURE_TENANT_ID、AZURE_CLIENT_SECRET用于服务主体Workload Identity用于 Kubernetes。在 .NET、Go、Java、Python、TypeScript 中ManagedIdentityCredential读取AZURE_CLIENT_ID来选择 User-assigned Managed Identity。Rust 在本指南中是一个例外它不使用DefaultAzureCredential而是在本地开发时用DeveloperToolsCredential、在 Azure 运行时用ManagedIdentityCredential见上文 Rust 示例中的credential_for_environment函数它按AZURE_TOKEN_CREDENTIALS环境变量分派。Rust 的凭据路径说明从 rust/src/provider_token.rs 的源码可以看到Rust SDK 定义了BearerTokenError实现Display与std::error::Error并支持从String/str构造以及get_token异步接口——回调错误统一包装为BearerTokenError::message(err.to_string())返回给运行时这正是 Rust 示例中map_err的落点。何时使用该模式ScenarioRecommendationAzure 托管应用带 Managed Identity✅ 使用本模式已有 Microsoft Entra 服务主体的应用✅ 使用本模式本地开发az login✅ 使用本模式非 Azure 环境、有静态 API Key使用标准 BYOK可用 GitHub Copilot 订阅使用 GitHub OAuth与静态 bearer token 的取舍若你的应用已经持有一个令牌也可以使用静态bearerToken字段直接设置Authorization头参见 BYOK 指南 的 bearer token 认证一节。但需注意bearerToken只接受静态字符串SDK 不会自动刷新令牌过期后请求会失败、需要重建会话。这正是本模式的价值所在——bearerTokenProvider回调让 SDK 运行时每次出站请求前重新调用回调获取令牌将缓存与刷新交给 Azure Identity 库从而在 Managed Identity 场景下实现无密钥、自动轮换的认证。在 ProviderConfig 中的优先级根据 BYOK 指南 的 provider 配置参考表认证字段的优先级为bearerTokenProvider回调优先于bearerToken静态令牌优先于apiKey静态 Key。本模式使用的正是优先级最高的回调方式。补充阅读BYOK 设置指南静态 API Key 配置与 ProviderConfig 字段完整参考后端服务部署服务端无头部署GitHub OAuth使用 GitHub Copilot 订阅认证的替代方案【免费下载链接】copilot-sdkMulti-platform SDK for integrating GitHub Copilot Agent into apps and services项目地址: https://gitcode.com/GitHub_Trending/co/copilot-sdk创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表