使用 Go 语言集成 Azure AD 实现单点登录(SSO)完整指南

11次阅读

使用 Go 语言集成 Azure AD 实现单点登录(SSO)完整指南

本文介绍如何基于 go 语言构建轻量、安全的单点登录(sso)框架,通过 openid connect 协议对接 azure active Directory(azure ad),实现用户身份认证与令牌管理。

go 生态中,推荐使用标准 OAuth2/OpenID Connect 流程(Authorization Code Flow with PKCE)对接 Azure AD —— 它比传统 WS-Fed 或 SAML 更简洁、现代,且原生支持无状态 Web 应用和 CLI 工具。Azure AD 完全兼容 OpenID Connect,是 Go 实现 SSO 的首选协议。

✅ 基础准备

  1. 注册应用:登录 Azure Portal → App Registrations → 新建注册 → 设置重定向 URI(如 http://localhost:8080/callback)→ 记录 application (client) ID 和 Directory (tenant) ID。
  2. 启用 ID Token:在应用的 Authentication 设置中,勾选 “ID tokens”(Implicit grant),或更推荐——直接使用 Authorization Code Flow(无需隐式模式)。
  3. 配置权限:在 API permissions 中添加 User.Read(Delegated),并点击 Grant admin consent

✅ Go 实现核心代码(使用 golang.org/x/oauth2 + coreos/go-oidc)

package main  import (     "context"     "fmt"     "log"     "net/http"     "time"      "golang.org/x/oauth2"     "github.com/coreos/go-oidc"     "golang.org/x/oauth2/google" // 仅用于 oidc.Provider 初始化(兼容 OIDC) )  var (     oauth2Config *oauth2.Config     oidcProvider *oidc.Provider )  func init() {     tenantID := "YOUR_TENANT_ID" // e.g., contoso.onmicrosoft.com or GUID     clientID := "YOUR_CLIENT_ID"     clientSecret := "YOUR_CLIENT_SECRET" // Web app only; public clients omit this     redirectURL := "http://localhost:8080/callback"      // 1. 初始化 OIDC Provider(自动发现 endpoint)     ctx := context.Background()     var err error     oidcProvider, err = oidc.NewProvider(ctx, fmt.Sprintf("https://login.microsoftonline.com/%s/v2.0", tenantID))     if err != nil {         log.Fatal("failed to get provider:", err)     }      // 2. 构建 OAuth2 配置(使用 Azure AD v2.0 endpoint)     oauth2Config = &oauth2.Config{         ClientID:     clientID,         ClientSecret: clientSecret,         RedirectURL:  redirectURL,         Endpoint: oauth2.Endpoint{             AuthURL:  fmt.Sprintf("https://login.microsoftonline.com/%s/oauth2/v2.0/authorize", tenantID),             TokenURL: fmt.Sprintf("https://login.microsoftonline.com/%s/oauth2/v2.0/token", tenantID),         },         Scopes: []string{"https://graph.microsoft.com/User.Read"},     } }  func loginHandler(w http.ResponseWriter, r *http.Request) {     state := fmt.Sprintf("%d", time.Now().Unix())     http.Setcookie(w, &http.Cookie{         Name:  "state",         Value: state,         Path:  "/",     })      url := oauth2Config.AuthCodeURL(state, oauth2.accessTypeOnline, oauth2.SetAuthURLParam("response_type", "code"))     http.Redirect(w, r, url, http.StatusFound) }  func callbackHandler(w http.ResponseWriter, r *http.Request) {     // 验证 state     cookie, err := r.Cookie("state")     if err != nil || r.URL.Query().Get("state") != cookie.Value {         http.Error(w, "state mismatch", http.StatusBadRequest)         return     }      // 交换 code 获取 token     code := r.URL.Query().Get("code")     token, err := oauth2Config.Exchange(r.Context(), code)     if err != nil {         http.Error(w, "failed to exchange code for token", http.StatusInternalServerError)         return     }      // 解析 ID Token(验证签名 & 提取用户信息)     rawIDToken, ok := token.Extra("id_token").(string)     if !ok {         http.Error(w, "no id_token in token response", http.StatusInternalServerError)         return     }      verifier := oidcProvider.Verifier(&oidc.Config{ClientID: oauth2Config.ClientID})     idToken, err := verifier.Verify(r.Context(), rawIDToken)     if err != nil {         http.Error(w, "failed to verify ID token", http.StatusInternalServerError)         return     }      // 解析用户声明(如 email, name, oid)     var claims map[string]interface{}     if err := idToken.Claims(&claims); err != nil {         http.Error(w, "failed to parse claims", http.StatusInternalServerError)         return     }      fmt.Fprintf(w, "✅ Login successful!nUser: %snEmail: %s",          claims["name"], claims["email"]) }  func main() {     http.HandleFunc("/login", loginHandler)     http.HandleFunc("/callback", callbackHandler)     log.Println("Server starting on :8080")     log.Fatal(http.ListenAndServe(":8080", nil)) }

⚠️ 关键注意事项

  • 客户端类型选择:若为纯前端 SPA(如 react/vue),请改用 PKCE 流程(不传 client_secret),并使用 authcode 库(如 go-authcode)或前端库(MSAL.js)处理授权码交换。
  • Token 验证必须离线:go-oidc 自动下载并缓存 Azure AD 的 JWKS 密钥,确保 ID Token 签名有效;切勿仅依赖 exp 字段做校验。
  • 租户锁定 vs 多租户:生产环境建议使用具体 tenant_id(提升安全性);如需支持多租户,可将 login.microsoftonline.com/common/v2.0 替换为 common,并在 ID Token 中检查 tid 声明。
  • 会话管理:示例未含 session 存储,请结合 gorilla/sessions 或 redis 实现服务端会话持久化。

✅ 总结

Azure AD 对 OpenID Connect 的完善支持,使 Go 成为构建企业级 SSO 框架的理想选择。通过 golang.org/x/oauth2 处理授权流程、go-oidc 验证身份令牌,你可快速落地高安全、低耦合的认证中间件。避免使用已弃用的 v1.0 endpoint 或 SAML(Go 生态支持弱、调试成本高),坚定采用 v2.0 + OIDC ,兼顾标准化与可维护性。

text=ZqhQzanResources