はじめに
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
を作成しましょう。
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))
}
アカウント作成用の処理
まずはログイン機能のひとつ、サインアップ機能を作ります。
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用ルーティング
の処理を追加しましょう。
...
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開発環境が起動していることを確認してから、リクエストを送ってみましょう。
サインアップ
curl -X POST "http://localhost:8080/signup" -H "Content-Type: application/json" -d '{"user_id": "login_user", "password": "Password_"}'
{
"message": "アカウント登録完了"
}
サインイン
curl -X POST "http://localhost:8080/signin" -H "Content-Type: application/json" -d '{"user_id": "login_user", "password": "Password_"}'
{
"message": "ログイン成功"
}
まとめ
いかがだったでしょうか?
今回はサインアップとサインインだけですが、ログイン状態の維持機能やログアウト機能を追加してみるのも良いかもしれません。