Go 语言调用 Azure AD 认证实战:go-autorest/adal 库完整指南 云原生后端前端运维可观测性开发工具【免费下载链接】octantHighly extensible platform for developers to better understand the complexity of Kubernetes clusters.项目地址https://gitcode.com/gh_mirrors/oc/octant点击查看免费下载本文以 vendor/github.com/Azure/go-autorest/autorest/adal 包为主线系统讲解如何在 Go 应用中完成 Azure Active DirectoryAAD服务主体Service Principal注册、RBAC 授权以及五种 OAuth2 令牌获取流程客户端密钥、客户端证书、设备码、用户名密码、授权码并深入剖析其源码实现与命令行工具。阅读本文后你将能够在自己的 Go 项目中独立完成 Azure 资源的认证接入、令牌刷新与缓存持久化。包定位与项目背景adalActive Directory Authentication Library for Go是 go-autorest 仓库中一个独立可用的认证包专门用于从其他 Go 库和应用中对 Azure Active Directory 进行认证其最主要的消费方是 Azure SDK for Go。它是一个纯 OAuth2 客户端实现不依赖任何 Azure 服务端组件任何需要访问 Azure 资源的 Go 程序都可以直接引用。需要注意两点尽管名称中包含 ADAL它与github.com/AzureAD组织维护的其他 ADAL 库无关两者没有代码或维护关系该包在 Octant 仓库中作为间接依赖indirect被 vendored当前版本为v0.9.5见 go.mod源码位于 vendor/github.com/Azure/go-autorest/autorest/adal属于 Kubernetes/Azure 生态中常用的 Azure 认证组件。安装使用 Go Modules 直接获取该独立包go get -u github.com/Azure/go-autorest/autorest/adal在模块化项目中它会作为独立依赖写入go.mod在 Octant 这类通过 vendor 目录管理依赖的仓库中包源码会以只读形式随项目一起分发本文引用的源码路径即为 vendor/github.com/Azure/go-autorest/autorest/adal。前置条件注册 Azure AD 应用使用该库之前必须先拥有一个 Azure AD 应用程序Application。注册方式有两种在 Azure Portal 中手动注册或使用 Azure CLI。CLI 方式便于脚本化和自动化本节完整给出两种凭据形态的注册步骤。使用 client secret客户端密钥注册注册一个带secret凭据的新应用az ad app create \ --display-name example-app \ --homepage https://example-app/home \ --identifier-uris https://example-app/app \ --password secret使用上一步返回的Application ID即appId创建服务主体az ad sp create --id Application ID将Application ID替换为第 1 步输出中的appId字段值。使用证书certificate注册证书认证比共享密钥更安全适合生产环境。完整步骤如下生成 2048 位 RSA 私钥openssl genrsa -out example-app.key 2048基于私钥创建证书签名请求CSR并自签生成证书openssl req -new -key example-app.key -subj /CNexample-app -out example-app.csr openssl x509 -req -in example-app.csr -signkey example-app.key -out example-app.crt -days 10000将证书与私钥打包为 PKCS12.pfx格式——该文件将用于 Go 侧加载openssl pkcs12 -export -out example-app.pfx -inkey example-app.key -in example-app.crt -passout pass:提取证书内容去掉首尾两行即剥离 PEM 头尾并注册应用certificateContents$(tail -n2 example-app.crt | head -n-1) az ad app create \ --display-name example-app \ --homepage https://example-app/home \ --identifier-uris https://example-app/app \ --key-usage Verify --end-date 2018-01-01 \ --key-value ${certificateContents}创建服务主体az ad sp create --id APPLICATION_ID将APPLICATION_ID替换为第 4 步返回的appId。授予必要权限RBACAzure 基于 Role-Based Access ControlRBAC模型对资源访问进行细粒度管理。Azure 预置了一批内置角色可按需将服务主体指派到某个角色az role assignment create --assigner SERVICE_PRINCIPAL_ID --role ROLE_NAMESERVICE_PRINCIPAL_ID替换为前文步骤返回的appIdROLE_NAME替换为你选择的角色名称例如Reader、Contributor等内置角色。如果内置角色无法满足需求还可以定义自定义角色az role definition create --role-definition role-definition.jsonrole-definition.json的文件结构actions、notActions、assignableScopes 等字段可参考 Azure 官方自定义角色文档。核心数据结构OAuthConfig 与 TokenOAuthConfig端点配置OAuthConfig是承载 OAuth 操作所需全部端点的结构定义于 config.gotype OAuthConfig struct { AuthorityEndpoint url.URL json:authorityEndpoint AuthorizeEndpoint url.URL json:authorizeEndpoint TokenEndpoint url.URL json:tokenEndpoint DeviceCodeEndpoint url.URL json:deviceCodeEndpoint }从源码看它包含四个端点权威端点Authority、授权端点Authorize、令牌端点Token和设备码端点DeviceCode。NewOAuthConfig会根据传入的activeDirectoryEndpoint如https://login.microsoftonline.com/与tenantID拼接出租户专属的四个 URL并使用默认的api-version1.0见 config.go。若需指定版本可调用NewOAuthConfigWithAPIVersion。源码还提供了多租户支持NewMultiTenantOAuthConfig要求传入 13 个辅助租户 ID生成主租户 辅助租户的配置集合config.go。Token令牌对象Token结构体封装了访问令牌及其元数据token.gotype Token struct { AccessToken string json:access_token RefreshToken string json:refresh_token ExpiresIn json.Number json:expires_in ExpiresOn json.Number json:expires_on NotBefore json.Number json:not_before Resource string json:resource Type string json:token_type }令牌有效期判断依赖Expires()与WillExpireIn()方法token.goWillExpireIn(d)返回“从现在起 d 时长内是否会过期”它是自动刷新机制的判定基础。五种令牌获取流程所有流程共享一段公共配置const activeDirectoryEndpoint https://login.microsoftonline.com/ tenantID : TENANT_ID oauthConfig, err : adal.NewOAuthConfig(activeDirectoryEndpoint, tenantID) applicationID : APPLICATION_ID callback : func(token adal.Token) error { // 令牌获取成功后回调可用于记录、持久化等 } // 申请令牌所针对的资源 resource : https://management.core.windows.net/TENANT_ID替换为你的租户 IDAPPLICATION_ID替换为前文注册得到的应用 ID。从源码看callback对应TokenRefreshCallback类型会在每次成功刷新后依次被调用token.go 与 token.go可用于日志、审计或调用SaveToken持久化。Client Credentials客户端密钥流程适用于服务到服务的无用户交互场景源码中对应ServicePrincipalTokenSecrettoken.goapplicationSecret : APPLICATION_SECRET spt, err : adal.NewServicePrincipalToken( *oauthConfig, appliationID, applicationSecret, resource, callbacks...) if err ! nil { return nil, err } // 获取新的访问令牌 err spt.Refresh() if err nil { token : spt.Token }APPLICATION_SECRET替换为注册时设置的password值。NewServicePrincipalToken内部会校验 OAuthConfig、clientID、secret、resource 均非空token.go并在构造时将AutoRefresh置为true、RefreshWithin置为默认的 5 分钟defaultRefresh 5 * time.Minute。Client Certificate客户端证书流程从 PKCS12 文件中读取证书与私钥构建基于 JWT 断言的认证certificatePath : ./example-app.pfx certData, err : ioutil.ReadFile(certificatePath) if err ! nil { return nil, fmt.Errorf(failed to read the certificate file (%s): %v, certificatePath, err) } // 从 pfx 文件解析出证书与 RSA 私钥 certificate, rsaPrivateKey, err : decodePkcs12(certData, ) if err ! nil { return nil, fmt.Errorf(failed to decode pkcs12 certificate while creating spt: %v, err) } spt, err : adal.NewServicePrincipalTokenFromCertificate( *oauthConfig, applicationID, certificate, rsaPrivateKey, resource, callbacks...) // 获取新的访问令牌 err spt.Refresh() if err nil { token : spt.Token }源码层面的实现细节生产代码中应使用包导出的DecodePfxCertificateData(pfxData, password)完成解析。其实现persist.go先用pkcs12.ToPEM解码再提取PRIVATE KEY块解析为 RSA 私钥并通过公钥模数与指数匹配priv.E certKey.E priv.N.Cmp(certKey.N) 0找到与之配套的证书私钥或证书缺失分别返回ErrMissingPrivateKey、ErrMissingCertificateServicePrincipalCertificateSecret.SignJwttoken.go会生成 RS256 签名的 JWT头中携带证书指纹x5t与完整证书链x5c声明包含aud令牌端点、iss/subclientID、随机jti以及 24 小时有效期的nbf/exp认证表单中通过client_assertionclient_assertion_typeurn:ietf:params:oauth:client-assertion-type:jwt-bearer提交该 JWTtoken.go。Device Code设备码流程适用于 CLI 或无浏览器环境的设备需要用户在其他设备上手动完成认证oauthClient : http.Client{} // 获取设备码 deviceCode, err : adal.InitiateDeviceAuth( oauthClient, *oauthConfig, applicationID, resource) if err ! nil { return nil, fmt.Errorf(Failed to start device auth flow: %s, err) } // 向用户展示认证引导信息 fmt.Println(*deviceCode.Message) // 轮询等待用户完成认证 token, err : adal.WaitForUserCompletion(oauthClient, deviceCode) if err ! nil { return nil, fmt.Errorf(Failed to finish device auth flow: %s, err) } spt, err : adal.NewServicePrincipalTokenFromManualToken( *oauthConfig, applicationID, resource, *token, callbacks...) if err nil { token : spt.Token }底层行为devicetoken.goInitiateDeviceAuth以POST向DeviceCodeEndpoint提交client_id与resource表单返回的DeviceCode包含user_code、verification_url、expires_in、interval与 Azure 特有的messagedevicetoken.goWaitForUserCompletion按interval周期性调用CheckForUserCompletion对authorization_pending继续等待、对slow_down将等待时长翻倍最长为interval * 3遇到access_denied、code_expired等其他错误则终止返回devicetoken.go通过设备码换取的令牌用NewServicePrincipalTokenFromManualToken包装成ServicePrincipalToken其内嵌的ServicePrincipalNoSecret表明该令牌不含可用于再刷新的密钥材料手动构造的 SPT 无法再次换取新令牌token.go。Username Password用户名密码流程适用于交互式登录场景凭据结构为ServicePrincipalUsernamePasswordSecretspt, err : adal.NewServicePrincipalTokenFromUsernamePassword( *oauthConfig, applicationID, username, password, resource, callbacks...) if err nil { token : spt.Token }对应源码将username、password填入 OAuth 表单token.gogrant_type为passwordOAuthGrantTypeUserPass。Authorization Code授权码流程适用于 Web 应用通过浏览器跳转完成授权后回调的场景spt, err : adal.NewServicePrincipalTokenFromAuthorizationCode( *oauthConfig, applicationID, clientSecret, authorizationCode, redirectURI, resource, callbacks...) err spt.Refresh() if err nil { token : spt.Token }实现要点token.go 与 token.go提交code、client_secret、redirect_uri三个字段grant_type为authorization_code刷新时因 Web 应用必须携带client_secretrefreshInternal会识别授权码类型并再次注入密钥token.go。令牌刷新机制与高级配置刷新与自动刷新ServicePrincipalToken提供三条刷新路径token.goRefresh()无条件强制刷新RefreshExchange(resource)针对另一个资源刷新令牌EnsureFresh()仅在令牌将在RefreshWithin窗口内过期且AutoRefresh开启时才刷新。EnsureFresh的实现采用“读锁先检查、写锁复查”的双重检查模式保证并发安全token.go。刷新请求默认走POST TokenEndpoint携带client_id、resource并依据令牌状态选择grant_type有 refresh_token 时用refresh_token否则按密钥类型推导client_credentials/password/authorization_code见 token.go。可通过以下方法调整行为spt.SetAutoRefresh(false) // 关闭自动刷新 spt.SetRefreshWithin(10 * time.Minute) // 调整提前刷新窗口 spt.SetSender(customSender) // 替换 HTTP 客户端Sender 接口 spt.SetCustomRefreshFunc(func(ctx context.Context, resource string) (*adal.Token, error) { // 自定义刷新逻辑 return nil, nil })令牌持久化persist.go提供磁盘持久化能力persist.goLoadToken(path)从 JSON 文件恢复TokenSaveToken(path, mode, token)将令牌写入临时文件后原子重命名os.Rename到目标路径避免多进程并发写导致的文件损坏并支持指定文件权限mode。典型用法是在TokenRefreshCallback中保存、在启动时加载实现跨进程令牌复用。受管服务标识MSI / IMDS当程序运行在 Azure VM、App Service 或 Functions 上时可以使用无凭据的 MSI 认证token.gomsiEndpoint, _ : adal.GetMSIEndpoint() // VM 固定 169.254.169.254App Service 读取 MSI_ENDPOINT 环境变量 spt, err : adal.NewServicePrincipalTokenFromMSI(msiEndpoint, resource)VM 环境固定使用http://169.254.169.254/metadata/identity/oauth2/token与api-version2018-02-01App Service 则使用MSI_ENDPOINT/MSI_SECRET环境变量与2017-09-01版本支持系统分配身份、用户分配身份client_id与身份资源 IDmi_res_id三种形态MSI 刷新自带重试逻辑retryForIMDS对 408/429/500/502/503/504 及部分 4xx/5xx 状态码做指数退避最大延迟 60 秒默认最多尝试 5 次MaxMSIRefreshAttempts见 token.go。HTTP 发送器与 User-Agent默认Sender是一个携带 cookie jar、强制 TLS 1.2 及以上版本的自定义http.Clientsender.go并支持 go-autorest 的 tracing 装饰器。所有刷新请求都会附加形如Go/1.x (amd64-linux) go-autorest/adal/v1.0.0的 User-Agentversion.go可通过AddToUserAgent追加自定义标识。命令行工具adal仓库在cmd/adal.go提供了一个命令行工具支持上述全部流程可直接为指定资源获取令牌。查看全部参数adal -h Usage of ./adal: -applicationId string application id -certificatePath string path to pk12/PFC application certificate -mode string authentication mode (device, secret, cert, refresh) (default device) -resource string resource for which the token is requested -secret string application secret -tenantId string tenant id -tokenCachePath string location of oath token cache (default /home/cgc/.adal/accessToken.json)参数说明参数含义-applicationIdAzure AD 应用的应用 ID-certificatePathPKCS12/PFX 格式证书文件路径cert模式使用-mode认证模式device、secret、cert、refresh默认device-resource申请令牌所针对的 Azure 资源-secret应用密钥secret模式使用-tenantId租户 ID-tokenCachePathOAuth 令牌缓存文件位置默认~/.adal/accessToken.json例如使用设备码流程为https://management.core.windows.net/获取令牌adal -mode device \ -applicationId APPLICATION_ID \ -tenantId TENANT_ID \ -resource https://management.core.windows.net/tokenCachePath对应persist.go中的SaveToken/LoadToken机制令牌会被缓存到本地文件以便后续refresh模式直接复用。在项目中集成时的建议凭据安全secret 或证书私钥不要硬编码建议通过环境变量、密钥管理服务如 Azure Key Vault注入证书优先使用有效期短的密钥并定期轮换令牌复用利用SaveToken/LoadToken做进程间共享配合RefreshWithin提前刷新窗口与EnsureFresh实现“按需刷新”减少对 AAD 令牌端点的无效请求并发安全Refresh、EnsureFresh、Token()等核心方法内部均使用读写锁保护可放心在多 goroutine 环境下调用但自定义回调中避免再触发刷新以免死锁错误处理刷新失败时优先检查返回的TokenRefreshError.Response()获取原始 HTTP 响应token.go区分“可重试的瞬时错误”与“凭据失效类错误”上下文支持所有方法都提供WithContext变体如RefreshWithContext、EnsureFreshWithContext在服务端程序中应始终传入可取消的context.Context以便优雅退出。延伸阅读包内全部实现源码 config.go、 token.go、 devicetoken.go、 persist.go、 sender.go依赖版本声明go.modgithub.com/Azure/go-autorest/autorest/adal v0.9.5 // indirect包在 Octant 仓库中以只读 vendor 方式随项目分发可直接在本地vendor/github.com/Azure/go-autorest/autorest/adal/下查阅全部源码与 LICENSE。赞分享云原生后端前端运维可观测性开发工具【免费下载链接】octantHighly extensible platform for developers to better understand the complexity of Kubernetes clusters.项目地址https://gitcode.com/gh_mirrors/oc/octant点击查看免费下载相关推荐LinuxKit 中的 Azure AD OAuth2 认证go-autorest/adal 库完全指南LinuxKit 中的 Azure AD OAuth2 认证go autorest/adal 库完全指南 本文围绕 linuxkit 仓库中 vendor 的操作系统云原生容器运行时Go 语言 Azure AD 认证实战深入 go-autorest/adal 的服务主体令牌获取、刷新与命令行工具Go 语言 Azure AD 认证实战深入 go autorest/adal 的服务主体令牌获取、刷新与命令行工具 导读 本文以 OpenShift Ori测试云原生质量保障Azure SDK for Go 认证迁移指南从 autorest/adal 迁移到 azidentityAzure SDK for Go 认证迁移指南从 autorest/adal 迁移到 azidentity azidentity 是微软新一代 Azure S机器学习深度学习数据可视化可观测性上一篇IronClaw 能力架构Reborn Capability Architecture实践指南类型化契约、受中介调用路径与运行时通道设计下一篇DragonOS 开源项目教程创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考