Open2

JWTの検証のサンプル

takashi.kanazawatakashi.kanazawa

JWTの署名・発行者の検証手順

1. JWTのヘッダーからalgkidを取得

import base64
import json

def decode_jwt_header(jwt_token):
    header_base64 = jwt_token.split('.')[0]
    padding = '=' * (4 - len(header_base64) % 4)
    header_json = base64.urlsafe_b64decode(header_base64 + padding).decode('utf-8')
    header = json.loads(header_json)
    return header.get('alg'), header.get('kid')

# 例: 
jwt_token = "ヘッダー.ペイロード.署名"
alg, kid = decode_jwt_header(jwt_token)
print(f"alg: {alg}, kid: {kid}")

2. JWKSエンドポイントから公開鍵を取得

import requests

def get_public_key(jwks_url, kid):
    response = requests.get(jwks_url)
    jwks = response.json()
    
    for key in jwks['keys']:
        if key['kid'] == kid:
            return key['n'], key['e'], key['alg']
    raise ValueError("kidに一致する鍵が見つかりませんでした")

# 例: 
jwks_url = "https://example.com/.well-known/jwks.json"
n, e, alg = get_public_key(jwks_url, kid)

3. 公開鍵を生成

from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.backends import default_backend
import base64

def create_rsa_public_key(n, e):
    try:
        n_int = int.from_bytes(base64.urlsafe_b64decode(n + "=="), 'big')
        e_int = int.from_bytes(base64.urlsafe_b64decode(e + "=="), 'big')
        public_key = rsa.RSAPublicNumbers(e_int, n_int).public_key(default_backend())
        return public_key
    except Exception as ex:
        print(f"公開鍵の生成に失敗しました: {ex}")
        return None

# 例: 
public_key = create_rsa_public_key(n, e)

4. JWTの署名を検証

import jwt
from jwt.exceptions import InvalidSignatureError, ExpiredSignatureError, InvalidTokenError

def verify_jwt(jwt_token, public_key):
    try:
        payload = jwt.decode(jwt_token, public_key, algorithms=['RS256'])
        return payload
    except InvalidSignatureError:
        print("署名が無効です")
    except ExpiredSignatureError:
        print("JWTの有効期限が切れています")
    except InvalidTokenError:
        print("トークンが無効です")
    return None

# 例: 
payload = verify_jwt(jwt_token, public_key)
if payload:
    print("署名検証成功:", payload)
else:
    print("署名検証失敗")

5. 発行者(iss)を検証

def verify_jwt_and_issuer(jwt_token, public_key, expected_issuer):
    try:
        # JWTをデコードして署名を検証
        payload = jwt.decode(jwt_token, public_key, algorithms=['RS256'])
        
        # `iss`フィールドを取得して検証
        issuer = payload.get('iss')
        if issuer != expected_issuer:
            raise ValueError(f"発行者が一致しません: 期待された発行者: {expected_issuer}, 実際の発行者: {issuer}")
        
        # 署名と発行者が正しければペイロードを返す
        return payload
    
    except jwt.InvalidTokenError as e:
        print(f"署名検証に失敗しました: {e}")
    except ValueError as e:
        print(f"発行者の検証に失敗しました: {e}")
    return None

# 例: JWTの署名と発行者を検証する
expected_issuer = "https://example.com"
payload = verify_jwt_and_issuer(jwt_token, public_key, expected_issuer)

if payload:
    print("署名検証および発行者検証成功:", payload)
else:
    print("署名または発行者の検証に失敗しました")

6. まとめ

  1. JWTのヘッダーからalgkidを取得
  2. JWKSエンドポイントから公開鍵情報を取得
  3. 公開鍵を使ってJWTの署名を検証
  4. ペイロードのissフィールドを検証
takashi.kanazawatakashi.kanazawa

IstioのRequestAuthenticationにおけるissuerとjwksUriも、このような概念でチェックしていると思う。

https://istio.io/latest/docs/tasks/security/authorization/authz-jwt/

$ kubectl exec "$(kubectl get pod -l app=sleep -n foo -o jsonpath={.items..metadata.name})" -c sleep -n foo -- curl "http://httpbin.foo:8000/headers" -sS -o /dev/null -H "Authorization: Bearer invalidToken" -w "%{http_code}\n"
401