如何在Golang中实现单腿OAuth 1签名API请求?
在Go中实现单腿OAuth 1.0 API调用(仅用Consumer Key/Secret)
单腿OAuth 1.0(无请求令牌/访问令牌,仅使用Consumer Key和Consumer Secret)的核心是按照OAuth 1.0规范生成正确的签名和Authorization请求头。以下是完整的Go实现代码,直接对应你在Postman中的操作逻辑:
完整实现代码
package main import ( "crypto/hmac" "crypto/sha1" "encoding/base64" "fmt" "math/rand" "net/http" "net/url" "sort" "strconv" "time" ) // 生成随机Nonce(16位字符串) func generateNonce() string { rand.Seed(time.Now().UnixNano()) chars := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" nonce := make([]byte, 16) for i := range nonce { nonce[i] = chars[rand.Intn(len(chars))] } return string(nonce) } // 构造OAuth 1.0 Authorization头 func createOAuth1Header(method, urlStr string, consumerKey, consumerSecret string) (string, error) { // 解析目标URL parsedURL, err := url.Parse(urlStr) if err != nil { return "", err } // 生成OAuth核心参数 oauthParams := url.Values{ "oauth_consumer_key": {consumerKey}, "oauth_nonce": {generateNonce()}, "oauth_timestamp": {strconv.FormatInt(time.Now().Unix(), 10)}, "oauth_signature_method": {"HMAC-SHA1"}, "oauth_version": {"1.0"}, } // 1. 构造基础签名字符串:方法 + & + URL编码的基础URL + & + URL编码的参数串 // 基础URL:协议+主机+路径(不含查询参数) baseURL := fmt.Sprintf("%s://%s%s", parsedURL.Scheme, parsedURL.Host, parsedURL.Path) encodedBaseURL := url.QueryEscape(baseURL) // 合并所有参数(包括URL查询参数)并按key排序 allParams := make(url.Values) for k, v := range oauthParams { allParams[k] = v } for k, v := range parsedURL.Query() { allParams[k] = v } // 排序参数键 sortedKeys := make([]string, 0, len(allParams)) for k := range allParams { sortedKeys = append(sortedKeys, k) } sort.Strings(sortedKeys) // 构造参数字符串(key=编码后的value,按&拼接) var paramStr string for i, k := range sortedKeys { if i > 0 { paramStr += "&" } paramStr += fmt.Sprintf("%s=%s", url.QueryEscape(k), url.QueryEscape(allParams.Get(k))) } encodedParamStr := url.QueryEscape(paramStr) // 基础签名字符串 signatureBaseStr := fmt.Sprintf("%s&%s&%s", method, encodedBaseURL, encodedParamStr) // 2. 生成HMAC-SHA1签名 signatureKey := fmt.Sprintf("%s&", consumerSecret) // 单腿OAuth无token secret,所以后缀为& h := hmac.New(sha1.New, []byte(signatureKey)) h.Write([]byte(signatureBaseStr)) signatureBytes := h.Sum(nil) signatureBase64 := base64.StdEncoding.EncodeToString(signatureBytes) encodedSignature := url.QueryEscape(signatureBase64) // 3. 构造Authorization头 oauthParams.Set("oauth_signature", encodedSignature) var authHeaderParts []string for k, v := range oauthParams { authHeaderParts = append(authHeaderParts, fmt.Sprintf(`%s="%s"`, k, url.QueryEscape(v[0]))) } sort.Strings(authHeaderParts) // 按key排序,避免签名不一致 authHeader := fmt.Sprintf("OAuth %s", joinStrings(authHeaderParts, ", ")) return authHeader, nil } // 辅助函数:用分隔符拼接字符串切片 func joinStrings(slice []string, sep string) string { var result string for i, s := range slice { if i > 0 { result += sep } result += s } return result } // 发起API请求示例 func main() { // 替换为你的实际参数 consumerKey := "your_authentication_key" consumerSecret := "your_authentication_secret" apiURL := "https://api.example.com/your-endpoint" method := "GET" // 或POST/PUT等 // 生成Authorization头 authHeader, err := createOAuth1Header(method, apiURL, consumerKey, consumerSecret) if err != nil { fmt.Printf("生成OAuth头失败: %v\n", err) return } // 创建HTTP请求 req, err := http.NewRequest(method, apiURL, nil) if err != nil { fmt.Printf("创建请求失败: %v\n", err) return } // 添加Authorization头 req.Header.Add("Authorization", authHeader) // 发起请求 client := &http.Client{} resp, err := client.Do(req) if err != nil { fmt.Printf("发起请求失败: %v\n", err) return } defer resp.Body.Close() fmt.Printf("请求状态码: %d\n", resp.StatusCode) // 后续可处理响应内容 }
关键逻辑说明
- Nonce与Timestamp:Nonce是随机字符串(防止重放攻击),Timestamp是当前Unix时间戳,两者每次请求都要重新生成。
- 基础签名字符串:必须严格按照
HTTP方法&URL编码的基础URL&URL编码的参数串格式构造,参数需按字典序排序,这是签名正确的核心。 - 签名生成:使用HMAC-SHA1算法,密钥为
Consumer Secret&(单腿OAuth无Token Secret,所以后缀保留&),生成的签名需Base64编码后再URL编码。 - Authorization头:所有OAuth参数需格式化为
key="value"的形式,value需URL编码,参数间用逗号分隔。
内容的提问来源于stack exchange,提问作者Angelo
相关产品推荐
相关产品推荐

