oauth2_test_client.go 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181
  1. package main
  2. import (
  3. "context"
  4. "fmt"
  5. "io"
  6. "log"
  7. "net/http"
  8. "net/url"
  9. "path"
  10. "strings"
  11. "time"
  12. "golang.org/x/oauth2"
  13. "golang.org/x/oauth2/clientcredentials"
  14. )
  15. func main() {
  16. // 测试 Client Credentials 流程
  17. //testClientCredentials()
  18. // 测试 Authorization Code + PKCE 流程(需要浏览器交互)
  19. testAuthorizationCode()
  20. }
  21. // testClientCredentials 测试服务对服务认证
  22. func testClientCredentials() {
  23. fmt.Println("=== Testing Client Credentials Flow ===")
  24. cfg := clientcredentials.Config{
  25. ClientID: "client_dsFyyoyNZWjhbNa2", // 需要先创建客户端
  26. ClientSecret: "hLLdn2Ia4UM7hcsJaSuUFDV0Px9BrkNq",
  27. TokenURL: "http://localhost:3000/api/oauth/token",
  28. Scopes: []string{"api:read", "api:write"},
  29. EndpointParams: map[string][]string{
  30. "audience": {"api://new-api"},
  31. },
  32. }
  33. // 创建HTTP客户端
  34. httpClient := cfg.Client(context.Background())
  35. // 调用受保护的API
  36. resp, err := httpClient.Get("http://localhost:3000/api/status")
  37. if err != nil {
  38. log.Printf("Request failed: %v", err)
  39. return
  40. }
  41. defer resp.Body.Close()
  42. body, err := io.ReadAll(resp.Body)
  43. if err != nil {
  44. log.Printf("Failed to read response: %v", err)
  45. return
  46. }
  47. fmt.Printf("Status: %s\n", resp.Status)
  48. fmt.Printf("Response: %s\n", string(body))
  49. }
  50. // testAuthorizationCode 测试授权码流程
  51. func testAuthorizationCode() {
  52. fmt.Println("=== Testing Authorization Code + PKCE Flow ===")
  53. conf := oauth2.Config{
  54. ClientID: "client_dsFyyoyNZWjhbNa2", // 需要先创建客户端
  55. ClientSecret: "JHiugKf89OMmTLuZMZyA2sgZnO0Ioae3",
  56. RedirectURL: "http://localhost:9999/callback",
  57. // 包含 openid/profile/email 以便调用 UserInfo
  58. Scopes: []string{"openid", "profile", "email", "api:read"},
  59. Endpoint: oauth2.Endpoint{
  60. AuthURL: "http://localhost:3000/api/oauth/authorize",
  61. TokenURL: "http://localhost:3000/api/oauth/token",
  62. },
  63. }
  64. // 生成PKCE参数
  65. codeVerifier := oauth2.GenerateVerifier()
  66. state := fmt.Sprintf("state-%d", time.Now().Unix())
  67. // 构建授权URL
  68. url := conf.AuthCodeURL(
  69. state,
  70. oauth2.S256ChallengeOption(codeVerifier),
  71. //oauth2.SetAuthURLParam("audience", "api://new-api"),
  72. )
  73. fmt.Printf("Visit this URL to authorize:\n%s\n\n", url)
  74. fmt.Printf("A local server will listen on http://localhost:9999/callback to receive the code...\n")
  75. // 启动回调本地服务器,自动接收授权码
  76. codeCh := make(chan string, 1)
  77. srv := &http.Server{Addr: ":9999"}
  78. http.HandleFunc("/callback", func(w http.ResponseWriter, r *http.Request) {
  79. q := r.URL.Query()
  80. if errParam := q.Get("error"); errParam != "" {
  81. fmt.Fprintf(w, "Authorization failed: %s", errParam)
  82. return
  83. }
  84. gotState := q.Get("state")
  85. if gotState != state {
  86. http.Error(w, "state mismatch", http.StatusBadRequest)
  87. return
  88. }
  89. code := q.Get("code")
  90. if code == "" {
  91. http.Error(w, "missing code", http.StatusBadRequest)
  92. return
  93. }
  94. fmt.Fprintln(w, "Authorization received. You may close this window.")
  95. select {
  96. case codeCh <- code:
  97. default:
  98. }
  99. go func() {
  100. // 稍后关闭服务
  101. _ = srv.Shutdown(context.Background())
  102. }()
  103. })
  104. go func() {
  105. _ = srv.ListenAndServe()
  106. }()
  107. // 等待授权码
  108. var code string
  109. select {
  110. case code = <-codeCh:
  111. case <-time.After(5 * time.Minute):
  112. log.Println("Timeout waiting for authorization code")
  113. _ = srv.Shutdown(context.Background())
  114. return
  115. }
  116. // 交换令牌
  117. token, err := conf.Exchange(
  118. context.Background(),
  119. code,
  120. oauth2.VerifierOption(codeVerifier),
  121. )
  122. if err != nil {
  123. log.Printf("Token exchange failed: %v", err)
  124. return
  125. }
  126. fmt.Printf("Access Token: %s\n", token.AccessToken)
  127. fmt.Printf("Token Type: %s\n", token.TokenType)
  128. fmt.Printf("Expires In: %v\n", token.Expiry)
  129. // 使用令牌调用 UserInfo
  130. client := conf.Client(context.Background(), token)
  131. userInfoURL := buildUserInfoFromAuth(conf.Endpoint.AuthURL)
  132. resp, err := client.Get(userInfoURL)
  133. if err != nil {
  134. log.Printf("UserInfo request failed: %v", err)
  135. return
  136. }
  137. defer resp.Body.Close()
  138. body, err := io.ReadAll(resp.Body)
  139. if err != nil {
  140. log.Printf("Failed to read UserInfo response: %v", err)
  141. return
  142. }
  143. fmt.Printf("UserInfo: %s\n", string(body))
  144. }
  145. // buildUserInfoFromAuth 将授权端点URL转换为UserInfo端点URL
  146. func buildUserInfoFromAuth(auth string) string {
  147. u, err := url.Parse(auth)
  148. if err != nil {
  149. return ""
  150. }
  151. // 将最后一个路径段 authorize 替换为 userinfo
  152. dir := path.Dir(u.Path)
  153. if strings.HasSuffix(u.Path, "/authorize") {
  154. u.Path = path.Join(dir, "userinfo")
  155. } else {
  156. // 回退:追加默认 /oauth/userinfo
  157. u.Path = path.Join(dir, "userinfo")
  158. }
  159. return u.String()
  160. }