①署名鍵生成
-
opensslを利用して秘密鍵/署名鍵を生成
openssl genrsa -out private-key.pem 2048 openssl rsa -in private-key.pem -pubout -out public-key.pem
②公開鍵をJWK形式に変換
-
jsライブラリ
pem-jwk
を利用して、JWK形式に変換する。※もっとスマートな方法があれば... -
generate_jwk.js
var fs = require('fs') var pem2jwk = require('pem-jwk').pem2jwk var str = fs.readFileSync('./public-key.pem', 'ascii') var jwk = pem2jwk(str) console.log(jwk)
node generate_jwk.js { kty: 'RSA', n: 'pw5We-LHJAIXSOwNbUxO8T615XgNn8e1J_1auE8Nza9MEkSWqf1ylz1MnOoPWqnnleo0mJfAza2Ro5xm7aKUGtPVwThwGj32SUtYj5mYPd3kfVgO9colc9k_dle8OpcP54CoA-5fVd24jt-jL3SXcITxB2-9-ms2gyTZnPjTaEeXCa6ElyMiHjcw4VApwzmZsu6wkCW4uPnX-_uZvxwjcGEH9RKMyWU0B5m_NOKcgy9eChZMw01rAxlxDugvvEU0iedzuAUu3vv19tn1UOvIUToyXrhUw0PSEr_nUg_zITVf-IjZ6yqKgI_2HU37AypypIe8TsjmgHwSP49X2MnUmQ', e: 'AQAB' }
③エンドポイント作成
プロジェクト構成
test --- docker-compose.yml
|_ app - Dockerfile
|_ requirements.txt
|_ main.py
|_ private-key.pem※前述の手順で生成したもの
コード・設定ファイル類
main.py
- FastAPIを使用して各種エンドポイントを作成する。
from fastapi import FastAPI,Form
from starlette.requests import Request
from starlette.responses import RedirectResponse
import jwt
from jwt import PyJWKClient
from pydantic import BaseModel
import datetime
from datetime import datetime, timedelta
import time
# リクエストbody定義
class IdToken(BaseModel):
id_token: str
# JWK,IDトークン要素
KID = "12345"
USE = "sig"
ALG = "RS256"
ISS = "http://server.example.com"
SUB = "248289761001"
AUD = "s6BhdRkqt3"
app = FastAPI()
# JWKSエンドポイント
@app.get("/auth/jwks")
def jwks():
# 「公開鍵をJWK形式に変換」の取得結果にkid,use,algを追加
return {
"keys":[{
"kid":KID,
"use":USE,
"alg":ALG,
"kty": "RSA",
"n": "pw5We-LHJAIXSOwNbUxO8T615XgNn8e1J_1auE8Nza9MEkSWqf1ylz1MnOoPWqnnleo0mJfAza2Ro5xm7aKUGtPVwThwGj32SUtYj5mYPd3kfVgO9colc9k_dle8OpcP54CoA-5fVd24jt-jL3SXcITxB2-9-ms2gyTZnPjTaEeXCa6ElyMiHjcw4VApwzmZsu6wkCW4uPnX-_uZvxwjcGEH9RKMyWU0B5m_NOKcgy9eChZMw01rAxlxDugvvEU0iedzuAUu3vv19tn1UOvIUToyXrhUw0PSEr_nUg_zITVf-IjZ6yqKgI_2HU37AypypIe8TsjmgHwSP49X2MnUmQ",
"e": "AQAB"
}]
}
# IDトークン生成エンドポイント
@app.get("/auth/generate_id_token")
def generate_it_token():
# 署名用秘密鍵
with open('./private-key.pem') as f_private:
private_key = f_private.read()
# ヘッダー部
header = {
"kid":KID,
"alg":ALG
}
# ペイロード部
payload = {
"iss":ISS,
"aud":AUD,
"sub":SUB,
"exp":int(time.mktime((datetime.now() + timedelta(days=1)).timetuple())),
"iat":int(time.mktime(datetime.now().timetuple()))
}
id_token = jwt.encode(payload, private_key,
algorithm='RS256', headers=header)
return {
"id_token":id_token
}
# IDトークン検証エンドポイント
@app.post("/auth/verify_id_token")
def verify_id_token(idToken: IdToken):
# ローカルJWKSエンドポイントURL
url = "http://localhost:8000/auth/jwks"
jwks_client = PyJWKClient(url)
public_key = jwks_client.get_signing_key_from_jwt(idToken.id_token)
payload = jwt.decode(idToken.id_token, public_key.key, algorithms=["RS256"], audience=AUD)
return payload
if __name__ == "__main__":
uvicorn.run(app, port=8000, loop="asyncio")
Dockerfile
FROM python:3.8
WORKDIR /usr/src/server
ADD requirements.txt .
ADD private-key.pem .
RUN pip install -r requirements.txt
# uvicornのオプションに--reloadを付与し、
# main.pyの編集と同時に変更内容を反映させる。
CMD ["uvicorn", "main:app", "--reload", "--host", "0.0.0.0", "--port", "8000"]
requirements.txt
※依存ライブラリ
fastapi
python-multipart
requests
uvicorn
cryptography
PyJWT
docker-compose.yml
version: "3.5"
networks:
container-link:
name: docker.internal
services:
app:
container_name: "app"
build: ./app
volumes:
- ./app:/usr/src/server
ports:
- "8000:8000"
networks:
- container-link
動作確認
起動
docker-compose up -d
IDトークン生成エンドポイント
-
リクエスト
GET /auth/generate_id_token HTTP/1.1 Host: 127.0.0.1:8000 Cache-Control: no-cache Postman-Token: 438dbc81-196c-9983-dce2-8e8875680b18
-
レスポンス
{ "id_token": "eyJ0eXAiOiJ...Y3fDPOg" }
IDトークン検証エンドポイント
-
リクエスト
POST /auth/verify_id_token HTTP/1.1 Host: 127.0.0.1:8000 Content-Type: application/json Cache-Control: no-cache Postman-Token: 1a357704-50f8-256e-5821-347114829cec { "id_token": "eyJ0eXAiOiJKV1Q...3rHY3fDPOg" }
-
レスポンス
{ "iss": "http://server.example.com", "aud": "s6BhdRkqt3", "sub": "248289761001", "exp": 1648722999, "iat": 1648636599 }
JWKSエンドポイント
-
リクエスト
GET /auth/jwks HTTP/1.1 Host: 127.0.0.1:8000 Cache-Control: no-cache Postman-Token: 5902c2a7-0337-f43e-0f71-c448d22ee3c8
-
レスポンス
{ "keys": [ { "kid": "12345", "use": "sig", "alg": "RS256", "kty": "RSA", "n": "pw5We-LHJAIXSOwNbUxO8T615XgNn8e1J_1auE8Nza9MEkSWqf1ylz1MnOoPWqnnleo0mJfAza2Ro5xm7aKUGtPVwThwGj32SUtYj5mYPd3kfVgO9colc9k_dle8OpcP54CoA-5fVd24jt-jL3SXcITxB2-9-ms2gyTZnPjTaEeXCa6ElyMiHjcw4VApwzmZsu6wkCW4uPnX-_uZvxwjcGEH9RKMyWU0B5m_NOKcgy9eChZMw01rAxlxDugvvEU0iedzuAUu3vv19tn1UOvIUToyXrhUw0PSEr_nUg_zITVf-IjZ6yqKgI_2HU37AypypIe8TsjmgHwSP49X2MnUmQ", "e": "AQAB" } ] }