20211125のGoに関する記事は2件です。

Golang + EchoでJWTを使ってみる

はじめに JWT(JSON Web Token)をSPAに使用するべきか否かの議論はありますが、今回はそれは度外視にGolangのEchoでJWTを使用するケースを想定し、実装をしてみようと思います。今回は説明を省きますが、当方フロントエンドにNext.js、AuthenticateにFirebaseを一部使用しています。またJWTの基本的な説明は公式サイトや他参考記事をご覧ください。 ゴール 任意のペイロードを設定できる GoアプリケーションでJWTが発行できる リクエスト内にJWTトークンを認可し適切な処理ができる JWTの送信者の本人認証ができる 実行環境 Mac 11.5.2 Docker Desktop 4.0.0 Golang Echo Gorm イメージ図 手順一覧 Echo環境構築 ルーティング設定 ログインメソッドとペイロードの設定とJWTの発行 本人確認メソッド PostmanでのJWTの扱い方 JavaScript(Typescript)でfetchメソッドでのJWTの取り扱い方 Echo環境構築 基本的なEchoの環境構築、実装は過去記事または公式をご参考ください。 ルーティング設定 EchoのJWT環境構築は公式を参考に構築していきます。 エンドポイント以降、"/restricted"をrとしてグループ化する。 上記でグループ化したrをEchoのmiddleware.JWTを経由させる。 こうすることで/restricted/以降はJWT認証を行う。 main.go package main import ( "net/http" "github.com/labstack/echo" "github.com/labstack/echo/middleware" ) func main() { router := newRouter() router.Logger.Fatal(router.Start(":8080")) } func newRouter() *echo.Echo { e := echo.New() e.Use(middleware.CORS()) e.GET("/", func(c echo.Context) error { return c.String(http.StatusOK, "Hello, World!") }) r := e.Group("/restricted") // グループ化されたrをmiddlwwareを経由するように設定 // ルーティングでeを選択するとJWT認証をせず、rを選択するとJWT認証を行うことができる。 r.Use(middleware.JWT([]byte("secret"))) r.POST("/login", middle_ware.Login()) r.POST("/user", controllers.UserCreate()) r.GET("/user/:id", controllers.UserShow()) r.PUT("/user/:id", controllers.UserEdit()) r.DELETE("/user/:id", controllers.UserDelete()) return e } ログインメソッドとJWTの発行 公式はHTMLのformタグから直接値を取得する方法で実装してますが、今回はNext.jsから送られてくるJSONを使用します。 type LoginUser struct { Id string `json:"id"` Email string `json:"email"` Uid string `json:"uid"` } type AuthUser struct { Id string `json:"id"` Email string `json:"email"` UidDigest string `json:"uid_digest"` } func Login() echo.HandlerFunc { return func(c echo.Context) error { db := dbconnect.Connect() defer db.Close() loginUser := new(LoginUser) if err := c.Bind(loginUser); err != nil { return err } email := loginUser.Email uid := loginUser.Uid user := AuthUser{} result := db.Table("users").Find(&user, "email = ?", email) if result.RecordNotFound() { fmt.Println("IDかfirebase_idが間違っています") return echo.ErrUnauthorized } else { hashedUid := user.UidDigest err := bcrypt.CompareHashAndPassword([]byte(hashedUid), []byte(uid)) if err != nil { fmt.Println("IDかfirebase_idが間違っています") return echo.ErrUnauthorized } else { id := user.Id token := jwt.New(jwt.SigningMethodHS256) claims := token.Claims.(jwt.MapClaims) claims["uid"] = uid claims["id"] = id claims["admin"] = false claims["iat"] = time.Now().Unix() claims["exp"] = time.Now().Add(time.Hour * 24).Unix() t, err := token.SignedString([]byte("secret")) if err != nil { return err } return c.JSON(http.StatusOK, map[string]string{ "token": t, }) } } } } 少し回りくどい実装をしているため解説いたします。 LoginUserを初期化し、JSONで送られてくるデータをバインドします。 バインドしたデータからメールアドレスとfirebase UIDを変数に格納します。 loginUser := new(LoginUser) if err := c.Bind(loginUser); err != nil { return err } email := loginUser.Email uid := loginUser.Uid 取得したメースアドレスを基にusersテーブルからユーザーを取得します。 result := db.Table("users").Find(&user, "email = ?", email) 上記でユーザーが取得できた場合、ユーザー作成時にFIrebaes Uidをハッシュ化したUidDigestを基にログイン時に取得したFirebase UIDを比較します。 *ハッシュ化にはbcryptを使用しています。 hashedUid := user.UidDigest err := bcrypt.CompareHashAndPassword([]byte(hashedUid), []byte(uid)) if err != nil { fmt.Println("IDかfirebase_idが間違っています") return echo.ErrUnauthorized ペーロードの設定とTokenの発行 ここは各プロジェクトで必要な値を入れればOKです。 JWTはJavaScriptなどで容易にデコード可能なため、含める情報には機密情報は含めないことを強くお勧めいたします。 クレーム名 説明 uid Firebase UID id usersテーブルのID admin ユーザーを管理者かどうかを判断する true or false exp JWT 有効期限 iat isuued at の略。JWT 発行時刻 id := adminUser.Id token := jwt.New(jwt.SigningMethodHS256) claims := token.Claims.(jwt.MapClaims) claims["uid"] = uid claims["id"] = id claims["admin"] = true claims["iat"] = time.Now().Unix() claims["exp"] = time.Now().Add(time.Hour * 24).Unix() t, err := token.SignedString([]byte("secret")) if err != nil { return err } return c.JSON(http.StatusOK, map[string]string{ "token": t, }) ログインのメソッドを送ると以下のような結果が返ってきます。 { "email": "golang.jwt@test.com", "uid": "CS1z7P70T4ZYNkl0mg37Rujv6Iu1" } // 成功時 { "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhZG1pbiI6ZmFsc2UsImV4cCI6MTYzNzgyMzczNiwiaWF0IjoxNjM3NzM3MzM2LCJpZCI6InVzZXIiLCJ1aWQiOiJDUzF6N1A3MFQ0WllOa2wwbWczN1J1anY2SXUxIn0.bxHJChPl8Oi6Mfpub02k5POv0n5F8P4Z8kUilEeMN_E" } // 失敗時 { "message": "Unauthorized" } 本人確認メソッド あくまでも一例ですが、パラメータとして送られてくるUserのIDと一緒に送られてくるJWTに含まれるidが一致するかをチェックするメソッドを紹介します。 rを含めるルーティング(EchoのMiddleware)はあくまでもJWTの有効性を確認するだけの処理なので本人確認までは担保しません。ですのでデータの編集や削除などの本人しか行わないような処理には本人確認処理を行うことをお勧めします。 // 本人かどうかでTrue/Falseを返す。 func Auth(c echo.Context, id string) bool { currentUser := c.Get("user").(*jwt.Token) claims := currentUser.Claims.(jwt.MapClaims) userId := claims["id"].(string) db := dbconnect.Connect() defer db.Close() user := User{} // 渡されたuser_idでUserを取得する。 result := db.Table("users").Find(&user, "id = ?", id) if result.RecordNotFound() { fmt.Println("レコードが見つかりません") return false } else { // 取得したUserのIDとJWTのIDが一致するかチェックする。 if user.Id != userId { fmt.Println("ユーザーが一致しません") return false } else { return true } } } func UserEdit() echo.HandlerFunc { return func(c echo.Context) error { db := dbconnect.Connect() defer db.Close() user_id := c.Param("id") new_user := new(User) user := User{} // Authメソッドを呼び出し、パラメータのuser_idを渡す。 judgement := Auth(c, user_id) if err := c.Bind(new_user); err != nil { return err } if !judgement { return c.JSON(http.StatusNotFound, nil) } else { result := db.Table("users").First(&user, "id = ?", user_id).Update(&new_user) if result.RecordNotFound() { fmt.Println("レコードが見つかりません") return c.JSON(http.StatusNotFound, nil) } else { return c.JSON(http.StatusOK, user) } } } } PostmanでのJWTの扱い方 Authorizationをクリック Bearer Tokenを選択 ログインメソッドで取得したJWTを入力する あとはURLやJSONなどを含めてリクエストするだけです! JavaScript(Typescript)でfetchメソッドでのJWTの取り扱い方 Typescriptですみません。型を消してくれれはOKです。 const editAdminUser = async ( data: Level, id: string, self_id: string, jwt: string ) => { try { await fetch( // 環境変数を呼び出してますが、エンドポイントが含まれます process.env.NEXT_PUBLIC_API_URL_RESTRICTED + "user/" + "/" + id, { method: "PUT", headers: { "Content-Type": "application/json", Authorization: "Bearer" + ":" + jwt, }, body: JSON.stringify(data), } ); } catch (err) { return err; } }; headerの中に以下を含めます。(jwtには実際のトークンを代入してください) Authorization: "Bearer" + ":" + jwt よかったら参考にしてみてください。 それでは? 参考記事
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

いろいろな言語でAPIコールするよ!

導入 webAPIの叩き方を各言語でまとめました。 後で見返す用です。 2021年11月23日時点:Postリクエスト送信 json リクエストのサンプルパラメータ sample_endpoint = "http://localhost:8888/sample" sample_parameter = { "title": "トマトスープ", "making_time": "15分", "serves": "5人", "ingredients": "玉ねぎ, トマト, スパイス, 水", "cost": "450" } Curl curl --location --request POST 'http://localhost:8888/recipes' \ --header 'Content-Type: application/json' \ --data-raw '{ "title": "トマトスープ", "making_time": "15分", "serves": "5人", "ingredients": "玉ねぎ, トマト, スパイス, 水", "cost": "450" }' Go package main import ( "fmt" "strings" "net/http" "io/ioutil" ) func main() { url := "http://localhost:8888/recipes" method := "POST" payload := strings.NewReader(`{ "title": "トマトスープ", "making_time": "15分", "serves": "5人", "ingredients": "玉ねぎ, トマト, スパイス, 水", "cost": "450" }`) client := &http.Client { } req, err := http.NewRequest(method, url, payload) if err != nil { fmt.Println(err) return } req.Header.Add("Content-Type", "application/json") res, err := client.Do(req) if err != nil { fmt.Println(err) return } defer res.Body.Close() body, err := ioutil.ReadAll(res.Body) if err != nil { fmt.Println(err) return } fmt.Println(string(body)) } php <?php require_once 'HTTP/Request2.php'; $request = new HTTP_Request2(); $request->setUrl('http://localhost:8888/recipes'); $request->setMethod(HTTP_Request2::METHOD_POST); $request->setConfig(array( 'follow_redirects' => TRUE )); $request->setHeader(array( 'Content-Type' => 'application/json' )); $request->setBody('{\n "title": "トマトスープ",\n "making_time": "15分",\n "serves": "5人",\n "ingredients": "玉ねぎ, トマト, スパイス, 水",\n "cost": "450"\n}'); try { $response = $request->send(); if ($response->getStatus() == 200) { echo $response->getBody(); } else { echo 'Unexpected HTTP status: ' . $response->getStatus() . ' ' . $response->getReasonPhrase(); } } catch(HTTP_Request2_Exception $e) { echo 'Error: ' . $e->getMessage(); } python import requests import json url = "http://localhost:8888/recipes" payload = json.dumps({ "title": "トマトスープ", "making_time": "15分", "serves": "5人", "ingredients": "玉ねぎ, トマト, スパイス, 水", "cost": "450" }) headers = { 'Content-Type': 'application/json' } response = requests.request("POST", url, headers=headers, data=payload) print(response.text) これから getパラメータの記述方法なども追記していく
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む