GAE/Go で公開鍵暗号を使う JWT 認証サーバーを作った
リポジトリ
特徴
- Google App Engine 上で動作する
- 名前とパスワードでユーザー登録ができる
- 名前とパスワードでユーザー認証を行い JWT トークンを発行する
- JWT トークンで認可されたユーザーにコンテンツを提供できる
GAE/Go 特有の実装ポイント
ローカルファイルを持てない
- GAE ではローカルのファイルシステムを利用できない
- ファイルシステムとしては Cloud Storage などがある
- 今回は公開鍵ファイルと秘密鍵ファイルは
github.com/jteeuwen/go-bindata
を使って go のファイルに変換して利用した
デプロイ用のディレクトリに実行ファイルと設定ファイルを用意する
- デプロイ用のディレクトリはプロジェクトルート直下の
app
-
app/app.go
が実行ファイルで init
が GAE から実行される
-
app/app.yaml
は最低限の設定ファイル
データベースには Datastore を利用する
- Google Cloud Platform ではデータベースとして NoSQL の Datastore や SQL が使える Cloud SQL などが利用できる
- 今回は無料枠があり簡単なクエリくらいなら実行できる Datastore を使う
簡単な解説
GAE 用のファイル構成
- GOPATH の切り替えなどは行わず通常の GOPATH 以下にプロジェクトを作成する
- app 以下に GAE 用のファイルを配置し、自プロジェクトのフルパスでファイルをインポートしてハンドラーを登録する
app/app.go
import (
"github.com/nirasan/gae-jwt/handler"
"net/http"
)
func init() {
http.Handle("/", handler.NewHandler())
}
ユーザーの登録処理
- handler.RegistrationHandler で実装
- Datastore へユーザー情報を登録する
- パスワードは bcrypt でハッシュ化して保存する
handler.go
// ユーザー情報の登録準備
ctx := appengine.NewContext(r)
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost)
if err != nil {
panic(err.Error())
}
ua := UserAuthentication{Username: req.Username, Password: string(hashedPassword)}
// Datastore へユーザー情報を登録
err = datastore.RunInTransaction(ctx, func(ctx context.Context) error {
key := datastore.NewKey(ctx, "UserAuthentication", req.Username, 0, nil)
var userAuthentication UserAuthentication
if err := datastore.Get(ctx, key, &userAuthentication); err != datastore.ErrNoSuchEntity {
return errors.New("user already exist")
}
if _, err := datastore.Put(ctx, key, &ua); err == nil {
return nil
} else {
return err
}
}, nil)
ユーザーの認証処理
- handler.AuthenticationHandler で実装
ユーザーの参照
- Datastore を検索してユーザーが存在するか確認する
- パスワードは bcrypt で検証する
handler.go
// ユーザーが存在するかどうか確認
ctx := appengine.NewContext(r)
key := datastore.NewKey(ctx, "UserAuthentication", req.Username, 0, nil)
var userAuthentication UserAuthentication
if err := datastore.Get(ctx, key, &userAuthentication); err != nil {
EncodeJson(w, AuthenticationHandlerResponse{Success: false})
return
}
// パスワードの検証
if err := bcrypt.CompareHashAndPassword([]byte(userAuthentication.Password), []byte(req.Password)); err != nil {
EncodeJson(w, AuthenticationHandlerResponse{Success: false})
return
}
秘密鍵の読み込み
- go-bindata で固めたファイルから秘密鍵を読み込む
handler.go
pem, e := bindata.Asset("assets/ec256-key-pri.pem")
JWT トークンの作成と署名
handler.go
// 署名アルゴリズムの作成
method := jwt.GetSigningMethod("ES256")
// トークンの作成
token := jwt.NewWithClaims(method, jwt.MapClaims{
"sub": req.Username,
"exp": time.Now().Add(time.Hour * 1).Unix(),
})
// 秘密鍵のパース
privateKey, e := jwt.ParseECPrivateKeyFromPEM(pem)
if e != nil {
panic(e.Error())
}
// トークンの署名
signedToken, e := token.SignedString(privateKey)
if e != nil {
panic(e.Error())
}
認可したユーザーのみ閲覧可能なコンテンツを提供
- handler.AuthorizedHelloWorldHandler で実装
- handler.Authorization でリクエストからトークンを取得して検証する
handler.go
token, e := Authorization(r)
if e != nil {
EncodeJson(w, HelloWorldHandlerResponse{Success: false})
}
インストール
-
App Engine SDK をインストールする
- 以下のパッケージをインストールする
go get -u github.com/dgrijalva/jwt-go
go get -u github.com/gorilla/mux
go get -u google.golang.org/appengine/datastore
go get -u google.golang.org/appengine
go get -u google.golang.org/appengine/log
go get -u github.com/jteeuwen/go-bindata/...
開発環境を起動する
- プロジェクトのルートで以下のコマンドを実行する
goapp serve app
デプロイ
Google Cloud Platform でプロジェクトを作成
以下のコマンドをプロジェクトのルート実行してデプロイ
appcfg.py -A <PROJECT_ID> -V v1 update app/
公開鍵と秘密鍵を準備する
openssl で鍵の作成
mkdir assets
cd assets
openssl ecparam -genkey -name prime256v1 -noout -out ec256-key-pair.pem
openssl ec -in ec256-key-pair.pem -outform PEM -pubout -out ec256-key-pub.pem
openssl ec -in ec256-key-pair.pem -outform PEM -out ec256-key-pri.pem
go-bindata で鍵を go の実行ファイルに変換
go-bindata -o bindata/bindata.go assets
鍵の実行ファイルの編集
- パッケージ名を 'main' から 'bindata' に変更
cURL でリクエストを実行する
ユーザー登録
curl -H "Accept: application/json" -H "Content-type: application/json" -X POST -d '{"username":"USERNAME","password":"PASSWORD"}' http://<PROJECT_ID>.appspot.com/registration
ユーザー認証
curl -H "Accept: application/json" -H "Content-type: application/json" -X POST -d '{"username":"USERNAME","password":"PASSWORD"}' http://<PROJECT_ID>.appspot.com/authentication
誰でもアクセスできるコンテンツの表示
curl -H "Accept: application/json" -H "Content-type: application/json" http://<PROJECT_ID>.appspot.com/hello
認可されたユーザーだけアクセスできるコンテンツの表示
-
<TOKEN>
にはユーザー認証のレスポンスの Token
要素の値を挿入する
curl -H "Accept: application/json" -H "Content-type: application/json" -H "Authorization: Bearer <TOKEN>" http://<PROJECT_ID>.appspot.com/authorized_hello
- Google App Engine 上で動作する
- 名前とパスワードでユーザー登録ができる
- 名前とパスワードでユーザー認証を行い JWT トークンを発行する
- JWT トークンで認可されたユーザーにコンテンツを提供できる
GAE/Go 特有の実装ポイント
ローカルファイルを持てない
- GAE ではローカルのファイルシステムを利用できない
- ファイルシステムとしては Cloud Storage などがある
- 今回は公開鍵ファイルと秘密鍵ファイルは
github.com/jteeuwen/go-bindata
を使って go のファイルに変換して利用した
デプロイ用のディレクトリに実行ファイルと設定ファイルを用意する
- デプロイ用のディレクトリはプロジェクトルート直下の
app
-
app/app.go
が実行ファイルで init
が GAE から実行される
-
app/app.yaml
は最低限の設定ファイル
データベースには Datastore を利用する
- Google Cloud Platform ではデータベースとして NoSQL の Datastore や SQL が使える Cloud SQL などが利用できる
- 今回は無料枠があり簡単なクエリくらいなら実行できる Datastore を使う
簡単な解説
GAE 用のファイル構成
- GOPATH の切り替えなどは行わず通常の GOPATH 以下にプロジェクトを作成する
- app 以下に GAE 用のファイルを配置し、自プロジェクトのフルパスでファイルをインポートしてハンドラーを登録する
app/app.go
import (
"github.com/nirasan/gae-jwt/handler"
"net/http"
)
func init() {
http.Handle("/", handler.NewHandler())
}
ユーザーの登録処理
- handler.RegistrationHandler で実装
- Datastore へユーザー情報を登録する
- パスワードは bcrypt でハッシュ化して保存する
handler.go
// ユーザー情報の登録準備
ctx := appengine.NewContext(r)
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost)
if err != nil {
panic(err.Error())
}
ua := UserAuthentication{Username: req.Username, Password: string(hashedPassword)}
// Datastore へユーザー情報を登録
err = datastore.RunInTransaction(ctx, func(ctx context.Context) error {
key := datastore.NewKey(ctx, "UserAuthentication", req.Username, 0, nil)
var userAuthentication UserAuthentication
if err := datastore.Get(ctx, key, &userAuthentication); err != datastore.ErrNoSuchEntity {
return errors.New("user already exist")
}
if _, err := datastore.Put(ctx, key, &ua); err == nil {
return nil
} else {
return err
}
}, nil)
ユーザーの認証処理
- handler.AuthenticationHandler で実装
ユーザーの参照
- Datastore を検索してユーザーが存在するか確認する
- パスワードは bcrypt で検証する
handler.go
// ユーザーが存在するかどうか確認
ctx := appengine.NewContext(r)
key := datastore.NewKey(ctx, "UserAuthentication", req.Username, 0, nil)
var userAuthentication UserAuthentication
if err := datastore.Get(ctx, key, &userAuthentication); err != nil {
EncodeJson(w, AuthenticationHandlerResponse{Success: false})
return
}
// パスワードの検証
if err := bcrypt.CompareHashAndPassword([]byte(userAuthentication.Password), []byte(req.Password)); err != nil {
EncodeJson(w, AuthenticationHandlerResponse{Success: false})
return
}
秘密鍵の読み込み
- go-bindata で固めたファイルから秘密鍵を読み込む
handler.go
pem, e := bindata.Asset("assets/ec256-key-pri.pem")
JWT トークンの作成と署名
handler.go
// 署名アルゴリズムの作成
method := jwt.GetSigningMethod("ES256")
// トークンの作成
token := jwt.NewWithClaims(method, jwt.MapClaims{
"sub": req.Username,
"exp": time.Now().Add(time.Hour * 1).Unix(),
})
// 秘密鍵のパース
privateKey, e := jwt.ParseECPrivateKeyFromPEM(pem)
if e != nil {
panic(e.Error())
}
// トークンの署名
signedToken, e := token.SignedString(privateKey)
if e != nil {
panic(e.Error())
}
認可したユーザーのみ閲覧可能なコンテンツを提供
- handler.AuthorizedHelloWorldHandler で実装
- handler.Authorization でリクエストからトークンを取得して検証する
handler.go
token, e := Authorization(r)
if e != nil {
EncodeJson(w, HelloWorldHandlerResponse{Success: false})
}
インストール
-
App Engine SDK をインストールする
- 以下のパッケージをインストールする
go get -u github.com/dgrijalva/jwt-go
go get -u github.com/gorilla/mux
go get -u google.golang.org/appengine/datastore
go get -u google.golang.org/appengine
go get -u google.golang.org/appengine/log
go get -u github.com/jteeuwen/go-bindata/...
開発環境を起動する
- プロジェクトのルートで以下のコマンドを実行する
goapp serve app
デプロイ
Google Cloud Platform でプロジェクトを作成
以下のコマンドをプロジェクトのルート実行してデプロイ
appcfg.py -A <PROJECT_ID> -V v1 update app/
公開鍵と秘密鍵を準備する
openssl で鍵の作成
mkdir assets
cd assets
openssl ecparam -genkey -name prime256v1 -noout -out ec256-key-pair.pem
openssl ec -in ec256-key-pair.pem -outform PEM -pubout -out ec256-key-pub.pem
openssl ec -in ec256-key-pair.pem -outform PEM -out ec256-key-pri.pem
go-bindata で鍵を go の実行ファイルに変換
go-bindata -o bindata/bindata.go assets
鍵の実行ファイルの編集
- パッケージ名を 'main' から 'bindata' に変更
cURL でリクエストを実行する
ユーザー登録
curl -H "Accept: application/json" -H "Content-type: application/json" -X POST -d '{"username":"USERNAME","password":"PASSWORD"}' http://<PROJECT_ID>.appspot.com/registration
ユーザー認証
curl -H "Accept: application/json" -H "Content-type: application/json" -X POST -d '{"username":"USERNAME","password":"PASSWORD"}' http://<PROJECT_ID>.appspot.com/authentication
誰でもアクセスできるコンテンツの表示
curl -H "Accept: application/json" -H "Content-type: application/json" http://<PROJECT_ID>.appspot.com/hello
認可されたユーザーだけアクセスできるコンテンツの表示
-
<TOKEN>
にはユーザー認証のレスポンスの Token
要素の値を挿入する
curl -H "Accept: application/json" -H "Content-type: application/json" -H "Authorization: Bearer <TOKEN>" http://<PROJECT_ID>.appspot.com/authorized_hello
github.com/jteeuwen/go-bindata
を使って go のファイルに変換して利用したapp
app/app.go
が実行ファイルで init
が GAE から実行されるapp/app.yaml
は最低限の設定ファイル
GAE 用のファイル構成
- GOPATH の切り替えなどは行わず通常の GOPATH 以下にプロジェクトを作成する
- app 以下に GAE 用のファイルを配置し、自プロジェクトのフルパスでファイルをインポートしてハンドラーを登録する
app/app.go
import (
"github.com/nirasan/gae-jwt/handler"
"net/http"
)
func init() {
http.Handle("/", handler.NewHandler())
}
ユーザーの登録処理
- handler.RegistrationHandler で実装
- Datastore へユーザー情報を登録する
- パスワードは bcrypt でハッシュ化して保存する
handler.go
// ユーザー情報の登録準備
ctx := appengine.NewContext(r)
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost)
if err != nil {
panic(err.Error())
}
ua := UserAuthentication{Username: req.Username, Password: string(hashedPassword)}
// Datastore へユーザー情報を登録
err = datastore.RunInTransaction(ctx, func(ctx context.Context) error {
key := datastore.NewKey(ctx, "UserAuthentication", req.Username, 0, nil)
var userAuthentication UserAuthentication
if err := datastore.Get(ctx, key, &userAuthentication); err != datastore.ErrNoSuchEntity {
return errors.New("user already exist")
}
if _, err := datastore.Put(ctx, key, &ua); err == nil {
return nil
} else {
return err
}
}, nil)
ユーザーの認証処理
- handler.AuthenticationHandler で実装
ユーザーの参照
- Datastore を検索してユーザーが存在するか確認する
- パスワードは bcrypt で検証する
handler.go
// ユーザーが存在するかどうか確認
ctx := appengine.NewContext(r)
key := datastore.NewKey(ctx, "UserAuthentication", req.Username, 0, nil)
var userAuthentication UserAuthentication
if err := datastore.Get(ctx, key, &userAuthentication); err != nil {
EncodeJson(w, AuthenticationHandlerResponse{Success: false})
return
}
// パスワードの検証
if err := bcrypt.CompareHashAndPassword([]byte(userAuthentication.Password), []byte(req.Password)); err != nil {
EncodeJson(w, AuthenticationHandlerResponse{Success: false})
return
}
秘密鍵の読み込み
- go-bindata で固めたファイルから秘密鍵を読み込む
handler.go
pem, e := bindata.Asset("assets/ec256-key-pri.pem")
JWT トークンの作成と署名
handler.go
// 署名アルゴリズムの作成
method := jwt.GetSigningMethod("ES256")
// トークンの作成
token := jwt.NewWithClaims(method, jwt.MapClaims{
"sub": req.Username,
"exp": time.Now().Add(time.Hour * 1).Unix(),
})
// 秘密鍵のパース
privateKey, e := jwt.ParseECPrivateKeyFromPEM(pem)
if e != nil {
panic(e.Error())
}
// トークンの署名
signedToken, e := token.SignedString(privateKey)
if e != nil {
panic(e.Error())
}
認可したユーザーのみ閲覧可能なコンテンツを提供
- handler.AuthorizedHelloWorldHandler で実装
- handler.Authorization でリクエストからトークンを取得して検証する
handler.go
token, e := Authorization(r)
if e != nil {
EncodeJson(w, HelloWorldHandlerResponse{Success: false})
}
インストール
-
App Engine SDK をインストールする
- 以下のパッケージをインストールする
go get -u github.com/dgrijalva/jwt-go
go get -u github.com/gorilla/mux
go get -u google.golang.org/appengine/datastore
go get -u google.golang.org/appengine
go get -u google.golang.org/appengine/log
go get -u github.com/jteeuwen/go-bindata/...
開発環境を起動する
- プロジェクトのルートで以下のコマンドを実行する
goapp serve app
デプロイ
Google Cloud Platform でプロジェクトを作成
以下のコマンドをプロジェクトのルート実行してデプロイ
appcfg.py -A <PROJECT_ID> -V v1 update app/
公開鍵と秘密鍵を準備する
openssl で鍵の作成
mkdir assets
cd assets
openssl ecparam -genkey -name prime256v1 -noout -out ec256-key-pair.pem
openssl ec -in ec256-key-pair.pem -outform PEM -pubout -out ec256-key-pub.pem
openssl ec -in ec256-key-pair.pem -outform PEM -out ec256-key-pri.pem
go-bindata で鍵を go の実行ファイルに変換
go-bindata -o bindata/bindata.go assets
鍵の実行ファイルの編集
- パッケージ名を 'main' から 'bindata' に変更
cURL でリクエストを実行する
ユーザー登録
curl -H "Accept: application/json" -H "Content-type: application/json" -X POST -d '{"username":"USERNAME","password":"PASSWORD"}' http://<PROJECT_ID>.appspot.com/registration
ユーザー認証
curl -H "Accept: application/json" -H "Content-type: application/json" -X POST -d '{"username":"USERNAME","password":"PASSWORD"}' http://<PROJECT_ID>.appspot.com/authentication
誰でもアクセスできるコンテンツの表示
curl -H "Accept: application/json" -H "Content-type: application/json" http://<PROJECT_ID>.appspot.com/hello
認可されたユーザーだけアクセスできるコンテンツの表示
-
<TOKEN>
にはユーザー認証のレスポンスの Token
要素の値を挿入する
curl -H "Accept: application/json" -H "Content-type: application/json" -H "Authorization: Bearer <TOKEN>" http://<PROJECT_ID>.appspot.com/authorized_hello
go get -u github.com/dgrijalva/jwt-go
go get -u github.com/gorilla/mux
go get -u google.golang.org/appengine/datastore
go get -u google.golang.org/appengine
go get -u google.golang.org/appengine/log
go get -u github.com/jteeuwen/go-bindata/...
- プロジェクトのルートで以下のコマンドを実行する
goapp serve app
デプロイ
Google Cloud Platform でプロジェクトを作成
以下のコマンドをプロジェクトのルート実行してデプロイ
appcfg.py -A <PROJECT_ID> -V v1 update app/
公開鍵と秘密鍵を準備する
openssl で鍵の作成
mkdir assets
cd assets
openssl ecparam -genkey -name prime256v1 -noout -out ec256-key-pair.pem
openssl ec -in ec256-key-pair.pem -outform PEM -pubout -out ec256-key-pub.pem
openssl ec -in ec256-key-pair.pem -outform PEM -out ec256-key-pri.pem
go-bindata で鍵を go の実行ファイルに変換
go-bindata -o bindata/bindata.go assets
鍵の実行ファイルの編集
- パッケージ名を 'main' から 'bindata' に変更
cURL でリクエストを実行する
ユーザー登録
curl -H "Accept: application/json" -H "Content-type: application/json" -X POST -d '{"username":"USERNAME","password":"PASSWORD"}' http://<PROJECT_ID>.appspot.com/registration
ユーザー認証
curl -H "Accept: application/json" -H "Content-type: application/json" -X POST -d '{"username":"USERNAME","password":"PASSWORD"}' http://<PROJECT_ID>.appspot.com/authentication
誰でもアクセスできるコンテンツの表示
curl -H "Accept: application/json" -H "Content-type: application/json" http://<PROJECT_ID>.appspot.com/hello
認可されたユーザーだけアクセスできるコンテンツの表示
-
<TOKEN>
にはユーザー認証のレスポンスの Token
要素の値を挿入する
curl -H "Accept: application/json" -H "Content-type: application/json" -H "Authorization: Bearer <TOKEN>" http://<PROJECT_ID>.appspot.com/authorized_hello
mkdir assets
cd assets
openssl ecparam -genkey -name prime256v1 -noout -out ec256-key-pair.pem
openssl ec -in ec256-key-pair.pem -outform PEM -pubout -out ec256-key-pub.pem
openssl ec -in ec256-key-pair.pem -outform PEM -out ec256-key-pri.pem
go-bindata -o bindata/bindata.go assets
ユーザー登録
curl -H "Accept: application/json" -H "Content-type: application/json" -X POST -d '{"username":"USERNAME","password":"PASSWORD"}' http://<PROJECT_ID>.appspot.com/registration
ユーザー認証
curl -H "Accept: application/json" -H "Content-type: application/json" -X POST -d '{"username":"USERNAME","password":"PASSWORD"}' http://<PROJECT_ID>.appspot.com/authentication
誰でもアクセスできるコンテンツの表示
curl -H "Accept: application/json" -H "Content-type: application/json" http://<PROJECT_ID>.appspot.com/hello
認可されたユーザーだけアクセスできるコンテンツの表示
-
<TOKEN>
にはユーザー認証のレスポンスのToken
要素の値を挿入する
curl -H "Accept: application/json" -H "Content-type: application/json" -H "Authorization: Bearer <TOKEN>" http://<PROJECT_ID>.appspot.com/authorized_hello
Author And Source
この問題について(GAE/Go で公開鍵暗号を使う JWT 認証サーバーを作った), 我々は、より多くの情報をここで見つけました https://qiita.com/nirasan/items/daa654f4f0e2321aedaa著者帰属:元の著者の情報は、元のURLに含まれています。著作権は原作者に属する。
Content is automatically searched and collected through network algorithms . If there is a violation . Please contact us . We will adjust (correct author information ,or delete content ) as soon as possible .