如何在Golang中实现Azure AD的Authorization Code授权码流并访问用户委托的Azure资源
实现Golang Azure AD授权码流获取访问令牌
我明白你要的是授权码流的完整实现——既要能引导用户完成Azure AD登录,又能拿到访问令牌去调用那些Azure SDK没覆盖的REST接口。下面我给你两种方案,先讲纯REST的实现,再给你更省心的MSAL Go方案,都是可以直接跑的示例。
一、纯REST实现授权码流
授权码流核心分三步:生成授权URL引导用户登录、接收回调里的授权码、用授权码换令牌。这里我们用net/http搭建一个简单的本地服务来处理回调,因为Azure AD需要一个可访问的回调地址(本地测试可以用http://localhost:8080/callback)。
步骤1:配置Azure AD应用
先确保你的Azure AD应用注册里:
- 重定向URI设为
http://localhost:8080/callback(类型选"Web") - 勾选"授权码流"的权限(比如
User.Read或者你需要的资源权限,要选委托权限) - 记录下你的
Tenant ID、Client ID、Client Secret(如果是web应用的话需要,桌面应用可以用公共客户端,不需要secret)
步骤2:纯REST代码示例
package main import ( "encoding/json" "fmt" "net/http" "net/url" "strings" ) const ( tenantID = "your-tenant-id" clientID = "your-client-id" clientSecret = "your-client-secret" // 桌面应用可省略,用公共客户端模式 redirectURI = "http://localhost:8080/callback" scope = "https://management.azure.com/user_impersonation offline_access" // 替换成你需要的资源范围,比如User.Read或者其他API的范围 ) // TokenResponse 解析令牌响应 type TokenResponse struct { AccessToken string `json:"access_token"` TokenType string `json:"token_type"` ExpiresIn int `json:"expires_in"` RefreshToken string `json:"refresh_token"` } func main() { // 处理根路径,重定向到Azure AD授权页面 http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { authURL := fmt.Sprintf( "https://login.microsoftonline.com/%s/oauth2/v2.0/authorize?"+ "client_id=%s&response_type=code&redirect_uri=%s&response_mode=query&scope=%s&state=12345", tenantID, url.QueryEscape(clientID), url.QueryEscape(redirectURI), url.QueryEscape(scope), ) http.Redirect(w, r, authURL, http.StatusTemporaryRedirect) }) // 处理回调,获取授权码并交换令牌 http.HandleFunc("/callback", func(w http.ResponseWriter, r *http.Request) { code := r.URL.Query().Get("code") if code == "" { http.Error(w, "未获取到授权码", http.StatusBadRequest) return } // 构造令牌请求 data := url.Values{ "client_id": {clientID}, "client_secret": {clientSecret}, // 公共客户端去掉这一行 "code": {code}, "redirect_uri": {redirectURI}, "grant_type": {"authorization_code"}, "scope": {scope}, } resp, err := http.PostForm(fmt.Sprintf("https://login.microsoftonline.com/%s/oauth2/v2.0/token", tenantID), data) if err != nil { http.Error(w, fmt.Sprintf("请求令牌失败: %v", err), http.StatusInternalServerError) return } defer resp.Body.Close() var tokenResp TokenResponse if err := json.NewDecoder(resp.Body).Decode(&tokenResp); err != nil { http.Error(w, fmt.Sprintf("解析令牌响应失败: %v", err), http.StatusInternalServerError) return } // 这里拿到了access_token,你可以用它调用REST API fmt.Fprintf(w, "访问令牌: %s\n", tokenResp.AccessToken) fmt.Fprintf(w, "刷新令牌: %s\n", tokenResp.RefreshToken) // 示例:用令牌调用Azure资源管理器API,获取用户有权限的订阅 callAzureRESTAPI(tokenResp.AccessToken) }) fmt.Println("服务启动在 http://localhost:8080,访问后将引导你登录Azure AD") http.ListenAndServe(":8080", nil) } // callAzureRESTAPI 用访问令牌调用Azure REST API func callAzureRESTAPI(accessToken string) { req, err := http.NewRequest("GET", "https://management.azure.com/subscriptions?api-version=2020-01-01", nil) if err != nil { fmt.Printf("创建请求失败: %v\n", err) return } req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", accessToken)) resp, err := http.DefaultClient.Do(req) if err != nil { fmt.Printf("调用API失败: %v\n", err) return } defer resp.Body.Close() fmt.Println("API响应状态码:", resp.Status) // 可以进一步解析响应体,获取订阅列表等信息 }
注意事项
- 如果是桌面应用,不要用
client_secret,把相关代码去掉,Azure AD会把它当作公共客户端处理 scope要根据你要访问的资源来设置,比如访问Graph API用https://graph.microsoft.com/User.Read,访问Azure资源管理器用https://management.azure.com/user_impersonation- 本地测试时,确保回调URI和Azure AD应用里的配置完全一致
二、MSAL Go实现(更稳定推荐)
如果你觉得纯REST处理细节太麻烦,MSAL Go是微软官方的库,封装了授权码流的所有逻辑,包括令牌缓存、刷新等,更可靠。
步骤1:安装MSAL Go
go get github.com/AzureAD/microsoft-authentication-library-for-go/apps/public
步骤2:MSAL Go代码示例
package main import ( "context" "fmt" "net/http" "github.com/AzureAD/microsoft-authentication-library-for-go/apps/public" ) const ( tenantID = "your-tenant-id" clientID = "your-client-id" redirectURI = "http://localhost:8080/callback" scope = []string{"https://management.azure.com/user_impersonation", "offline_access"} ) func main() { // 创建公共客户端(桌面/移动应用),如果是web应用用confidential.Client client, err := public.New(clientID, public.WithAuthority(fmt.Sprintf("https://login.microsoftonline.com/%s", tenantID))) if err != nil { fmt.Printf("创建客户端失败: %v\n", err) return } // 处理根路径,生成授权URL http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { authCodeURL, err := client.AuthCodeURL(context.Background(), redirectURI, public.WithScopes(scope...)) if err != nil { http.Error(w, fmt.Sprintf("生成授权URL失败: %v", err), http.StatusInternalServerError) return } http.Redirect(w, r, authCodeURL, http.StatusTemporaryRedirect) }) // 处理回调,交换令牌 http.HandleFunc("/callback", func(w http.ResponseWriter, r *http.Request) { code := r.URL.Query().Get("code") if code == "" { http.Error(w, "未获取到授权码", http.StatusBadRequest) return } result, err := client.AcquireTokenByAuthCode(context.Background(), code, redirectURI, public.WithScopes(scope...)) if err != nil { http.Error(w, fmt.Sprintf("获取令牌失败: %v", err), http.StatusInternalServerError) return } // 拿到令牌 fmt.Fprintf(w, "访问令牌: %s\n", result.AccessToken) fmt.Fprintf(w, "刷新令牌: %s\n", result.RefreshToken) // 调用Azure REST API callAzureRESTAPI(result.AccessToken) }) fmt.Println("服务启动在 http://localhost:8080,访问后将引导你登录Azure AD") http.ListenAndServe(":8080", nil) } // callAzureRESTAPI 用访问令牌调用Azure REST API func callAzureRESTAPI(accessToken string) { req, err := http.NewRequest("GET", "https://management.azure.com/subscriptions?api-version=2020-01-01", nil) if err != nil { fmt.Printf("创建请求失败: %v\n", err) return } req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", accessToken)) resp, err := http.DefaultClient.Do(req) if err != nil { fmt.Printf("调用API失败: %v\n", err) return } defer resp.Body.Close() fmt.Println("API响应状态码:", resp.Status) // 解析响应体获取资源信息 }
优势
- 自动处理令牌缓存,后续可以用
AcquireTokenSilent获取缓存的令牌 - 自动处理刷新令牌,当令牌过期时无需用户重新登录
- 官方维护,更稳定,适配Azure AD的更新
常见问题排查
- 无法弹出登录提示:确保你的授权URL构造正确,特别是
redirect_uri和Azure AD应用里的配置完全一致,scope格式正确(用空格分隔多个范围,URL编码) - 授权码交换令牌失败:检查
client_secret是否正确(web应用),或者是否用了公共客户端模式却加了secret;检查scope是否和授权时的一致 - 调用API返回403:确保用户对该Azure资源有委托权限,并且应用注册里已经添加了对应的权限并获得管理员同意(如果需要)
内容的提问来源于stack exchange,提问作者Sumesh
相关产品推荐
相关产品推荐

