JWT 検証の大まかな動きは 昨日 の調べたのですが、もうちょっとちゃんと理解したい。
オーソライザーのサンプル をデバッグして、もうちょっと理解する。
テスト用の Auth0 アカウントを omuron
で取得して試してみる。
環境構築
Fork したサンプルソースを取得。
$ git clone https://github.com/omuron/auth0-golang-api-samples.git
環境変数設定。
始めから準備されている APIs
を利用して設定。
この設定をクローンしたソースで設定する。
$ cd 01-Authorization-RS256/ $ cp .env.example .env $ vi .env
先の情報から、 .env
の内容は以下の通りとなる。
AUTH0_DOMAIN=omuron.us.auth0.com AUTH0_AUDIENCE=https://omuron.us.auth0.com/api/v2/
サンプル起動
docker で動かすシェルが準備されいるので起動する。
$ chmod +x exec.sh $ ./exec.sh
サンプル呼び出し
curl
コマンドで試す。
public API
パブリックエンドポイントはそのまま呼べる。
$ curl -X GET http://localhost:3010/api/public {"message":"Hello from a public endpoint! You don't need to be authenticated to see this."
private API
プライベートエンドポイントは、そのまま呼ぶと認証エラーになる
$ curl -X GET http://localhost:3010/api/private Required authorization token not found
JWT を取得するために Auth0 Management API
の API Explorer
からアプリを作成して Token
を簡単に取れるようにする。
この Token
を Authorization: Bearer
ヘッダーに付与して呼び出すと、プライベート API が呼び出せる。
$ curl -X GET http://localhost:3010/api/private -H "Authorization: Bearer ey..." {"message":"Hello from a private endpoint! You need to be authenticated to see this."}
試しに適当に署名を改ざん(後ろ2byte削る)して渡すと認証エラーになる。
$ curl -X GET http://localhost:3010/api/private -H "Authorization: Bearer ey..." crypto/rsa: verification error
認証処理
認証部分のソース。
jwtmiddleware
というライブラリで、署名の検証を行っている。
jwtMiddleware := jwtmiddleware.New(jwtmiddleware.Options{ ValidationKeyGetter: func(token *jwt.Token) (interface{}, error) { // Verify 'aud' claim aud := os.Getenv("AUTH0_AUDIENCE") checkAud := token.Claims.(jwt.MapClaims).VerifyAudience(aud, false) if !checkAud { return token, errors.New("Invalid audience.") } // Verify 'iss' claim iss := "https://" + os.Getenv("AUTH0_DOMAIN") + "/" checkIss := token.Claims.(jwt.MapClaims).VerifyIssuer(iss, false) if !checkIss { return token, errors.New("Invalid issuer.") } cert, err := getPemCert(token) if err != nil { panic(err.Error()) } result, _ := jwt.ParseRSAPublicKeyFromPEM([]byte(cert)) return result, nil }, SigningMethod: jwt.SigningMethodRS256, })
クレームの検証
環境変数で指定した値と同一かチェックを行っている。
// Verify 'aud' claim aud := os.Getenv("AUTH0_AUDIENCE") checkAud := token.Claims.(jwt.MapClaims).VerifyAudience(aud, false) if !checkAud { return token, errors.New("Invalid audience.") } // Verify 'iss' claim iss := "https://" + os.Getenv("AUTH0_DOMAIN") + "/" checkIss := token.Claims.(jwt.MapClaims).VerifyIssuer(iss, false) if !checkIss { return token, errors.New("Invalid issuer.") }
公開鍵取得
この getPemCert
内で、公開鍵を取得している。
cert, err := getPemCert(token) if err != nil { panic(err.Error()) }
getPemCert
内で、 jwks.json
にアクセスして、公開鍵を生成する元ネタを取得。
resp, err := http.Get("https://" + os.Getenv("AUTH0_DOMAIN") + "/.well-known/jwks.json")
cert
に公開鍵がちゃんと生成されていることを確認できる。
-----BEGIN CERTIFICATE----- MIIDATCCAemgAwIBAgIJbOX8RffC+u+LMA0GCSqGSIb3DQEBCwUAMB4xHDAaBgNVBAMTE29tdXJvbi51cy5hdXRoMC5jb20wHhcNMjAwNjE5MDgxMjAwWhcNMzQwMjI2MDgxMjAwWjAeMRwwGgYDVQQDExNvbXVyb24udXMuYXV0aDAuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA5HgBZMeT0iwu24jS9KsDQX85GLwU9kdbLYnuq/IT8LFENrUH7WxUhRaIaS3FvA4T/OCVNdVcJ+nPH++Gq2AlW4rkv+BBp963IICj9dwYbwZFFKeSlUMp2/QYdg2HeBZLJpqM8z6MhC8tejvWfAZ7Pblu3hICP5wyByDzjF9BiDDfrxdugfUGP0RtRe9EK1+51ZhByO8Hss4mIgoyrDOaw6R0vxJYxJCivOpvhhaKuSvEr+rA4tvOEkd3yeC5JqMyzLb/wouF9Md+0foxpzOrgoYnlIR02zcgnzlMHyRbV5Soaj1nokeXhMmIxU5d8BGNgSn+KX3lIexEHj5XchUUUwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSSScOdNOvjZ/oiJTaKoRk21MXjVzAOBgNVHQ8BAf8EBAMCAoQwDQYJKoZIhvcNAQELBQADggEBAIKHXiXfdzB7IS2N2oeI2519aoFukgx5gzfeG7lecjFDGo4CVxt8ez1/oF9ZZeIopZ150zEtkWOuU4C0IvkKNqT5MyKBfW38ED23SPXuuuPdqXcmlsL7AaKpNYuFTAE7escc1yJ2ddVGWRh+F+jeyAii+cP+5LxDyz39p3wBTeB9Am0RiXnNG+nXkPtT/y/MJ7H7jqjoK5zUHcveLRvxfp2K2RDpZtzDV2u0L+SzsPPgi27VCaCKchZpih2I9SDvhXVMKXLWm5mir8triXBjfYIYcSOz/UqzgpxJp6H9wzyaIfrxBMnMWsMF82movhwINV9bSXl475FpLVvEcUvMwp0= -----END CERTIFICATE-----
RSA 暗号化処理
公開鍵を使って、暗号化(実際は復号化?)している
result, _ := jwt.ParseRSAPublicKeyFromPEM([]byte(cert)) return result, nil
先程の公開鍵から、暗号化に必要な Modulus
と Exponent
の値を取得。
28841510422262814191184117115823758213752086907513605263219924877674141560262837182643343501219794671606638745267994212071107711888444383578941981655498917109003582367242857637556293418425232866320959921520192272011454483771082546734210512600409639950379314126836230221764126757761049713194259208036603196548828825386256538323924518953230503729593844469047641753946128749417423643395982894203570868421081354315530158085347299958369526501387655729392972982576739942821990361267465203802890701650287459043817568473076772711628632764276314805392871972678852782737089350470516910831041844758005478192083860668206781109331 65537
この値を利用して、「電子署名」を復号化する。
復号化した値が、「ヘッダー + .
+ ペイロード」と同じか確認して、改ざんされてないかチェックを行う。
暗号化については このサイト が参考になった。
github.com/auth0/go-jwt-middleware
を利用すれば、あまり深くわかってなくても処理ができる。