Help us understand the problem. What is going on with this article?

Go言語でWebサイトを作ってみる (7: ログインしたユーザーしか見られないページを作ってみる)

More than 3 years have passed since last update.

すべての記事の一覧はこちら。
Go言語でWebサイトを作ってみる:目次
http://qiita.com/y_ussie/items/8fcc3077274449478bc9

前回のJSONからのユーザー情報読み込み篇 http://qiita.com/y_ussie/items/8704ce209704bf191e63 の続きになります。
今回は、前回作成したユーザー情報読み込み機能を使用して、ログイン機能を作っていきたいと思います。

やりたいこと

前回は

  • ユーザー情報をJSONから読み込んで参照できるようにする

まで作成しましたので、今回は

  • ログイン画面からユーザーID/パスワードでログインできるようにする
  • ログインユーザーしか見られないページを作ってみる

を実装してみたいと思います。

まずは認証関係の処理をまとめたコードを作成します。

auth.go

https://github.com/yoshinoyaussie/golang-website-sample/blob/chapter-7/webserver/auth.go

auth.go
package main

import (
    "errors"

    "./model"
    "./session"
    "github.com/labstack/echo"
)

// auth.goが返すエラーの定義
var (
    ErrorInvalidUserID   = errors.New("Invalid UserID")
    ErrorInvalidPassword = errors.New("Invalid Password")
    ErrorNotLoggedIn     = errors.New("Not Logged In")
)

// UserLogin はユーザーログイン時の処理を行います。
func UserLogin(c echo.Context, userID string, password string) error {
    users, err := userDA.FindByUserID(userID, model.FindFirst)
    if err != nil {
        return err
    }
    user := &users[0]
    encodePassword := model.EncodeStringMD5(password)
    if user.Password != encodePassword {
        return ErrorInvalidPassword
    }
    sessionID, err := sessionManager.Create()
    if err != nil {
        return err
    }
    err = session.WriteCookie(c, sessionID)
    if err != nil {
        return err
    }
    sessionStore, err := sessionManager.LoadStore(sessionID)
    if err != nil {
        return err
    }
    sessionData := map[string]string{
        "user_id": userID,
    }
    sessionStore.Data = sessionData
    err = sessionManager.SaveStore(sessionID, sessionStore)
    if err != nil {
        return err
    }

    return nil
}

// UserLogout はユーザーログアウト時の処理を行います。
func UserLogout(c echo.Context) error {
    sessionID, err := session.ReadCookie(c)
    if err != nil {
        return err
    }
    err = sessionManager.Delete(sessionID)
    if err != nil {
        return err
    }

    return nil
}

// CheckUserID は指定されたユーザーIDでログインしているか確認します。
func CheckUserID(c echo.Context, userID string) error {
    sessionID, err := session.ReadCookie(c)
    if err != nil {
        return err
    }
    sessionStore, err := sessionManager.LoadStore(sessionID)
    if err != nil {
        return err
    }
    sessionUserID, ok := sessionStore.Data["user_id"]
    if !ok {
        return ErrorNotLoggedIn
    }
    if sessionUserID != userID {
        return ErrorInvalidUserID
    }

    return nil
}

auth.go には、以下の3つの関数を実装しています。

UserLogin()

  • パラメータとしてユーザーID, パスワードを受け取り、ユーザー情報と照合して一致するかチェックします。
  • チェックの結果、一致した場合には以下の処理を行います。
    • セッションデータストアを作成し、セッションIDを取得します。
    • セッションIDをCookieに保存します。
    • セッションIDをキーにして、セッションデータストアにユーザーIDを保存します。

UserLogout()

  • CookieよりセッションIDを取得します。
  • セッションIDをキーにして、セッションデータストアの削除を行います。

CheckUserID()

  • CookieよりセッションIDを取得します。
  • セッションIDをキーにして、セッションデータストアよりユーザーIDを取得します。
  • パラメータとして受け取ったユーザーIDと、セッションデータストアより取得したユーザーIDが一致するかチェックします。

セッションデータストアに関する処理は、以前のセッションデータストア篇
http://qiita.com/y_ussie/items/b1db86b0b54ec69bb928
で作成したものです。

これらの関数を使用して、ログイン画面関連のリクエストハンドラを作成します。

https://github.com/yoshinoyaussie/golang-website-sample/blob/chapter-7/webserver/handler.go

handler.go
    e.GET("/login", handleLoginGet)
    e.POST("/login", handleLoginPost)
    e.POST("/logout", handleLogoutPost)
handler.go
// GET:/login
func handleLoginGet(c echo.Context) error {
    return c.Render(http.StatusOK, "login", nil)
}

// POST:/login
func handleLoginPost(c echo.Context) error {
    userID := c.FormValue("userid")
    password := c.FormValue("password")
    err := UserLogin(c, userID, password)
    if err != nil {
        c.Echo().Logger.Debugf("User[%s] Login Error. [%s]", userID, err)
        msg := "ユーザーIDまたはパスワードが誤っています。"
        data := map[string]string{"user_id": userID, "password": "", "msg": msg}
        return c.Render(http.StatusOK, "login", data)
    }
    return c.Redirect(http.StatusTemporaryRedirect, "/users/"+userID)
}

// POST:/logout
func handleLogoutPost(c echo.Context) error {
    err := UserLogout(c)
    if err != nil {
        c.Echo().Logger.Debugf("User Logout Error. [%s]", err)
        return c.Render(http.StatusOK, "login", nil)
    }
    msg := "ログアウトしました。"
    data := map[string]string{"user_id": "", "password": "", "msg": msg}
    return c.Render(http.StatusOK, "login", data)
}

GET /login

ログイン画面を表示します。「ログイン」押下で POST:/login に遷移します。

POST /login

ログイン処理を行います。ここで UserLogin() を使用しています。
ログインが正常に行われると、/users/<ユーザーID> のページに遷移します。

POST /logout

ログアウト処理を行い、ログイン画面を表示します。

ここまででログインの処理は作成できましたので、最後に /users/<ユーザーID> で遷移する各ユーザーのページに、現在ログイン中かどうかのチェックを追加します。

handler.go
// GET:/users/:user_id
// POST:/users/:user_id
func handleUsers(c echo.Context) error {
    userID := c.Param("user_id")
    err := CheckUserID(c, userID)
    if err != nil {
        c.Echo().Logger.Debugf("User Page[%s] Roll Error. [%s]", userID, err)
        msg := "ログインしていません。"
        return c.Render(http.StatusOK, "error", msg)
    }
    users, err := userDA.FindByUserID(c.Param("user_id"), model.FindFirst)
    if err != nil {
        return c.Render(http.StatusOK, "error", err)
    }
    user := users[0]
    return c.Render(http.StatusOK, "user", user)
}

最初に CheckUserID() でパスパラメータにて指定されたユーザーIDでログインしているかチェックし、ログインしていない場合にはエラーページを表示します。
ログインしている場合にはユーザーのページを表示します。

実行結果

ログイン画面

/login で以下のログイン画面が表示されます。

1.png

ユーザー「a-chan」でログインしてみます。

2.png

ユーザー画面

/users/a-chan に遷移し、ユーザー「a-chan」の画面が表示されます。

3.png

この状態で、アドレスバーに /users/nocchi を入力し、ユーザー「nocchi」のページを表示しようとしてみます。

認証エラー

エラー画面が表示されました。

4.png

再度 /users/a-chan を入力し、ユーザー「a-chan」のページを表示してみます。
ユーザー「a-chan」でログイン中なので、こちらは正常に表示されます。

5.png

「ログアウト」を押下します。

ログアウト

ログイン画面に戻ります。

6.png

今回使用したコードについて

今回使用したコードの一式は以下の GitHub に置いてあります。
https://github.com/yoshinoyaussie/golang-website-sample/tree/chapter-7

次回予定

すべてのユーザーのページを参照できる管理者ユーザーを作成してみます。
また、ユーザー情報の編集・追加・削除ができるようにしてみます。

y_ussie
しがないSEです。 仕事ではC++ばっかり書いてます。 趣味プログラミングでは最近Goに傾倒しています。 ハードウェアも少し。 https://twitter.com/y_ussie
https://ussie.net
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした