Elsaの技術日記(徒然なるままに)

主に自分で作ったアプリとかの報告・日記を記載

MENU

Go言語でログインサーバ構築

前回Go言語でhttpsサーバを構築してみました。
elsammit-beginnerblg.hatenablog.com

サイトによってはログイン権限が必要になることもありますよね??
そこで今回は、httpsでのログインサーバを構築してみたいと思います!!



■動作環境

前回のhttpsサーバの環境と同様に、
・OS:Ubuntu20.04
・言語:go言語(1.13.8)
・ツール:gin
を用いていきます。

■フロントエンド実装

まずはhtmlでフロントエンドの実装をしていきます。
コードはこちら。

<!DOCTYPE html>
<html lang="ja">
    <head>
        <meta cahrset="UTF-8">
        <title>Gin Application</title>
    </head>
    <body>
        <form method="post" action="/login">
            <h1>Login</h1>
            <input type="text" name="userId"><br/>
            <input type="password" name="password">
            <input type="submit" value="ログイン">
            <script type="text/javascript">
            console.log({{.result}})
            if({{.result}} != 0){
                alert({{.text}})
            }
            </script>
        </form>
    <body>
</html>

こちらをhtmlコードは下記のような画面が構成されます。
f:id:Elsammit:20210320190036p:plain

やっていることは単純で、
ユーザ名、パスワードを入力する欄を用意し、
ログインボタンを押下すると"/login"パスで入力情報がサーバサイドに送信。
といった構成になっております。

responseはjson形式で得られ、resultキーにログイン結果が格納されるので結果を元にalertを出すコードにしました。

■サーバサイド DB作成

次にサーバサイド。
まずはユーザ名やパスワードを格納しておくためのDBを用意します。
今回はログインのみかつユーザも1名だけなのでファイルで管理してみても良かったのですが、、、
汎用性を考えてDBでの構築を行いました。

DBの作成やデータ登録はこちらにまとめましたのでこちらをご確認ください。
elsammit-beginnerblg.hatenablog.com

今回は、
テーブル名を、
・UserInfo
とし、カラム名を、
・ユーザ名
・パスワード
とし、登録内容は、
・elsammit
・test
としました。

■サーバサイド 入力データからログイン結果判定

では今回の本題である、Go言語でログインサーバを構築していきたいと思います。
百聞は一見に如かず!!
まずはコードを載せます。

package main

import (
	"database/sql"
	"fmt"
	"log"
	"net/http"

	"github.com/gin-gonic/gin"
	_ "github.com/mattn/go-sqlite3"
)

type state struct {
	username string
	password string
}

func DB_Read(Name string, Password string) int {
	ret := -1
	DbConnection, err1 := sql.Open("sqlite3", "./db.sql")
	if err1 != nil {
		log.Fatal(err1)
	}

	cmd := "SELECT * FROM UserInfo"
	rows, _ := DbConnection.Query(cmd)
	defer rows.Close()

	for rows.Next() {
		var s state
		err := rows.Scan(&s.username, &s.password)
		if err != nil {
			log.Println(err)
		}
		fmt.Println(s.username, s.password)
		if s.username == Name {
			if s.password == Password {
				ret = 0
			} else {
				ret = 1
			}
		} else {
			ret = 2
		}
	}
	return ret
}

func Index(ctx *gin.Context) {
	fmt.Println("Index")
	ctx.HTML(http.StatusOK, "signin.html", gin.H{
		"text":   "hello",
		"result": 0,
	})
}

func Login(ctx *gin.Context) {
	fmt.Println("Login")

	userid, _ := ctx.GetPostForm("userId")
	password, _ := ctx.GetPostForm("password")
	buf := DB_Read(userid, password)
	sendMsg := "None"
	html := "signin.html"
	result := -1
	if buf == 0 {
		sendMsg = "OK"
		html = "LoginOK.html"
		result = 0
	} else if buf == 1 {
		sendMsg = "Password not collect"
	} else if buf == 2 {
		sendMsg = "User Name not resistered"
	}
	fmt.Println(userid)
	fmt.Println(password)
	ctx.HTML(http.StatusOK, html, gin.H{
		"text":   sendMsg,
		"result": result,
	})
}

func main() {
	r := gin.Default()
	r.LoadHTMLGlob("html/*")
	r.Static("/image", "./image")
	r.Static("/Certificate", "./Certificate")
	r.GET("/", Index)       // ログイン画面(GET処理)
	r.POST("/login", Login) // ログイン画面(POST処理)
	r.RunTLS(":8080", "./Certificate/server.pem", "./Certificate/server.key")
}

ちょっとコードが長いですね😅
長くなっている理由はDBからのデータ読み出しの処理が長くなってしまったためです。
長くなっているDBについてまずは解説していきます。
DBの読み出し部分のコードはこちら。

func DB_Read(Name string, Password string) int {
	ret := -1
	DbConnection, err1 := sql.Open("sqlite3", "./db.sql")
	if err1 != nil {
		log.Fatal(err1)
	}

	cmd := "SELECT * FROM UserInfo"
	rows, _ := DbConnection.Query(cmd)
	defer rows.Close()

	for rows.Next() {
		var s state
		err := rows.Scan(&s.username, &s.password)
		if err != nil {
			log.Println(err)
		}
		fmt.Println(s.username, s.password)
		if s.username == Name {
			if s.password == Password {
				ret = 0
			} else {
				ret = 1
			}
		} else {
			ret = 2
		}
	}
	return ret
}

引数は入力されたユーザ名、パスワードです。

DBからのデータを

	cmd := "SELECT * FROM UserInfo"
	rows, _ := DbConnection.Query(cmd)
	defer rows.Close()

で読み出し、rowsに読み出しデータを格納。

	for rows.Next() {
		var s state
		err := rows.Scan(&s.username, &s.password)
		if err != nil {
			log.Println(err)
		}
		fmt.Println(s.username, s.password)
		if s.username == Name {
			if s.password == Password {
				return  0
			} else {
				ret = 1
			}
		} else {
			ret = 2
		}
	}

にてrows内のデータを読み出していきます。

var s state

ですが、あらかじめ構造体としてこちらを定義しております。

type state struct {
	username string
	password string
}

こちらの構造体にデータを格納し、入力されたデータとDBで読み出したデータの比較を行っております。

そして、、、
こちらの関数にてフロントエンドに送信するメッセージやログイン結果をresponseしています。

func Login(ctx *gin.Context) {
	fmt.Println("Login")

	userid, _ := ctx.GetPostForm("userId")
	password, _ := ctx.GetPostForm("password")
	buf := DB_Read(userid, password)
	sendMsg := "None"
	html := "signin.html"
	result := -1
	if buf == 0 {
		sendMsg = "OK"
		html = "LoginOK.html"
		result = 0
	} else if buf == 1 {
		sendMsg = "Password not collect"
	} else if buf == 2 {
		sendMsg = "User Name not resistered"
	}
	fmt.Println(userid)
	fmt.Println(password)
	ctx.HTML(http.StatusOK, html, gin.H{
		"text":   sendMsg,
		"result": result,
	})
}

■最後に

今回は登録人数が1名だったこともあり、判定式が1名用で作成してしまっております😥
登録ユーザが複数人いる場合には今回のコードだとダメな部分あるので変更しなきゃな🤔

後、
サーバーサイド側のパスワード管理やSQLインジェクション対応などのセキュリティに関しもしっかりしとかなきゃですね!!
まだまだセキュリティ関係は勉強が足りないのが悩みどころ😫
重要なところなのでもっと勉強していきます!!