如何使用Python验证ADFS返回的SAML登录响应签名
SAML HTTP-Redirect绑定签名验证步骤及Python实现
验证前置说明
ADFS通过HTTP-Redirect绑定返回SAML响应时,签名是HTTP层级的签名,而非SAML XML内部的签名,这是绝大多数验证失败的核心原因:不要对解密后的XML内容做签名校验,要对URL参数拼接串做校验。
具体验证步骤
- 步骤1:获取请求中的三个核心参数的原始值(未经过URL解码的原始查询参数值):
SAMLResponse、SigAlg,如果请求携带RelayState也需要同步获取 - 步骤2:按照SAML 2.0规范构造待验签字符串:
- 将所有参与签名的参数按参数名字符升序排列,顺序为
RelayState(如有)→SAMLResponse→SigAlg - 每个参数按照
参数名=URL编码后的参数值格式拼接,参数值编码遵循RFC 3986规则,*不做编码 - 多个参数之间用
&拼接,将最终拼接结果转为UTF-8编码的字节流
- 将所有参与签名的参数按参数名字符升序排列,顺序为
- 步骤3:从ADFS元数据中获取签名用的X.509公钥证书,导出为PEM格式
- 步骤4:将请求中的
Signature参数值做Base64解码,得到原始签名字节流 - 步骤5:使用公钥、
SigAlg指定的签名算法,验证待验签字节流和签名字节流是否匹配
常见错误说明
- 错误使用SAML POST绑定的验证逻辑,对解密/解码后的SAML XML内容做验签,Redirect绑定的签名不涉及XML内容
- 拼接待验签串时顺序错误,或者对参数值做了URL解码后再拼接
- 混淆加密证书和签名证书:签名验签用的是ADFS的公钥,不是你用来解密SAML响应的业务侧私钥
Python实现方案
依赖安装
执行以下命令安装加密库:
pip install cryptography
示例代码
from urllib.parse import quote from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.asymmetric import padding from cryptography.x509 import load_pem_x509_certificate from cryptography.hazmat.backends import default_backend import base64 # -------------------------- 配置项开始 -------------------------- # 填入请求中获取的原始参数值(不要做URL解码) SAML_RESPONSE_RAW = "替换为你的SAMLResponse参数原始值" SIG_ALG_RAW = "替换为你的SigAlg参数原始值" SIGNATURE_RAW = "替换为你的Signature参数原始值" # 可选:如果有RelayState参数,取消下方注释并填入原始值 # RELAY_STATE_RAW = "替换为你的RelayState参数原始值" # 填入ADFS签名证书PEM内容,从ADFS元数据中获取 ADFS_SIGN_CERT_PEM = b""" -----BEGIN CERTIFICATE----- 替换为你的ADFS签名公钥证书内容 -----END CERTIFICATE----- """ # -------------------------- 配置项结束 -------------------------- # 1. 构造待验签字节流 param_list = [] # 有RelayState则先加入,按参数名字母升序排列 # param_list.append(f"RelayState={quote(RELAY_STATE_RAW, safe='*')}") param_list.append(f"SAMLResponse={quote(SAML_RESPONSE_RAW, safe='*')}") param_list.append(f"SigAlg={quote(SIG_ALG_RAW, safe='*')}") sign_data = "&".join(param_list).encode("utf-8") # 2. 加载ADFS公钥 cert = load_pem_x509_certificate(ADFS_SIGN_CERT_PEM, backend=default_backend()) public_key = cert.public_key() # 3. 解码签名值 signature_bytes = base64.b64decode(SIGNATURE_RAW) # 4. 执行验签 try: public_key.verify( signature_bytes, sign_data, padding.PKCS1v15(), # 如果SigAlg为其他算法,此处对应修改,如sha1则用hashes.SHA1() hashes.SHA256() ) print("签名验证通过") except Exception as e: print(f"签名验证失败:{str(e)}")
内容的提问来源于stack exchange,提问作者Kadhiresh Gajendiran
相关产品推荐
相关产品推荐

