将 Java SSO SDK 迁移至 Python 云函数(Serverless)时,我们面临三大挑战:
rsa、pyjwt等第三方库,必须使用原生代码。Object reference空指针错误。return_url,且对 URL 编码有严格要求。标准 JWT 是对 "UrlSafeBase64" 字符串进行签名,而 对 "StandardBase64" 字符串进行签名。这是导致验签失败的根本原因。

为了摆脱对第三方库的依赖,我们利用 Python 内置的大数运算 pow(m, d, n)手动实现了 PKCS#1 v1.5 签名

以下代码剔除了辅助工具类,保留了解决报错的关键逻辑:
_generate_id_tokenexecutereturn_url、KID 生成及 Payload 结构嵌套问题。import json, time, base64, hashlibclass SsoTokenService:ENCODING = "utf-8"# 厂商公钥 (用于生成KID)PUBLIC_KEY_STR = """-----BEGIN PUBLIC KEY-----\n...\n-----END PUBLIC KEY-----"""# 厂商私钥 (用于签名)PRIVATE_KEY_STR = """-----BEGIN RSA PRIVATE KEY-----\n...\n-----END RSA PRIVATE KEY-----"""def execute(self, **kwargs):"""主执行逻辑"""# 1. 处理跳转地址 (关键点:参数名必须是 return_url)# 优先使用传入参数,否则使用硬编码的已验证长链接兜底DEFAULT_URL = "https%3A%2F%2Fwww.example.com%2Fportal..."target_url = kwargs.get("return_url") or DEFAULT_URL# (此处省略防止二次编码的逻辑...)# 2. 构造 Payload (关键点:类型匹配与结构嵌套)# Java端要求 exp/iat 为 String,且 cls 必须嵌套payload_dict = {"iss": kwargs.get("iss", "default_iss"),"sub": kwargs.get("sub", "user@email.com"),"aud": kwargs.get("aud", "tenant_id"),"exp": str(int(time.time()) + 900), # 转String"iat": str(int(time.time())), # 转String"cls": kwargs.get("cls", {"appid": "100"}) # 必须嵌套}# 3. 生成 KID (关键点:保留PEM头尾标签进行Hash)kid = hashlib.sha256(self.PUBLIC_KEY_STR.replace("\n", "").replace("\r", "").encode()).hexdigest()header_json = json.dumps({"alg": "RS256", "kid": kid}, separators=(',', ':'))payload_json = json.dumps(payload_dict, separators=(',', ':'))# 4. 生成 Token (调用非标逻辑)id_token = self._generate_id_token(header_json, payload_json)return f"https://sso.example.com/Auth?id_token={id_token}&return_url={target_url}"def _generate_id_token(self, header_str, payload_str):"""[核心算法] 复刻 Java 非标 JWT 生成流程"""# 1. 先转为【标准】Base64 (含 + / =)std_h = base64.b64encode(header_str.encode()).decode()std_p = base64.b64encode(payload_str.encode()).decode()# 2. 拼接 (注意:对标准Base64串进行拼接)sign_content = f"{std_h}.{std_p}"# 3. 签名 (得到标准Base64签名)std_sig = self._sign_rsa_manual(sign_content)# 4. 最后统一转为 URL-Safe (替换字符,去等号)to_safe = lambda s: s.replace('+', '-').replace('/', '_').replace('=', '')return f"{to_safe(std_h)}.{to_safe(std_p)}.{to_safe(std_sig)}"def _sign_rsa_manual(self, content):"""[零依赖] 纯 Python 实现 RSA SHA256 签名 (s = m^d mod n)"""# (此处省略 ASN.1 解析 n, d 的代码...)# n, d = self._parse_pem(self.PRIVATE_KEY_STR)# 1. SHA256 摘要msg_hash = hashlib.sha256(content.encode()).digest()# 2. PKCS#1 v1.5 填充 (含 OID)oid = b'\x30\x31\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x01\x05\x00\x04\x20'# padding 逻辑...# 3. 核心数学运算m_int = int.from_bytes(padding, 'big')s_int = pow(m_int, d, n)return base64.b64encode(s_int.to_bytes(key_len, 'big')).decode()
pyjwt),当服务端抛出空指针异常时,极大概率是 Base64 编码方式或 JSON 结构不匹配。return_urlvs redirect_url)、URL 编码次数、KID Hash 规则,任何一个细节的偏差都会导致 SSO 链路断裂。