前回までの記事で、セッション管理機構パッケージの実装が完了しました。
設計および実装内容については、以下の記事をご参照ください。
【Go実践】セッション管理を行う(1)―セッション管理機構の設計
【Go実践】セッション管理を行う(2)―セッションマネージャの実装
【Go実践】セッション管理を行う(3)―セッションの構造定義とセッション操作の実装
今回は、ログイン機能を通してこのパッケージを実用し、セッション管理を実行する例を解説していきたいと思います。
実装する内容
今回は、ログイン画面とログイン後に表示されるトップ画面を実装します。
ログイン画面は、アカウントとパスワードを入力し、ボタンクリックして認証を行うオーソドックスなものです。
2画面間でのセッション共有の解説が目的ですので、ログイン認証の機能自体はハリボテで、アカウントとパスワードの入力内容にかかわらず認証成功するものになります。
ログイン後のトップ画面は、ログイン済の場合と未ログインの場合で表示内容を変更します。
クライアントの状態(ステート)に応じてレスポンスを分岐させるために、セッション管理の機能を使用することになります。
HTMLテンプレートの実装
ログイン画面のテンプレート
まずはログイン画面のHTMLテンプレートを実装します。
templatesディレクトリにlogin.gtplファイルを作成し、以下の通り記述してください。
<!DOCTYPE html> <html> <head> <link rel="stylesheet" type="text/css" href="../asset/css/style.css"> </head> <body> <form action="exec-login" method="post"> <h2>ログイン</h2> <table> <tr> <td>アカウント名</td> <td><input type="text" name="account" value=""></td> </tr> <tr> <td>パスワード</td> <td><input type="password" name="passwd" value=""></td> </tr> </table> <input type="submit" class="btn-push btn-push-blue" value="ログイン"> </form> </body> </html>
ログイン後画面のテンプレート
続いて、ログイン後トップ画面のテンプレートです。
ファイル名はlogin-top.gtplとしてください。
<!DOCTYPE html> <html> <head> <link rel="stylesheet" type="text/css" href="../asset/css/style.css"> </head> <body> {{ if .isLogIn }} <h2>ようこそ{{ .account }}さん</h2> <form action="exec-logout" method="POST"> <input type="submit" class="btn-push btn-push-blue" value="ログアウト"> </form> </body> {{ else }} <h2>このページは会員専用です</h2> <a href="https://localhost:10443/login">こちらのページ</a>からログインしてください {{ end }} </body> </html>
.isLogInという変数フィールドにより表示内容の分岐を行っています。
このフィールドに、リクエストハンドラの処理でセッションを利用しクライアントのステート情報を流入することになります。
リクエストハンドラの実装
続いては、リクエストパスに対応するハンドラを実装します。
ログイン画面のリクエストハンドラ
req_handlerディレクトリにh_Login.goファイルを作成し、以下の通り記述してください。
package req_handler // 独自のHTTPリクエストハンドラパッケージ import ( "fmt" "html/template" "net/http" ) // ログイン画面 func HandlerLogin(w http.ResponseWriter, req *http.Request) { // テンプレートをパースする tpl := template.Must(template.ParseFiles("templates/login.gtpl")) // テンプレートに出力する値をマップにセット values := map[string]string{} // マップを展開してテンプレートを出力する if err := tpl.ExecuteTemplate(w, "login.gtpl", values); err != nil { fmt.Println(err) } } // ログイン処理 func HandlerExecLogin(w http.ResponseWriter, req *http.Request) { // セッションを開始 manager := sessions.NewManager() sessions.StartDefaultSession(manager) // セッション変数をセット session := manager.Get(req, sessions.DefaultCookieName) session.Set("account", req.FormValue("account")) // セッションを保存 session.Save() // ログイントップ画面へリダイレクト http.Redirect(w, req, "https://localhost:10443/login-top", 301) }
HandlerLogin関数は、今までに実装してきた画面と同様に、テンプレートをパースして必要なデータを流入するオーソドックスなハンドラ関数です。
もうひとつ、HandlerExecLoginというハンドラ関数が実装されていることに着目してください。
このハンドラは、ログインボタンをクリックした際のサブミット先のパスに対応します。
このハンドラ内でセッションの生成を行い、入力値をセッション変数にセットした上で、ログイン後トップ画面へリダイレクトしています。
ログイン後画面のリクエストハンドラ
req_handlerディレクトリにh_LoginTop.goファイルを作成し、以下の通り記述してください。
package req_handler // 独自のHTTPリクエストハンドラパッケージ import ( "fmt" "html/template" "net/http" ) // ログイントップ画面 func HandlerLoginTop(w http.ResponseWriter, r *http.Request) { // テンプレートをパースする tpl := template.Must(template.ParseFiles("templates/login-top.gtpl")) isLoggedIn := "" account := "" // セッションを取得 session := manager.Get(req, sessions.DefaultCookieName) if session.Values["account"] { isLoggedIn = "isLoggedIn" account = session.Values["account"] } values := map[string]string{ "isLoggedIn": isLoggedIn, "account": account, } // マップを展開してテンプレートを出力する if err := tpl.ExecuteTemplate(w, "login-top.gtpl", values); err != nil { fmt.Println(err) } } // ログアウト処理 func HandlerExecLogout(w http.ResponseWriter, req *http.Request) { // セッションの取得 session := manager.Get(req, sessions.DefaultCookieName) // セッションを削除する session.Terminate() // ログイン画面へリダイレクト http.Redirect(w, req, "https://localhost:10443/login", 301) }
まずHandlerLoginTop関数が、ログイン後画面のパスに対するリクエストハンドラです。
17行目でログイン時に生成したセッション情報を取得しています。
セッションにアカウント名がセットされるかどうかで、ログイン済/未ログインの判定を行っています。
また、HandlerExecLogoutは、ログイン済の表示画面でログアウトを行った場合の処理です。
セッションを破棄後にログイン画面にリダイレクトしています。
メイン処理
最後に、main.goファイルにリクエストの処理を追加しましょう。
追加するのは、ログイン画面、ログイン後トップ画面、およびログイン処理・ログアウト処理へのリクエストです。
package main import ( "fmt" "net/http" "./req_handler" // 実装したHTTPリクエストハンドラパッケージの読み込み ) func main() { // "user-list"へのリクエストを関数で処理する http.HandleFunc("/user-list", req_handler.HandlerUserList) // "user-form"へのリクエストを関数で処理する http.HandleFunc("/user-form", req_handler.HandlerUserForm) // "user-confirm"へのリクエストを関数で処理する http.HandleFunc("/user-confirm", req_handler.HandlerUserConfirm) // "user-registered"へのリクエストを関数で処理する http.HandleFunc("/user-registered", req_handler.HandlerUserRegistered) // "user-edit"へのリクエストを関数で処理する http.HandleFunc("/user-edit", req_handler.HandlerUserEdit) // "user-update"へのリクエストを関数で処理する http.HandleFunc("/user-update", req_handler.HandlerUserUpdate) // "login"へのリクエストを関数で処理する http.HandleFunc("/login", req_handler.HandlerLogin) // "login-top"へのリクエストを関数で処理する http.HandleFunc("/login-top", req_handler.HandlerLoginTop) // "exec-login"へのリクエストを関数で処理する http.HandleFunc("/exec-login", req_handler.HandlerExecLogin) // "exec-logout"へのリクエストを関数で処理する http.HandleFunc("/exec-logout", req_handler.HandlerExecLogout) // css・js・イメージファイル等の静的ファイル格納パス http.Handle("/asset/", http.StripPrefix("/asset/", http.FileServer(http.Dir("asset/")))) // サーバーを起動 //http.ListenAndServe(":8080", nil) err := http.ListenAndServeTLS(":10443", "crt/localhost.crt", "crt/localhost.key", nil) if err != nil { fmt.Printf("ERROR : %s", err) } }
動作確認
それでは、実際に画面の動きを確認してみましょう。
まずは、未ログイン状態でログイン後画面にアクセスします。
main.goファイルを実行し、ブラウザでhttps://localhost:10443/login-topにアクセスしてください。
想定通り、未ログインの内容が画面表示されますね。
画面内のリンクをクリックし、ログイン画面に遷移しましょう。
ログイン画面が表示されたら、任意のアカウント名とパスワードを入力し、[ログイン]ボタンをクリックします。
今度は下図のように、入力したアカウント情報とログアウトボタンが表示されると思います。
更に、ここからログアウトを行った場合の挙動も確認してみましょう。
まずはログアウトボタンをクリックし、ログイン画面に遷移します。
この時点で、ログアウト処理によりセッションは破棄されているはずです。
この状態で再度、https://localhost:10443/login-topにアクセスしてみましょう。
想定通り、未ログイン状態の表示内容に戻っていることが確認できました。
終わりに
今回で、セッション管理に関する記事は一段落になります。
ここまで4回にわたって実装・解説を行ってきましたが、いかがでしょうか。
これまで実装してきた内容に比べると、設計・実装ともに複雑な内容だったと思います。
しかし、ログイン機能が必要なWEBサイトに限らず、本格的なWEBアプリケーションを実装するならば、クライアントのステートを保持するセッション管理の技術は必須です。
ご紹介した設計を参考に、要件に応じた実装していただければ幸いです。