You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Azure AD JWT验证报错:签名验证失败求排查方案

Azure AD JWT验证签名失败问题排查

实现Azure AD的JWT验证时,执行validate_access_token代码返回**"Invalid access token: Signature verification failed"**错误。已确认JWT的kid与从JWKS获取的公钥kid匹配,算法为RS256,但签名仍验证失败。以下是代码、JWT令牌及调试日志,请求排查原因:

验证代码

def validate_access_token(self, access_token):
    jwks_url = f"{self.authority}/discovery/v2.0/keys"

    # 从JWKS端点获取密钥
    response = requests.get(jwks_url)
    jwks_data = response.json()

    # 提取并存储Azure AD公钥
    azure_ad_keys = {}
    for key in jwks_data["keys"]:
        key_id = key["kid"]
        # 提取x509证书
        x5c_cert = key["x5c"][0]
        # 转换为PEM格式
        pem_cert = f"-----BEGIN CERTIFICATE-----
{x5c_cert}
-----END CERTIFICATE-----"
        azure_ad_keys[key_id] = pem_cert
    # 解码访问令牌
    print(azure_ad_keys)    
    try:
        token_header = jwt.get_unverified_header(access_token)
        logging.debug(f"Token header: {token_header}")
        token_kid = token_header["kid"]
        token_alg = token_header["alg"]
        
        public_key = azure_ad_keys[token_kid]
        logging.debug(f"Token kid: {token_kid}, Token alg: {token_alg}, public key: {public_key}")
        payload = jwt.decode(
            token=access_token, 
            key=public_key, 
            algorithms=[token_alg])
        logging.debug(f"Token payload: {payload}")
    except JWTError as e:
        raise HTTPException(status_code=401, detail=f"Invalid access token: {e}")

    # 验证令牌声明
    if payload["iss"] != f"{self.authority}/v2.0":
        raise HTTPException(status_code=401, detail="Invalid token issuer")

    if payload["aud"] != self.client_id:
        raise HTTPException(status_code=401, detail="Invalid token audience")

    if payload["exp"] < time.time():
        raise HTTPException(status_code=401, detail="Access token expired")

    return payload

JWT令牌

eyJ0eXAiOiJKV1QiLCJub25jZSI6IjVETWxPSjV4T1BrZUZ4bnE3QVJVZDl2Uy1taGYwc09Qb290NGRaTlViVkkiLCJhbGciOiJSUzI1NiIsIng1dCI6ImtXYmthYTZxczh3c1RuQndpaU5ZT2hIYm5BdyIsImtpZCI6ImtXYmthYTZxczh3c1RuQndpaU5ZT2hIYm5BdyJ9.eyJhdWQiOiIwMDAwMDAwMy0wMDAwLTAwMDAtYzAwMC0wMDAwMDAwMDAwMDAiLCJpc3MiOiJodHRwczovL3N0cy53aW5kb3dzLm5ldC81NzA2ZTg5Ny02NWYyLTQ4YjMtODY2ZS0wZGY3YTE4M2Y0NjEvIiwiaWF0IjoxNzA3ODMxODcwLCJuYmYiOjE3MDc4MzE4NzAsImV4cCI6MTcwNzgzNjYyMCwiYWNjdCI6MCwiYWNyIjoiMSIsImFpbyI6IkUyVmdZSEQ0K1o1VHE0L2ZXZEJCNU5GSlE0YnlYRlBGTzJHRjZUK3JsVVNtNTk3S0NNNmZWeSt0SU5GVFhYcENVRnowWC9jdkFBPT0iLCJhbXIiOlsicHdkIl0sImFwcF9kaXNwbGF5bmFtZSI6IkF1dGggU2VydmljZSIsImFwcGlkIjoiODVmMmEzMTktMGUzOC00NTU0LWIwM2UtZjVkMzcyY2EzZjI5IiwiYXBwaWRhY3IiOiIxIiwiZmFtaWx5X25hbWUiOiJBZG1pbmlzdHJhdG9yIiwiZ2l2ZW5fbmFtZSI6Ik1PRCIsImlkdHlwIjoidXNlciIsImlwYWRkciI6IjExNi4wLjEuODEiLCJuYW1lIjoiTU9EIEFkbWluaXN0cmF0b3IiLCJvaWQiOiI4MGMxMzJkMi1iOTQzLTRlNGMtYjA4OS02MzY3ZmNlNjVkNDEiLCJwbGF0ZiI6IjUiLCJwdWlkIjoiMTAwMzIwMDM0ODBDMTQ4RSIsInJoIjoiMC5BU3NBbC1nR1ZfSmxzMGlHYmczM29ZUDBZUU1BQUFBQUFBQUF3QUFBQUFBQUFBRENBRjAuIiwic2NwIjoiRGlyZWN0b3J5LlJlYWQuQWxsIGVtYWlsIG9wZW5pZCBwcm9maWxlIFVzZXIuUmVhZCBVc2VyLlJlYWQuQWxsIFVzZXIuUmVhZEJhc2ljLkFsbCIsInNpZ25pbl9zdGF0ZSI6WyJrbXNpIl0sInN1YiI6InV2TG90Q2dERFF0eGtpdGE4UHU0V3hBd0FpYWFEOG5rOV91eXVQX2E3NG8iLCJ0ZW5hbnRfcmVnaW9uX3Njb3BlIjoiQVMiLCJ0aWQiOiI1NzA2ZTg5Ny02NWYyLTQ4YjMtODY2ZS0wZGY3YTE4M2Y0NjEiLCJ1bmlxdWVfbmFtZSI6ImFkbWluQE0zNjV4NzI2NTQ3NzYub25taWNyb3NvZnQuY29tIiwidXBuIjoiYWRtaW5ATTM2NXg3MjY1NDc3Ni5vbm1pY3Jvc29mdC5jb20iLCJ1dGkiOiIzcWtySm1XRWowYTRBQ0ZzS2l0b0FBIiwidmVyIjoiMS4wIiwid2lkcyI6WyI2MmU5MDM5NC02OWY1LTQyMzctOTE5MC0wMTIxNzcxNDVlMTAiLCJiNzlmYmY0ZC0zZWY5LTQ2ODktODE0My03NmIxOTRlODU1MDkiXSwieG1zX3N0Ijp7InN1YiI6Ik1zdTFwRFVQc3g0SGlaODItVDBTZlB3a29NSmNDN3hkNWVJRGRpU3FWY1UifSwieG1zX3RjZHQiOjE3MDYwODE5NTF9.sQKGfmYPFCarHwx1hMKbgJsTmlSMt8-Sg491w0DWWXE1ZjXBLdOSbuEJjTEpNj0VG-YSuN1SR-fs44D969bGaKD4UvZBAAm8gJIm5inkGp6-x_3WL3VMmUz3nMthgFKocNcxp7HwOqn7Gsj_oiTzNMveD5YgT3SXvrgXQQFzG1eNNT8WsN1NCa1nanFO789yaqXdjaDQbo5HKqyg9xwrKp-n6fpsFQjWXesr3GFZLHNt1tTMAnjOY_hzQ9xSVGdd7z7FAacM_NlLQZVhvD0iDp2D4nFXQWHAy9qDuk8u-6U9jcweQYubw68W2oP3gYvMqacZKTr8z70iE9sF0uujrQ

调试日志

DEBUG:root:Token header: {'typ': 'JWT', 'nonce': '5DMlOJ5xOPkeFxnq7ARUd9vS-mhf0sOPoot4dZNUbVI', 'alg': 'RS256', 'x5t': 'kWbkaa6qs8wsTnBwiiNYOhHbnAw', 'kid': 'kWbkaa6qs8wsTnBwiiNYOhHbnAw'}
DEBUG:root:Token kid: kWbkaa6qs8wsTnBwiiNYOhHbnAw, Token alg: RS256, public key: -----BEGIN CERTIFICATE-----
MIIC/TCCAeWgAwIBAgIICHb5qy8hKKgwDQYJKoZIhvcNAQELBQAwLTErMCkGA1UEAxMiYWNjb3VudHMuYWNjZXNzY29udHJvbC53aW5kb3dzLm5ldDAeFw0yNDAxMTUxODA0MTRaFw0yOTAxMTUxODA0MTRaMC0xKzApBgNVBAMTImFjY291bnRzLmFjY2Vzc2NvbnRyb2wud2luZG93cy5uZXQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC3pDZdJ5acwD/5ysfZRt+19LTVAMCoeg9AWqxG1WTEbh7Jqac2VOXcNrTtBCSOk8Rsugu2C0wjY+vSmU7vFT1/3iaFt8r9QnpjQpxbGHAoyQKCfNwU5AXh4f8AgIcs4pry8+2G1yms1wKaNuSxblNgFmLq4uEUvD8eyMY7GErRVoNadaLM1V6q/NUHSO31V2Z+GzpmiHL/VvZa6x1p3U2ZIrOELvggOOUhoWiKT9kkl20s6CgjA5lMtbQzVQqFGta2PsCNUKcT/MGKWgAKbUisgz8/KYTXRwknpYXPb16niDtfrnEIRTrMnmggWJu+TpwopwU0HsUWNt6FhWnDkHFVAgMBAAGjITAfMB0GA1UdDgQWBBQLGQYqt7pRrKWQ25XWSi6lGN818DANBgkqhkiG9w0BAQsFAAOCAQEAtky1EYTKZvbTAveLmL3VCi+bJMjY5wyDO4Yulpv0VP1RS3dksmEALOsa1Bfz2BXVpIKPUJLdvFFoFhDqReAqRRqxylhI+oMwTeAsZ1lYCV4hTWDrN/MML9SYyeQ441Xp7xHIzu1ih4rSkNwrsx231GTfzo6dHMsi12oEdyn6mXavWehBDbzVDxbeqR+0ymhCgeYjIfCX6z2SrSMGYiG2hzs/xzypnIPnv6cBMQQDS4sdquoCsvIqJRWmF9ow79oHhzSTwGJj4+jEQi7QMTDR30rYiPTIdE63bnuARdgNF/dqB7n4ZJv566jvbzHpfCTqrJyj7Guvjr9i56NpLmz2DA==
-----END CERTIFICATE-----

排查方向及解决方案

  1. 修复PEM证书格式
    构建PEM证书时,显式使用\n确保换行符正确,避免环境解析差异:

    pem_cert = f"-----BEGIN CERTIFICATE-----\n{x5c_cert}\n-----END CERTIFICATE-----"
    
  2. 正确解析公钥
    不要直接传入PEM字符串给jwt.decode,先将证书解析为RSA公钥对象:

    from cryptography.x509 import load_pem_x509_certificate
    from cryptography.hazmat.backends import default_backend
    
    # 在获取public_key后添加解析逻辑
    cert_obj = load_pem_x509_certificate(public_key.encode(), default_backend())
    public_key = cert_obj.public_key()
    
  3. 核对令牌版本与Issuer验证逻辑
    从令牌Payload看,iss是https://sts.windows.net/5706e897-65f2-48b3-866e-0df7a183f461/,这是Azure AD v1.0格式的Issuer。如果你的authority是https://login.microsoftonline.com/5706e897-65f2-48b3-866e-0df7a183f461,当前代码中f"{self.authority}/v2.0"的拼接会导致Issuer验证失败,同时需确认JWKS端点是否对应v1.0({authority}/discovery/v1.0/keys)。

  4. 确认依赖完整性
    确保安装了cryptography库,PyJWT依赖它处理RS256签名:

    pip install cryptography
    

内容的提问来源于stack exchange,提问作者galih

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.30 03:22:33