It Made My Day

Acutally, my whole life is just one big yak shaving exercise.

Goのミドルウェアパターン

ミドルウェア is 何

ミドルウェア」という単語自体は幅広い意味を持つが、ミドルウェアパターンというのは、(どの言語を使用するかに関わらず) HTTPリクエストを処理する際にURLに関わらず行う共通の処理を設定すること。Webサーバーと実際のハンドラーの中間にあるため、「ミドルウェア」と呼ばれる。

f:id:turbofish:20220227233742p:plain

出所:drstearns.github.io

ミドルウェアは、下記のような用途を任せるレイヤとして使用されることが多い。

  • リクエストのログの記録
  • 認証処理
  • キャッシュのチェック
  • APIをクロスオリジンで呼び出し可能にする
  • その他レスポンスの圧縮とかなんらか共通の処理

Goでミドルウェアパターン

net/http 単体で実装する場合

ハンドラー(http.Handler)をラップしたミドルウェアハンドラーを定義するのが一般的なやり方っぽい。

雰囲気

mux := http.NewServeMux()  // マルチプレクサ
mux.HandleFunc("/", middleware1(middleware2(middleware3(indexHandler)))) // ハンドラー関数を登録

個人の感想だが見通しが悪くて読みづらいし、ミドルウェア用の関数がハンドラー関数のinterfaceを満たすようにするために若干冗長な実装になる。

まあ言うて若干の差ではあるし、サードパーティ製のMiddleware用ライブラリも色々あるのでそれらを組み合わせて使うという選択肢もあるけど、ginなどのWAFを使っていればより簡潔に書ける。個人的には、処理の流れをパッと見て理解しやすいと感じるからこっちが好き。

WAFを使って実装する場合

ginを使うとこんな感じ。

router := gin.Default()
router.Use(middleware1)
router.Use(middleware2)
router.Use(middleware3)
// こんな書き方もできる
// router.Use(middleware1, middleware2, middleware3)

router.GET("/", indexHandler)
router.Run()

それぞれの処理を跨いでデータを受け渡ししたい場合は、ginのコンテキストを用いて伝播させる。 ミドルウェア関数&ハンドラー関数は、router (gin.Engine)に適用した順番で実行される。ミドルウェア関数の中でc.Next()を呼ぶことで次の関数に移行する。

// middleware2の中で c.Next() が呼ばれるとここに来る
func middleware3() gin.HandlerFunc {
    return func(c *gin.Context) {
        fmt.Println("-------middleware3 Start------")
        // contextに値をセットすると、ハンドラ関数内でも使用できる
        c.Set("hoge", "fuga")
        // ハンドラ関数の処理に移行
        c.Next()
        // ハンドラ関数実行後にここに来る
        fmt.Println("-------middleware3 End------")
    }
}

ちなみに、ロガーとクラッシュからのリカバリgin.Default()で生成されるエンジンに標準搭載されている。

// Default returns an Engine instance with the Logger and Recovery middleware already attached.
func Default() *Engine {
    debugPrintWARNINGDefault()
    engine := New()
    engine.Use(Logger(), Recovery())
    return engine
}

echoでも似たような書き方ができる。 ginにあったロガーとクラッシュリカバリ以外にも、BasicAuthやCORSなどのメジャーな用途については標準のミドルウェアが定義されているようだ。

e := echo.New()
e.Use(middleware.Recover())
e.Use(middleware.Logger())

「OAuth徹底入門」のサンプルコードをGoで書いてみた

認可 is 何みたいな話はこちら

OAuth2.0 に則った認可コードフローのMVP実装をGoで書いた

業務で認可サーバーを開発している関係で、最近OAuth徹底入門 セキュアな認可システムを適用するための原則と実践を読んだ。 歴史ある分野だけにフローや仕様など理解しようとすると複雑だが、基礎となる概念から技術的な話までいろんなレイヤの思考を行ったり来たりしながらも順を追って理解できる良書だったため、OAuth2.0を学びたい方には圧倒的におすすめ。

サンプルコードはJavaScript & Expressで書かれているのだが、Goだとライブラリの使い勝手などで多少異なる部分もあるため、自力で書き直してみた。

github.com

なお、「サンプルコードをGoで書いてみた」というのは実質嘘で、現状進捗はWebアプリを想定したBearerトークンを使う認可コードフローの部分のみ。つまり、3章まで。 だが、基本的に認可コードフロー以外のパターンを使用することは推奨されていないため、一旦ここで区切りとすることにした。 今後もちょこちょこ書きたいと思っていて、個人的に特に実装したいところは下記(優先度順)。

  • JWTトークンを用いる場合の実装
    • トークン取り消し (Token Revocation)
  • 動的クライアント登録
  • OAuthを用いた認証
  • PoPトークンを用いる場合の実装

OAuth2.0完全理解への道

結論、ググってシュッと概要を理解しようとしても秒で心が折れる類の話だったので、いきなりこの本を読むのがむしろ近道だと感じた。 「きちんと理解したい場合は結局のところ手を動かすことが一番早い」というのはOAuthに限った話ではないが、特にOAuthを勉強する上では、「あれ、これってどこからとってくるデータだっけ?」とか、「なんでこの仕組みで脆弱性防げるんだっけ?」とか、本の説明を読んでるだけだと空中戦感が出てきやすい。本書のコードを追うだけでもかなり勉強にはなるが、実装しながら地道に頭に叩き込むことで知識が定着する類だと思う。

名著の宿命とも言えるのかもしれないが、それでもやはりこの本は分厚い。サラッと読むだけでも大変なので、必然的にたまに全体像と自分の現在地を把握したくなる時はあるだろう。 いずれにせよざっくり理解したいという場面で個人的に最も勉強になったのは、こちら↓の動画。

www.youtube.com

AuthleteさんはSaaSサービスとして認可機能を提供している会社で、Qiitaなどでも記事を書いていらっしゃるので、気になる方は見てみてほしい。

余談

OAuth 2.0を勉強するニーズがなくても、HTTPサーバーの実装に慣れてない人がWebに関する知識を深めることもできそうなテーマだと感じた。サラッとフローを追うためだけに実装することももちろんできるが、細かいケースに対応しようと思うとHTTP周りの仕様を細かく調べることになるだろう。実際、ここまでhandlerがモリッとするコードはサーバーサイドをやってるとあまりお目にかからない気がする。

まずhandlerとかがよくわからん、という方はこちら↓の本がとてもおすすめ。

また、GithubのREADMEに使用したライブラリについても書いたが、Goを用いた開発についても学べるサンプルになると良いなとも思っている。