ginでWebアプリのログイン処理を実装してセッションについて学ぶ
この記事の対象者
Go言語で書かれたWebアプリケーションフレームワークであるginでCookieを使用した基礎的なセッション管理の実装について知りたい方向け
この記事内で紹介している実装の全体が見たい方はこちら
セッションとは
まとめると「Webアプリケーションにアクセス中のユーザー固有の情報を保存する仕組み」
セッションとcookieの関係
gin-contrib/sessions/cookieを使用してセッション管理する
- cookieをセッションストアとして使用する場合、session情報はSet-cookieヘッダでクライアントに送信されブラウザに保存される
- よくあるセッションIDのみCookieに保存するパターンはセッション情報はサーバ側に保存されていて、Cookieに保存されたセッションIDをサーバに送信して管理しているが、gin-contrib/sessions/cookieはセッション情報をすべてCookieに保存する仕組みとなっている
https://github.com/litencatt/playground-gin/pull/1/files
package main import ( "playground-gin/handler" "github.com/gin-contrib/sessions" "github.com/gin-contrib/sessions/cookie" "github.com/gin-gonic/gin" ) func main() { r := gin.Default() store := cookie.NewStore([]byte("secret")) r.Use(sessions.Sessions("sample_session", store)) r.LoadHTMLGlob("templates/*.go.tmpl") r.GET("/", handler.RootHandler) r.GET("/foo", handler.FooHandler) r.GET("/bar", handler.BarHandler) r.Run(":8090") }
以下セッションへの値の保存と取得例
func RootHandler(c *gin.Context) { session := sessions.Default(c) session.Set("session_value", 1) session.Save() c.HTML(http.StatusOK, "root.go.tmpl", gin.H{ "title": "index", "title": "root", }) } func FooHandler(c *gin.Context) { session := sessions.Default(c) val := session.Get("session_value") var v int if val != nil { v = val.(int) + 1 } session.Set("session_value", v) session.Save() c.HTML(http.StatusOK, "foo.go.tmpl", gin.H{ "title": "foo", "value": val, }) }
Chromeの場合DevToolのApplicationタブよりCookiesを見るとsample_sessionという名前でデータが保存されているのがわかる
セッション情報を複数扱いたい場合
sessions.SessionsMany()
を使用することで2つ以上のセッションを扱うことが可能
https://github.com/litencatt/playground-gin/pull/2/files
// main.go sessionNames := []string{"session_foo", "session_bar"} r.Use(sessions.SessionsMany(sessionNames, store))
使用する場合はセッション名を指定してsessions.DefaultMany()
で取得する
sessionFoo := sessions.DefaultMany(c, "session_foo") sessionFoo.Set("value", 1) sessionFoo.Save() sessionBar := sessions.DefaultMany(c, "session_bar") sessionBar.Set("value", 1) sessionBar.Save()
ログインとセッションの関係
セッションはアクセス中のユーザー固有の情報を保存する仕組みであることから、これを利用してログイン処理などで認証後の状態を表す情報をセッションに保存することで以降のリクエストは認証済みのユーザーからのリクエストとして処理することができるようになり、マイページなどそのユーザーしか閲覧することができないような情報をユーザーに参照させることができるようになる。
これをginで実装する場合、今回以下のようにPOST /login
のハンドラでセッション内にloginというキーに対して1という値を設定しこれをログイン情報として保存することで実現してみました。
ここでは超簡略化しているため実際にはログインフォームより/loginにPOSTされてきたメールアドレスやパスワードを使用した認証処理の実装が必要になると思います。
https://github.com/litencatt/playground-gin/pull/3
func PostLoginHandler(c *gin.Context) { sessionFoo := sessions.DefaultMany(c, "session_foo") sessionFoo.Set("login", 1) sessionFoo.Save() c.Redirect(http.StatusFound, "/mypage") }
また、未ログインユーザーの/mypageへのアクセスを拒否するため現在の認証処理を行うためのmiddlewareを登録します。これにより指定したエンドポイントに対してのアクセス時にUseで登録した処理が事前に呼び出されるようになります。
a := r.Group("/") a.Use(handler.AuthCheck()) { a.GET("/mypage", handler.MyPageHandler) }
今回登録したAuthCheck()
ではセッション情報にもつloginの値が1となっているかをチェックしており、1でない場合はエラーページにリダイレクトするようにしています。
これによりログインしている場合のみ/mypageへのアクセスができるようにすることができました。
func AuthCheck() gin.HandlerFunc { return func(c *gin.Context) { sessionFoo := sessions.DefaultMany(c, "session_foo") login := sessionFoo.Get("login") if login != 1 { c.Redirect(http.StatusFound, "/error") } } }