You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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的更新

常见问题排查

  1. 无法弹出登录提示:确保你的授权URL构造正确,特别是redirect_uri和Azure AD应用里的配置完全一致,scope格式正确(用空格分隔多个范围,URL编码)
  2. 授权码交换令牌失败:检查client_secret是否正确(web应用),或者是否用了公共客户端模式却加了secret;检查scope是否和授权时的一致
  3. 调用API返回403:确保用户对该Azure资源有委托权限,并且应用注册里已经添加了对应的权限并获得管理员同意(如果需要)

内容的提问来源于stack exchange,提问作者Sumesh

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.29 23:37:43