How to exchange OAUTH token ( Go ) using Google API

huangapple go评论86阅读模式
英文:

How to exchange OAUTH token ( Go ) using Google API

问题

这是我目前的代码,返回了一个"400错误"。我做错了什么吗?我无法弄清楚为什么它不起作用,因为请求非常简单明了。

package main

import (
    "code.google.com/p/goauth2/oauth"
    "fmt"
    "log"
)

func main() {
    cachefile := "cache.json"
    code := "4/xxxxx.8uFT5Z0slpMbJvIeHux6iLY_9k7ajw" //从URL重定向中接收到的代码
    // 设置配置。
    config := &oauth.Config{
        ClientId:     "xx.apps.googleusercontent.com",
        ClientSecret: "cWP3HudD3XmaP33j8",
        RedirectURL:  "https://crm.com/sender/gmail/auth/callBack",
        Scope:        "https://www.googleapis.com/auth/gmail.compose",
        AuthURL:      "https://accounts.google.com/o/oauth2/auth",
        TokenURL:     "https://accounts.google.com/o/oauth2/token",
        AccessType:   "offline",
        TokenCache:   oauth.CacheFile(cachefile),
    }

    // 使用配置设置传输。
    transport := &oauth.Transport{Config: config}
    token, err := config.TokenCache.Token()
    if err != nil {
        token, err = transport.Exchange(code)
        if err != nil {
            log.Fatal("Exchange:", err)
        }
    }
    // (Exchange方法将自动缓存令牌。)

    transport.Token = token
    fmt.Println(token)
}

结果

Exchange:OAuthError: updateToken: Unexpected HTTP status 400 Bad Request
英文:

Here is what I have so far which returns a "400 error". Am I doing something wrong? I can't figure it out why is not working as the request is pretty straightforward

package main

import (
	"code.google.com/p/goauth2/oauth"
	"fmt"
	"log"
)

func main() {
	cachefile := "cache.json"
	code := "4/xxxxx.8uFT5Z0slpMbJvIeHux6iLY_9k7ajw" //the code received from the URL redirect
	// Set up a configuration.
	config := &oauth.Config{
		ClientId:     "xx.apps.googleusercontent.com",
		ClientSecret: "cWP3HudD3XmaP33j8",
		RedirectURL:  "https://crm.com/sender/gmail/auth/callBack",
		Scope:        "https://www.googleapis.com/auth/gmail.compose",
		AuthURL:      "https://accounts.google.com/o/oauth2/auth",
		TokenURL:     "https://accounts.google.com/o/oauth2/token",
		AccessType:   "offline",
		TokenCache:   oauth.CacheFile(cachefile),
	}

	// Set up a Transport using the config.
	transport := &oauth.Transport{Config: config}
	token, err := config.TokenCache.Token()
	if err != nil {
		token, err = transport.Exchange(code)
		if err != nil {
			log.Fatal("Exchange:", err)
		}
	}
	// (The Exchange method will automatically cache the token.)

	transport.Token = token
	fmt.Println(token)
}

Result

Exchange:OAuthError: updateToken: Unexpected HTTP status 400 Bad Request

答案1

得分: 2

我建议使用“一次性代码流”,如文档中所述:

要充分利用Google+登录的所有优势,您必须使用混合服务器端流程,在客户端使用JavaScript API客户端授权您的应用程序,并将特殊的一次性授权代码发送到您的服务器。您的服务器使用这个一次性代码来获取自己的访问令牌和刷新令牌,以便服务器能够在用户离线时进行自己的API调用。与纯服务器端流程和将访问令牌发送到服务器相比,这种一次性代码流程具有安全优势。

由于代码只能使用一次,因此破坏用户帐户的可能性较小。

客户端代码非常简单,可以按照第3步中的示例进行操作。

对于服务器端,我建议使用包oauth2而不是goauth2

$ go get code.google.com/p/google-api-go-client/plus/v1
$ go get github.com/golang/oauth2
$ go get google.golang.org/appengine

由于某种原因,oauth2包还需要appengine包。

可以使用函数NewTransportWithCode来将一次性代码交换为可重用令牌:

func exchangeCode(code string) (*oauth2.Token, *oauth2.Transport, error) {
    config, err := google.NewConfig(&oauth2.Options{
        ClientID:     CLIENT_ID,
        ClientSecret: CLIENT_SECRET,
        RedirectURL:  "postmessage",
        Scopes:       []string{"https://www.googleapis.com/auth/plus.login"},
    })
    if err != nil {
        return &oauth2.Token{}, &oauth2.Transport{}, err
    }

    transport, err := config.NewTransportWithCode(code)
    if err != nil {
        return &oauth2.Token{}, &oauth2.Transport{}, err
    }

    token := transport.Token()
    return token, transport, nil
}

最后,在第3步中创建的代码可以将一次性代码提交给监听/oauth2的处理程序:

func oauth2Handler(w http.ResponseWriter, r *http.Request) {
    // TODO 检查请求是否具有...
    // - 方法:POST
    // - 内容类型:application/octet-stream; charset=utf-8
    // - CSRF令牌 http://goo.gl/mNCjJm

    body, err := ioutil.ReadAll(r.Body)
    defer r.Body.Close()
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }

    code := string(body[:])
    token, transport, err := exchangeCode(code)
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }

    // 从这里开始可以使用transport
    client := http.Client{Transport: transport}
    service, err := plus.New(&client)
    if err != nil {
        return nil, err
    }

    // https://www.googleapis.com/plus/v1/people/me
    person, err := service.People.Get("me").Do()
    // ...
}

func main() {
    http.HandleFunc("/oauth2", oauth2Handler)
    log.Fatal(http.ListenAndServe(":8000", nil))
}

一些错误处理被省略了,但你可以理解这个思路。

英文:

I would recommend to use 'one-time code flow', as described in the documentation:

> To take advantage of all of the benefits of Google+ Sign-In you must use a hybrid server-side flow where a user authorizes your app on the client side using the JavaScript API client and you send a special one-time authorization code to your server. Your server exchanges this one-time-use code to acquire its own access and refresh tokens from Google for the server to be able to make its own API calls, which can be done while the user is offline. This one-time code flow has security advantages over both a pure server-side flow and over sending access tokens to your server.

As code can be used just once there are less chances of compromising user's account.

Client code is pretty straight forward, follow the example in step 3.

For server-side, I would recommend using the package oauth2 instead of goauth2.

$ go get code.google.com/p/google-api-go-client/plus/v1
$ go get github.com/golang/oauth2
$ go get google.golang.org/appengine

For some reason, oauth2 package requires also appengine package.

Exchanging the one-time code for a reusable token can be done using function NewTransportWithCode:

func exchangeCode(code string) (*oauth2.Token, *oauth2.Transport, error) {
    config, err := google.NewConfig(&oauth2.Options{
        ClientID:     CLIENT_ID,
        ClientSecret: CLIENT_SECRET,
        RedirectURL:  "postmessage",
        Scopes:       []string{"https://www.googleapis.com/auth/plus.login"},
    })
    if err != nil {
        return &oauth2.Token{}, &oauth2.Transport{}, err
    }

    transport, err := config.NewTransportWithCode(code)
    if err != nil {
        return &oauth2.Token{}, &oauth2.Transport{}, err
    }

    token := transport.Token()
    return token, transport, nil
}

And finally the code you created in step 3 can submit one time code to a handler listening at /oauth2:

func oauth2Handler(w http.ResponseWriter, r *http.Request) {
    // TODO Check request has...
    // - Method: POST
    // - Content-Type: application/octet-stream; charset=utf-8
    // - CSRF Token http://goo.gl/mNCjJm

    body, err := ioutil.ReadAll(r.Body)
    defer r.Body.Close()
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }

    code := string(body[:])
    token, transport, err := exchangeCode(code)
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }

    // From here you can use the transport
    client := http.Client{Transport: transport}
    service, err := plus.New(&client)
    if err != nil {
        return nil, err
    }

    // https://www.googleapis.com/plus/v1/people/me
    person, err := service.People.Get("me").Do()
    // ...
}

func main() {
    http.HandleFunc("/oauth2", oauth2Handler)
    log.Fatal(http.ListenAndServe(":8000", nil))
}

Some error handling is missing, but you get the idea.

huangapple
  • 本文由 发表于 2014年8月19日 16:39:56
  • 转载请务必保留本文链接:https://go.coder-hub.com/25379038.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定