0
0

Go言語でログイン機能を実装してみた

Posted at

はじめに

Go言語で簡単なログイン機能(サインアップ、サインイン)を実装したときのお話
今回も要素検証だけですので、単純構成で作っています

検証環境

こちらの記事で構築した環境を利用しました。

ディレクトリ構成

go-gin-gorm
├── tmp
├── .air.toml
├── docker-compose.yml
├── Dockerfile
├── go.mod
├── go.sum
├── libraries
│       └ crypto.go
└── main.go

実行条件

Golang v1.22.3
Gin v1.10.0
GORM v1.25.10
MySQL 8.0.29

ログイン機能の実装

サインインを確認するために実装したサインアップとサインインの実装例を以下で示します。

パスワードの暗号化

暗号化にはbcryptを使用しています。
bcryptというのはパスワードのハッシュ化手法のひとつです。

プロジェクト以下に libraries ディレクトリを作成し、その配下に crypto.go を作成しましょう。

crypto.go
package crypto

import (
    "golang.org/x/crypto/bcrypt"
)

// 暗号化用
func PasswordEncrypt(password string) (string, error) {
    hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
    return string(hash), err
}

// パスワードの比較用
func CompareHashAndPassword(hash, password string) error {
    return bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
}

アカウント作成用の処理

まずはログイン機能のひとつ、サインアップ機能を作ります。

main.go
package main

import (
    "github.com/gin-gonic/gin"
    "net/http"
    "gorm.io/driver/mysql"
    "gorm.io/gorm"
    "go_gin_gorm/libraries"
    "fmt"
)

type User struct {
    Id int
    UserId   string
    Password string
}

type JsonRequest struct {
    UserId  string `json:"user_id"`
    Password  string    `json:"password"`
}

func main() {
    engine:= gin.Default()

    dsn := "root@tcp(mysql:3306)/first?charset=utf8mb4&parseTime=True&loc=Local"
    db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
    if err != nil {
        fmt.Println("DB接続失敗");
        return
    }

    // Sign up用ルーティング
    engine.POST("/signup", func(c *gin.Context) {
        var json JsonRequest
        if err := c.ShouldBindJSON(&json); err != nil {
            c.JSON(http.StatusBadRequest, gin.H{
                "error" : err.Error(),
            })
            return
        }
        userId := json.UserId
        pw := json.Password

        // 同一ユーザIDの検証
        user := User{}
        db.Where("user_id = ?", userId).First(&user)
        if user.Id != 0 {
            c.JSON(http.StatusBadRequest, gin.H{
                "message" : "そのUserIdは既に登録されています。",
            })
            return
        }

        // パスワードの暗号化
        encryptPw, err := crypto.PasswordEncrypt(pw)
        if err != nil {
            c.JSON(http.StatusInternalServerError, gin.H{
                "message" : "パスワードの暗号化でエラーが発生しました。",
            })
            return
        }
        
        // DBへの登録
        user = User{UserId: userId, Password: encryptPw}
        db.Create(&user)
        
        c.JSON(http.StatusOK, gin.H{
            "message" : "アカウント登録完了",
        })
    })
    engine.Run(":8080")
}

main.go の内容を少し見ていきましょう

type JsonRequest struct {
    UserId  string `json:"user_id"`
    Password  string    `json:"password"`
}

リクエストボディにはjsonを採用したため、バインドするための構造体 JsonRequest を用意します。

var json JsonRequest
if err := c.ShouldBindJSON(&json); err != nil {
    c.JSON(http.StatusBadRequest, gin.H{
        "error" : err.Error(),
    })
    return
}
userId := json.UserId
pw := json.Password

ShouldBindJSON を用いて構造体にリクエストをバインドします。
取り出す際には 構造体の変数名.構造体が持つキー名 で行います。

encryptPw, err := crypto.PasswordEncrypt(pw)
if err != nil {
    c.JSON(http.StatusInternalServerError, gin.H{
        "message" : "パスワードの暗号化でエラーが発生しました。",
    })
    return
}

crypto.go に実装した PasswordEncrypt 関数を呼び出しています。
crypto.go を利用するためには、importに "go_gin_gorm/libraries" を追加します。

サインイン用の処理

次はサインイン機能を実装します。

main.go を編集します。 // Sign in用ルーティング の処理を追加しましょう。

main.go

...

func main() {

...

    // Sign up用ルーティング
    engine.POST("/signup", func(c *gin.Context) {

...

    })

    // Sign in用ルーティング
    engine.POST("/signin", func(c *gin.Context) {
        var json JsonRequest
        if err := c.ShouldBindJSON(&json); err != nil {
            c.JSON(http.StatusBadRequest, gin.H{
                "error" : err.Error(),
            })
            return
        }
        userId := json.UserId
        pw := json.Password

        user := User{}
        db.Where("user_id = ?", userId).First(&user)
        if user.Id == 0 {
            c.JSON(http.StatusUnauthorized, gin.H{
                "message" : "ユーザーが存在しません。",
            })
            return
        }

        err := crypto.CompareHashAndPassword(user.Password, pw)
        if err != nil {
            c.JSON(http.StatusUnauthorized, gin.H{
                "message" : "パスワードが一致しません。",
            })
            return
        }
        
        c.JSON(http.StatusOK, gin.H{
            "message" : "ログイン成功",
        })
    })

...

これも中身を見てみきましょう。基本的にはサインアップ用の処理と同じで、jsonから値を取得してデータベースと照合しています。
サインアップと異なるのは、暗号化して登録したパスワードと入力値を比較する必要があるということです。

err := crypto.CompareHashAndPassword(user.Password, pw)
if err != nil {
    c.JSON(http.StatusUnauthorized, gin.H{
        "message" : "パスワードが一致しません。",
    })
    return
}

crypto.go に実装した CompareHashAndPassword 関数を呼び出しています。
エラーがなければ照合成功ということになります。

動作確認

作成が完了したら実際に動作を確認します。
Docker開発環境が起動していることを確認してから、リクエストを送ってみましょう。

サインアップ

signup
curl -X POST "http://localhost:8080/signup" -H "Content-Type: application/json" -d '{"user_id": "login_user", "password": "Password_"}'
result
{
    "message": "アカウント登録完了"
}

サインイン

signin
curl -X POST "http://localhost:8080/signin" -H "Content-Type: application/json" -d '{"user_id": "login_user", "password": "Password_"}'
result
{
    "message": "ログイン成功"
}

まとめ

いかがだったでしょうか?
今回はサインアップとサインインだけですが、ログイン状態の維持機能やログアウト機能を追加してみるのも良いかもしれません。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0