GO示例
Go 示例(标准库 + net/http)
go运行
package main
import (
"crypto/rand"
"encoding/base64"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"strings"
"sync"
)
// 全局配置
var (
clientID = "YOUR_CLIENT_ID"
clientSecret = "YOUR_CLIENT_SECRET"
redirectURI = "https://your-domain.com/callback"
authURL = "https://id.rutno.com/rn-oauth/authorize"
tokenURL = "https://id.rutno.com/rn-oauth/token"
userInfoURL = "https://id.rutno.com/rn-oauth/userinfo"
// 简易session存储(生产环境建议用Redis)
stateStore = sync.Map{}
)
// TokenResponse Token响应结构体
type TokenResponse struct {
AccessToken string `json:"access_token"`
TokenType string `json:"token_type"`
ExpiresIn int `json:"expires_in"`
RefreshToken string `json:"refresh_token"`
Scope string `json:"scope"`
}
// UserInfo 用户信息结构体
type UserInfo struct {
ID string `json:"id"`
Username string `json:"username"`
Nickname string `json:"nickname"`
Email string `json:"email"`
Avatar string `json:"avatar"`
}
// 生成随机state
func generateState() string {
b := make([]byte, 16)
rand.Read(b)
return base64.URLEncoding.EncodeToString(b)
}
// 步骤1:授权页面跳转
func authHandler(w http.ResponseWriter, r *http.Request) {
// 生成并存储state
state := generateState()
stateStore.Store(state, true)
// 构建授权URL
params := url.Values{}
params.Set("client_id", clientID)
params.Set("redirect_uri", redirectURI)
params.Set("response_type", "code")
params.Set("scope", "username email avatar")
params.Set("state", state)
authFullURL := authURL + "?" + params.Encode()
http.Redirect(w, r, authFullURL, http.StatusTemporaryRedirect)
}
// 步骤2:回调处理
func callbackHandler(w http.ResponseWriter, r *http.Request) {
// 1. 校验state
state := r.URL.Query().Get("state")
if state == "" {
http.Error(w, "State参数缺失", http.StatusBadRequest)
return
}
// 检查state是否存在
_, exists := stateStore.LoadAndDelete(state) // 一次性使用
if !exists {
http.Error(w, "State验证失败,可能是CSRF攻击", http.StatusForbidden)
return
}
// 2. 处理错误
if err := r.URL.Query().Get("error"); err != "" {
http.Error(w, "授权失败:"+err, http.StatusBadRequest)
return
}
// 3. 获取code
code := r.URL.Query().Get("code")
if code == "" {
http.Error(w, "授权码缺失", http.StatusBadRequest)
return
}
// 4. 换取Token
tokenResp, err := getAccessToken(code)
if err != nil {
http.Error(w, "获取Token失败:"+err.Error(), http.StatusInternalServerError)
return
}
// 5. 获取用户信息
userInfo, err := getUserInfo(tokenResp.AccessToken)
if err != nil {
http.Error(w, "获取用户信息失败:"+err.Error(), http.StatusInternalServerError)
return
}
// 6. 业务处理(示例:返回用户信息)
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(userInfo)
}
// 换取Access Token
func getAccessToken(code string) (*TokenResponse, error) {
// 构建POST参数
params := url.Values{}
params.Set("grant_type", "authorization_code")
params.Set("client_id", clientID)
params.Set("client_secret", clientSecret)
params.Set("code", code)
params.Set("redirect_uri", redirectURI)
// 发送请求
resp, err := http.PostForm(tokenURL, params)
if err != nil {
return nil, fmt.Errorf("请求Token接口失败:%v", err)
}
defer resp.Body.Close()
// 检查状态码
if resp.StatusCode != http.StatusOK {
body, _ := ioutil.ReadAll(resp.Body)
return nil, fmt.Errorf("Token接口返回异常:%d,响应:%s", resp.StatusCode, string(body))
}
// 解析响应
var tokenResp TokenResponse
if err := json.NewDecoder(resp.Body).Decode(&tokenResp); err != nil {
return nil, fmt.Errorf("解析Token响应失败:%v", err)
}
if tokenResp.AccessToken == "" {
return nil, fmt.Errorf("Token为空")
}
return &tokenResp, nil
}
// 获取用户信息
func getUserInfo(accessToken string) (*UserInfo, error) {
// 构建请求
req, err := http.NewRequest("GET", userInfoURL, nil)
if err != nil {
return nil, fmt.Errorf("创建请求失败:%v", err)
}
// 设置Authorization头
req.Header.Set("Authorization", "Bearer "+accessToken)
// 发送请求
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return nil, fmt.Errorf("请求用户信息接口失败:%v", err)
}
defer resp.Body.Close()
// 检查状态码
if resp.StatusCode != http.StatusOK {
body, _ := ioutil.ReadAll(resp.Body)
return nil, fmt.Errorf("用户信息接口返回异常:%d,响应:%s", resp.StatusCode, string(body))
}
// 解析响应
var userInfo UserInfo
if err := json.NewDecoder(resp.Body).Decode(&userInfo); err != nil {
return nil, fmt.Errorf("解析用户信息失败:%v", err)
}
return &userInfo, nil
}
func main() {
// 注册路由
http.HandleFunc("/auth/rutno", authHandler)
http.HandleFunc("/callback", callbackHandler)
// 启动服务
fmt.Println("服务器启动在 :8080 端口")
http.ListenAndServe(":8080", nil)
}
Go 使用说明
- Go 版本要求:1.18+
- 生产环境优化建议:使用 sync.Map 替换为 Redis 存储 state增加 HTTP 客户端超时配置增加重试机制使用 context 控制请求生命周期