# 极光一键登录 — 服务端核验 + RSA 解密手机号（jiguang）

> 文件:`app/integrations/jiguang.py` | 关联接口:[auth-jverify-login](../api/auth-jverify-login.md) | [← 集成索引](./README.md)

## 作用
极光一键登录的服务端环节：拿客户端的 `loginToken` 调极光 REST 验真，取回 RSA 加密的手机号并解密成明文。

## 链路
```
Android SDK loginAuth → loginToken
  → 本服务 POST 极光 /v1/web/loginTokenVerify  (Basic Auth = AppKey:MasterSecret)
  → 极光返回 RSA 公钥加密的手机号 (base64)
  → 用配套 RSA 私钥解密
  → 返回明文手机号
```

## 函数 / 异常
- `verify_and_get_phone(login_token)` —— 对外入口，验证 + 解密一把梭。
- 失败统一抛 `JiguangError`，api 层 catch 后翻成 4xx/5xx（验证失败/解密失败 → `502`）。
- RSA 私钥**懒加载并缓存**（`_private_key_cache`）：文件缺失抛 `JiguangError`（让单请求失败 + 日志，而不是 import 时炸进程）。

## RSA 解密策略（关键）
极光文档没明说 padding。实测 PKCS1v15 套在 OAEP 密文上会"假成功"返回垃圾（`UnicodeDecodeError 0x8e`）。因此按 **OAEP-SHA1 → OAEP-SHA256 → PKCS1v15** 顺序尝试，谁能解出 **11 位纯数字手机号**就用谁；base64 先按 4 字节对齐补 `=`（各运营商可能返回不带 `=` 的 base64）。

## 配置
| 配置项 | 说明 |
|---|---|
| `JG_APP_KEY` / `JG_MASTER_SECRET` | 极光 AppKey / MasterSecret（拼 Basic Auth） |
| `JG_PRIVATE_KEY_PATH` | RSA 私钥 .pem 路径 |
| `JG_VERIFY_ENDPOINT` | 极光验证接口 URL（`/v1/web/loginTokenVerify`） |
| `JG_REQUEST_TIMEOUT_SEC` | 请求超时 |

## 踩坑
- **私钥必须与极光控制台为该 appkey 登记的公钥配对**，否则三种 padding 全失败、报 `all RSA paddings failed; last error: Decryption failed`。私钥由相关开发同学生成提供。
- **换私钥后必须重启后端**——`_private_key_cache` 会缓存旧 key。
- 极光验证响应 `code != 8000` 视为失败；缺 `phone` 字段也失败。
