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-----
排查方向及解决方案
修复PEM证书格式
构建PEM证书时,显式使用\n确保换行符正确,避免环境解析差异:pem_cert = f"-----BEGIN CERTIFICATE-----\n{x5c_cert}\n-----END CERTIFICATE-----"正确解析公钥
不要直接传入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()核对令牌版本与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)。确认依赖完整性
确保安装了cryptography库,PyJWT依赖它处理RS256签名:pip install cryptography
内容的提问来源于stack exchange,提问作者galih
相关产品推荐
相关产品推荐

