Table of Contents
期待するもの
新規登録のAPIを作成したので、次はログイン機能を作成したい。
ログインは、メールアドレスとパスワードを送り一致したら、tokenを取得する。
で、tokenを使って認証付きのAPIを叩けるようになりたい。
tokenにはjwtを使用する。
jwtってなに?
これはどこかで調べてまとめる(気が向いた時)。
JWT認証を作成する
パッケージのインストール
github.com/dgrijalva/jwt-goパッケージの情報が多かったので、それに従うことにする。長い物には巻かれるべきで間違いない。
go get github.com/dgrijalva/jwt-goパッケージをインストールする。
Tokenを生成する
まずjwt.goというファイルを作成する。
今回はauthフォルダの中に作成した。
それでは書いていく。
まずは、jwtを生成するためのシークレットキーを作成する。
var jwtKey = []byte("your_secret_key")ここでは任意の文字列(今回は”your_secret_key”)をバイト列に変換し、jwtKeyに代入している。
バイト列というのがピンとこないので、
fmt.Printlnで書き出しみると、[121 111 117 114 95 115 101 99 114 101 116 95 107 101 121]となった。なんとなく理解した。
このようにして文字列をバイト列に変換することは、多くの場面でデータの操作や処理に利用されます。JWTの署名などのセキュリティ関連の操作では、シークレットキーをバイト列として扱うことが一般的です。
とのことでした。
次に、JWTのクレームを表す構造体であるClaimsを定義する。
これは任意のJSONデータっていう意味っぽい。
type Claims struct { Name string `json:"name"` jwt.StandardClaims}今回はnameを含めることにする。
と思ったけど、ユニークで必要がありそうなので、ユーザーIDにした。
下記のように変更する。
type Claims struct { UserID uint `json:"user_id"` jwt.StandardClaims}次に、Tokenを作成する関数を作成する。
コードの詳細はコメントアウトに書いた。
func generateToken(userId uint) (string, error) {
// トークンの有効期限を設定(この場合は15分) expirationTime := time.Now().Add(15 * time.Minute)
// トークンのClaimsを構築 claims := &Claims{ UserID: userId, // ユーザーIDをトークンに含める StandardClaims: jwt.StandardClaims{ ExpiresAt: expirationTime.Unix(), // トークンの有効期限を設定 }, }
// 新しいJWTトークンを作成 token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
// トークンを署名して文字列に変換 tokenString, err := token.SignedString(jwtKey) if err != nil { return "", err }
// : JWTトークンを文字列として表現したもの&エラーが発生しなかったことを示すための値を返却する return tokenString, nil}これでTokenを作成することができた。
ログイン時にこのTokenを返却することにする。
Tokenを認証する
次に認証を行う関数を作成する。
func Authenticate(next http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { // Authorizationヘッダーからトークン文字列を取得します。 tokenString := r.Header.Get("Authorization")
// トークンが存在しない場合は、Unauthorizedエラーを返して処理を中断します。 if tokenString == "" { http.Error(w, "Unauthorized", http.StatusUnauthorized) return }
// トークン文字列から"Bearer "を削除します。 tokenString = strings.Replace(tokenString, "Bearer ", "", 1)
// JWTトークンをパースし、クレーム(Claims)を含むトークンオブジェクトを取得します。 token, err := jwt.ParseWithClaims(tokenString, &Claims{}, func(token *jwt.Token) (interface{}, error) { return jwtKey, nil })
// トークンのパースに失敗した場合は、Unauthorizedエラーを返して処理を中断します。 if err != nil { http.Error(w, "Unauthorized", http.StatusUnauthorized) return }
// トークンのクレームを取得し、有効であるかを検証します。 claims, ok := token.Claims.(*Claims) if !ok || !token.Valid { http.Error(w, "Unauthorized", http.StatusUnauthorized) return }
// 認証されたユーザーの情報をログに出力します。 fmt.Printf("Authenticated user: %s\n", strconv.Itoa(int(claims.UserID)))
// 次のハンドラ関数を呼び出します。 next(w, r) }}ちなみにこの関数の関数名が大文字始まりなのは下記の通り。
Go言語では、関数や変数が大文字で始まる場合には他のパッケージから参照可能(エクスポート可能)となりますが、小文字で始まる場合には同じパッケージ内からのみアクセス可能となります。
で、このコントローラーを下記のように噛ませる。
http.HandleFunc("/users", auth.Authenticate(FetchUsers))これで認証が必要になった。
実際に叩くときは
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjo4LCJleHAiOjE3MDMwODUxNzR9.bOJ6jYFLr3BneqtEhQdFb9b2y3lAubm3MpqXbzVQzQYこんな感じでヘッダーを持たせる必要がある。
これでJWT認証ができるようになった。