Go で OAuth2.0
Posted on Sep 2, 2018
Go 学習のため、Go を使ってウェブアプリを作ってみた際、困ったことをまとめておこうと思います。これまで、Python + Flask でウェブをスクレイピングしてJSONを返す、みたいなのは作ったことあったのですが、型のある言語でのウェブアプリ作成は初めてになります。
題材としては今回、「認可サーバー」を選んでいます。現職にて、事業開発(というか営業)として認可ソリューションを売っている事もあり、OAuth2.0 への理解をもう少し深めたい、というのがモチベーションになります。教材として、『OAuth 2 in Action』を使っています。
OAuth 2 in Action
Manning 出版より2017年に出版された、OAuth2.0 を理解するために書かれた本です。認可サーバー側だけでなく、クライアント側、リソースサーバー側の視点からも OAuth2.0 のフローについて書かれた本であり、認可サーバーをスクラッチで実装するために書かれた本、というよりは、OAuth2.0 を正しく理解し既存のソリューションを正しく使いましょう、というニュアンスの内容になります。実際、最後のあとがき部分にて、“We know that almost no one reading this book will implement an OAuth ecosystem from scratch, end to end. ” と書かれています。
本に付属のサンプルコードは、Javascript (Node.js)で書かれており、非常にシンプルでわかりやすいです。この本を一通り読み、コードを触ったおかげで、難解だった RFC6749 もスラスラ読めるようになりました。同時に、上記のサンプルコードが、OAuth2.0 を理解してもらうためのものであり、実戦で使うためにはまだまだ工夫が必要だろうな、というのもよくわかりました。出版されるかどうかわかりませんが、日本語版が出たら、お客さんに配って回りたい気分です。
Go での実装
上記のサンプルコードをただ眺めるだけでは理解に時間がかかるだろうな、と思った事もあり、学習中の Go を使って写経を行ってみました。Go を使ってウェブアプリを作るのは初めてだった事もあり、エラーハンドリング等で色々悩みました。
困った事1:フレームワーク選び
Python であれば、Django や Flask といった有名フレームワークがあり、選択が楽だったのですが、Go では、gin や echo など数え切れないくらい種類があり、どれがベストかわからず。高速な事で有名な httprouter の利用も考えましたが、後述のエラーハンドリングを考えると使いづらかったため、今回は、標準ライブラリだけでの実装を選択しました。
困った事2:エラーハンドリング
エラーハンドリングについては、公式ドキュメントを参考にしました。
ただ、これだけでは、グローバル変数を扱いづらかったため、下記のブログを参考に修正を加えました。
http.Handler and Error Handling in Go
出来上がったコードはこんな感じです。あとは main() のところで、http.Handleを使い、パスと関数を登録していくだけになります。標準ライブラリだけを使って、すっきりした形で書けるようになった気がします。
// クライアントに送り返す内容
type responseContent interface {
jsonify() ([]byte, error)
}
// クライアントに返すデータ(またはエラー)とHTTPステータスコード
type response struct {
content responseContent
code int
}
// env contains global parameters for a web application
type env struct {
template *template.Template
//DBのアドレスとか、その他グローバル変数はここへ
}
// handlerFunc is a function that can be registed to a router
type appHandlerFunc func(http.ResponseWriter, *http.Request, *env) *response
// appHandler is a struct that contains appContext and an original appHandler
type appHandler struct {
env *env
handlerFunc appHandlerFunc
}
// ServeHTTP makes the handler implement the http.Handler interface
func (h *appHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
//Response a JSON if catch an error
if res := h.handlerFunc(w, r, h.env); res != nil {
// Jsonify the response. Return a error if failed to create a JSON.
result, err := res.content.jsonify()
if err != nil {
http.Error(w, "Internal Error", http.StatusInternalServerError)
log.Println(err)
return
}
// Send back JSON
w.WriteHeader(res.code)
w.Header().Set("Content-Type", "application/json")
w.Header().Set("Cache-Control", "no-store")
w.Header().Set("Pragma", "no-cache")
w.Write(result)
}
}
困った事3:パッケージ、ファイルの構造
これに関してはいまだに理解できていない部分が多くありますが、下記のブログ等を参考にしました。今回書いたコードは利用シーンを想定いないので、正直適当です。
Structuring Go API Successful Go Program Design Go Project Layout
まとめ
まだまだレベルは低いですが、Go を使ってウェブアプリは作れるようにはなりました。今後は、goDoc なんかも意識しつつ、また別の物を作ってみようと思っています。より良いベストプラクティス的なものがあれば、教えていただけると嬉しいです。
なお、今回作った認可サーバーはこちらです。色々途中なのですが、十分目的は達したので作りかけのまま放置予定です。
comments powered by Disqus