Go
golang
OAuth
oauth2

GolangでGoogle Driveにアクセスするためのトークンを取得

More than 3 years have passed since last update.

おおざっぱに、Google Driveにアクセスするには、以下の様な手順を踏めばいいみたいです。

  1. Google Developer ConsoleにアクセスしてDrive APIを有効に
  2. クライアントID, クライアントシークレット、リダイレクトURLを生成
  3. 生成した上記の3つの文字列を使ってブラウザで認証
  4. 認証したときに表示される文字列を使って、トークン(JSON形式)を作成
  5. 以後はトークンを使ってAPIにアクセス。リフレッシュなどはクライアントAPIがやってくれるっぽい。

1, 2の手順については下記のページがスクリーンショット付きで親切です。

http://d.hatena.ne.jp/taknb2nch/20140225/1393314429

3, 4の部分はプログラムで行います。ウェブサービスなんかは実サービスコードの一部に認証のための機能もバンドル(つまり、3, 4, 5がひとつのプログラム)されると思います。

バッチのプログラムなどの場合は3, 4だけやっておいて、生成されるトークンをスクリプト等に埋め込めば大丈夫っぽいという噂。本来ならGoogleが提供するサービスアカウント機能なんかを使うのが正解っぽいのですが、社内の運用上、システム用のアカウントでやってね、と言われることもあるかもしれないのでこの部分だけ切り出してみました。

下記のサンプルコードでは古いoauthライブラリを使っていたのですが、これで使っているgoauth2は古いみたいなので、最新のoauth2ライブラリに書きなおしました。

https://github.com/tyokoyama/golangcafe/tree/master/goauth2sample

以下がそのコードです。

使い方としては、

  1. go run create_token.goで起動
  2. クライアントID、シークレット、リダイレクトURLを聞かれるので、Google Developer Consoleで表示されている内容をコピペして入力
  3. URLが表示されるのでブラウザを開いて認証
  4. 生成されたコードをコピーして go run create_token.go <コード> と引数に渡して実行
  5. ../token/token.json フォルダにトークンファイルが生成される

といった感じです。使うときはJSONファイルを読み込んで、UnmershalしてTokenオブジェクトを作って、oauth2のConfig.Clientメソッドに渡してHTTPクライアントを作ればAPIアクセスができるんじゃないですかね?go-bindataとか使うとトークンを隠せておしゃれですよね。コンパイル言語バンザイ。

create_token.go
package main

import (
    "golang.org/x/oauth2"
    "os"
    "io/ioutil"
    "flag"
    "fmt"
    "log"
    "encoding/json"
)

type Auth struct {
    ClientID string         `json:"id"`
    Secret string           `json:"secret"`
    RedirectUrl string      `json:"redirect"`
}

func GetAuthInfo() (auth Auth, err error) {
    var text []byte
    auth = Auth {
        ClientID: "",
        Secret: "",
        RedirectUrl: "",
    }

    file, err := os.Open("auth.json")
    defer file.Close()
    if os.IsNotExist(err) {
        // ファイルなし

        inputNewData(&auth)

        err = writeFile(auth)

    } else if err == nil {
        text, err = ioutil.ReadAll(file)
        if err != nil {
            return auth, err
        }

        err = json.Unmarshal(text, &auth)
        if err != nil || auth.ClientID == "" || auth.Secret == "" || auth.RedirectUrl == "" {
            // データがおかしい->新規入力
            inputNewData(&auth)
            err = writeFile(auth)
        }
    }

    return
}

func inputNewData(auth *Auth) {
    // 新規入力
    fmt.Println("Input ClientID")
    fmt.Scanf("%s\n", &auth.ClientID)
    fmt.Println("Input Secret")
    fmt.Scanf("%s\n", &auth.Secret)
    fmt.Println("Input Redirect URL")
    fmt.Scanf("%s\n", &auth.RedirectUrl)
}

func writeFile(auth Auth) error {
    text, err := json.Marshal(auth)
    err = ioutil.WriteFile("auth.json", text, 0777)
    return err
}

func main() {
    flag.Parse()

    // 認証情報の取得(何もなければ、入力を促します)
    // clientID、secret、redirect_urlはDevelopers ConsoleのCredentialsからコピー&ペーストして下さい。
    var auth Auth
    var err error
    if auth, err = GetAuthInfo(); err != nil {
        log.Fatalln("GetAuthInfo: ", err)
    }

    fmt.Println("Start Execute API")

    // 認証コードを引数で受け取る。
    code := flag.Arg(0)

    config := &oauth2.Config{
        ClientID:     auth.ClientID,
        ClientSecret: auth.Secret,
        RedirectURL:  auth.RedirectUrl,
        Scopes:       []string{" https://www.googleapis.com/auth/drive.readonly"},
        Endpoint: oauth2.Endpoint{
            AuthURL:  "https://accounts.google.com/o/oauth2/auth",
            TokenURL: "https://accounts.google.com/o/oauth2/token",
        },
    }

    var NoContext oauth2.Context = nil

    // 認証コードなし=>ブラウザで認証させるためにURLを出力
    if code == "" {
        url := config.AuthCodeURL("")
        fmt.Println("ブラウザで以下のURLにアクセスし、認証して下さい。")
        fmt.Println(url)
        return
    }

    // 認証トークンを取得する。(取得後、キャッシュへ)
    token, err := config.Exchange(NoContext, code)
    if err != nil {
        log.Fatalln("Exchange: ", err)
    }
    text, err := json.Marshal(token)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("token is saved to ../token/token.json")
    err = ioutil.WriteFile("../token/token.json", text, 0777)
    if err != nil {
        log.Fatal(err)
    }
}