Golang中如何使用OAuth1授权eTrade API
Golang中如何使用OAuth1授权eTrade API https://forum.golangbridge.org/t/etrade-api-authentication-could-not-be-completed-at-this-time/28660
我仍在尝试解决这个问题。eTrade的技术支持只是让我去阅读说明文档。据我所知,我已经按照说明在做了。
如果您能提供任何帮助或指导,我将不胜感激。
更多关于Golang中如何使用OAuth1授权eTrade API的实战教程也可以访问 https://www.itying.com/category-94-b0.html
1 回复
更多关于Golang中如何使用OAuth1授权eTrade API的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
在Golang中实现OAuth1授权访问eTrade API,关键在于正确构建签名基础字符串和签名。以下是完整的实现示例:
package main
import (
"crypto/hmac"
"crypto/sha1"
"encoding/base64"
"fmt"
"math/rand"
"net/http"
"net/url"
"sort"
"strconv"
"strings"
"time"
)
type OAuth1Config struct {
ConsumerKey string
ConsumerSecret string
RequestTokenURL string
AuthorizeURL string
AccessTokenURL string
CallbackURL string
}
func generateNonce() string {
const chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
result := make([]byte, 32)
for i := range result {
result[i] = chars[rand.Intn(len(chars))]
}
return string(result)
}
func generateTimestamp() string {
return strconv.FormatInt(time.Now().Unix(), 10)
}
func percentEncode(s string) string {
return strings.ReplaceAll(url.QueryEscape(s), "+", "%20")
}
func buildSignatureBaseString(method, baseURL string, params map[string]string) string {
// 收集所有参数
allParams := make(map[string]string)
for k, v := range params {
allParams[k] = v
}
// 排序参数
keys := make([]string, 0, len(allParams))
for k := range allParams {
keys = append(keys, k)
}
sort.Strings(keys)
// 构建参数字符串
paramPairs := make([]string, 0, len(keys))
for _, k := range keys {
paramPairs = append(paramPairs,
percentEncode(k)+"="+percentEncode(allParams[k]))
}
parameterString := strings.Join(paramPairs, "&")
// 构建签名基础字符串
baseComponents := []string{
method,
percentEncode(baseURL),
percentEncode(parameterString),
}
return strings.Join(baseComponents, "&")
}
func generateSignature(baseString, consumerSecret, tokenSecret string) string {
signingKey := percentEncode(consumerSecret) + "&" + percentEncode(tokenSecret)
h := hmac.New(sha1.New, []byte(signingKey))
h.Write([]byte(baseString))
signature := base64.StdEncoding.EncodeToString(h.Sum(nil))
return percentEncode(signature)
}
func buildOAuthHeader(params map[string]string) string {
headerParts := []string{"OAuth"}
for k, v := range params {
if strings.HasPrefix(k, "oauth_") {
headerParts = append(headerParts,
percentEncode(k)+`="`+percentEncode(v)+`"`)
}
}
return strings.Join(headerParts, " ")
}
func GetRequestToken(config OAuth1Config) (string, error) {
nonce := generateNonce()
timestamp := generateTimestamp()
oauthParams := map[string]string{
"oauth_callback": config.CallbackURL,
"oauth_consumer_key": config.ConsumerKey,
"oauth_nonce": nonce,
"oauth_signature_method": "HMAC-SHA1",
"oauth_timestamp": timestamp,
"oauth_version": "1.0",
}
// 构建签名
baseString := buildSignatureBaseString(
"POST",
config.RequestTokenURL,
oauthParams,
)
signature := generateSignature(baseString, config.ConsumerSecret, "")
oauthParams["oauth_signature"] = signature
// 创建请求
req, err := http.NewRequest("POST", config.RequestTokenURL, nil)
if err != nil {
return "", err
}
req.Header.Set("Authorization", buildOAuthHeader(oauthParams))
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
// 发送请求
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return "", err
}
defer resp.Body.Close()
// 解析响应
body, err := io.ReadAll(resp.Body)
if err != nil {
return "", err
}
values, err := url.ParseQuery(string(body))
if err != nil {
return "", err
}
return values.Get("oauth_token"), nil
}
func GetAccessToken(config OAuth1Config, requestToken, verifier string) (string, error) {
nonce := generateNonce()
timestamp := generateTimestamp()
oauthParams := map[string]string{
"oauth_consumer_key": config.ConsumerKey,
"oauth_token": requestToken,
"oauth_verifier": verifier,
"oauth_nonce": nonce,
"oauth_signature_method": "HMAC-SHA1",
"oauth_timestamp": timestamp,
"oauth_version": "1.0",
}
// 构建签名
baseString := buildSignatureBaseString(
"POST",
config.AccessTokenURL,
oauthParams,
)
signature := generateSignature(baseString, config.ConsumerSecret, "")
oauthParams["oauth_signature"] = signature
// 创建请求
req, err := http.NewRequest("POST", config.AccessTokenURL, nil)
if err != nil {
return "", err
}
req.Header.Set("Authorization", buildOAuthHeader(oauthParams))
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
// 发送请求
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return "", err
}
defer resp.Body.Close()
// 解析响应
body, err := io.ReadAll(resp.Body)
if err != nil {
return "", err
}
values, err := url.ParseQuery(string(body))
if err != nil {
return "", err
}
return values.Get("oauth_token"), nil
}
func MakeAuthenticatedRequest(method, urlStr, accessToken string, config OAuth1Config) (*http.Response, error) {
nonce := generateNonce()
timestamp := generateTimestamp()
oauthParams := map[string]string{
"oauth_consumer_key": config.ConsumerKey,
"oauth_token": accessToken,
"oauth_nonce": nonce,
"oauth_signature_method": "HMAC-SHA1",
"oauth_timestamp": timestamp,
"oauth_version": "1.0",
}
// 构建签名
baseString := buildSignatureBaseString(method, urlStr, oauthParams)
signature := generateSignature(baseString, config.ConsumerSecret, "")
oauthParams["oauth_signature"] = signature
// 创建请求
req, err := http.NewRequest(method, urlStr, nil)
if err != nil {
return nil, err
}
req.Header.Set("Authorization", buildOAuthHeader(oauthParams))
req.Header.Set("Content-Type", "application/json")
client := &http.Client{}
return client.Do(req)
}
func main() {
config := OAuth1Config{
ConsumerKey: "your_consumer_key",
ConsumerSecret: "your_consumer_secret",
RequestTokenURL: "https://api.etrade.com/oauth/request_token",
AuthorizeURL: "https://us.etrade.com/e/t/etws/authorize",
AccessTokenURL: "https://api.etrade.com/oauth/access_token",
CallbackURL: "oob",
}
// 获取请求令牌
requestToken, err := GetRequestToken(config)
if err != nil {
fmt.Printf("Error getting request token: %v\n", err)
return
}
fmt.Printf("Request Token: %s\n", requestToken)
fmt.Printf("Authorize URL: %s?key=%s&token=%s\n",
config.AuthorizeURL, config.ConsumerKey, requestToken)
// 用户在此处授权后获取验证码
// verifier := "user_provided_verifier"
// 获取访问令牌
// accessToken, err := GetAccessToken(config, requestToken, verifier)
// if err != nil {
// fmt.Printf("Error getting access token: %v\n", err)
// return
// }
// 使用访问令牌进行API调用
// resp, err := MakeAuthenticatedRequest("GET",
// "https://api.etrade.com/v1/accounts/list",
// accessToken, config)
}
关键注意事项:
- eTrade API要求使用HMAC-SHA1签名方法
- 所有参数必须按字母顺序排序后进行百分比编码
- 签名基础字符串的格式为:
METHOD&URL&PARAMETERS - 授权头部的格式必须严格符合OAuth1规范
- 时间戳和非随机数必须为每个请求重新生成
常见的错误原因包括:
- 签名基础字符串构建不正确
- 参数编码不符合规范
- 时间戳与非随机数重复使用
- 授权头部格式错误
确保你的Consumer Key和Secret已正确从eTrade开发者门户获取,并且API终端点URL与你的账户区域匹配(美国账户使用api.etrade.com,沙盒环境使用apisb.etrade.com)。

