【Go】ginを使った一連の認証実装をまとめる

3 min read読了の目安(約3100字 2

概要

Golangで、現状広く使われているWebフレームワークとして挙げられるのがginです。今回はこのginを使って認証の処理を私が実装した場合どのような感じになったのか、というのをまとめます。

前提

  • APIを実装する前提とします。
  • 認証情報はCookieを使ってセッションに保持するものとします。

対応すること

今回、対応することは以下2点とします。

<ログイン処理>

認証にはメールアドレスとパスワードを使用するものとします。パスワードはハッシュ化するので、bcryptというライブラリを使用します。bcryptの詳細については、【Go言語】パスワードをハッシュ化(bcrypt)をご参考ください。
また、ログイン時にセッションにユーザ情報を保存するときは、Json形式で保存するものとします。

<APIの認証ガード>

APIの認証情報はセッションを使うので、セッションから取り出す情報で判断します。また、各handlerの処理を行う前にガードをかけたいので、ミドルウェアを使うものとします。セッションとミドルウェアの概要はGoフレームワークGinでミドルウェアを使ってログインAPIを実装の記事にまとめられています。
また、ginにはグループという機能があって、特定のパス配下にしたいhandlerをまとめるような設定ができます。この時にグループ単位でミドルウェアも設定が行えます。詳細についてはGolangのWebフレームワークginのmiddlewareについての覚書が参考になります。

実装サンプル

メールアドレスでの認証を想定した実装サンプルになります。

<Main>

main.go
func main() {
	router := gin.Default()
	// セッションCookieの設定
	store := cookie.NewStore([]byte("secret"))
	router.Use(sessions.Sessions("mysession", store))
        // ログイン用のhandler
	router.POST("/login", handler.Login)
	// 認証済のみアクセス可能なグループ
	authUserGroup := router.Group("/auth")
	authUserGroup.Use(middleware.LoginCheckMiddleware())
	{
		authUserGroup.GET("/getSample", handler.getSample)
	}
	router.Run()
}

<リクエスト用のmodel>

emailLoginRequest.go
type EmailLoginRequest struct {
	Email    string `json:"email" bson:"email"`
	Password string `json:"password" bson:"password"`
}

<ログイン用のhandler>

login.go
func Login(c *gin.Context) {
	var request authModel.EmailLoginRequest
	err := c.BindJSON(&request)
	if err != nil {
		c.Status(http.StatusBadRequest)
	} else {
		// メールアドレスでDBからユーザ取得(詳細は割愛)
		user, err := authRepository.GetUserByEmail(request.email)
		// ハッシュ値でのパスワード比較
		err = bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(request.password))
		if err != nil {
			c.Status(http.StatusBadRequest)
		} else {
			session := sessions.Default(c)
			// セッションに格納する為にユーザ情報をJson化
			loginUser, err := json.Marshal(auth)
			if err == nil {
				session.Set("loginUser", string(loginUser))
				session.Save()
				c.Status(http.StatusOK)
			} else {
				c.Status(http.StatusInternalServerError)
			}
		}
	}
}

<ミドルウェア>

authMiddleware.go
func LoginCheckMiddleware() gin.HandlerFunc {
	return func(c *gin.Context) {
		session := sessions.Default(c)
		// Json文字列がinterdace型で格納されている。dproxyのライブラリを使用して値を取り出す
		loginUserJson, err := dproxy.New(session.Get("loginUser")).String()

		if err != nil {
			c.Status(http.StatusUnauthorized)
			c.Abort()
		} else {
			var loginInfo model.AuthUser
			// Json文字列のアンマーシャル
			err := json.Unmarshal([]byte(loginUserJson), &loginInfo)
			if err != nil {
				c.Status(http.StatusUnauthorized)
				c.Abort()
			} else {
				c.Next()
			}
		}
	}
}