# DataHub SDK 认证机制

本文档说明 DataHub SDK 的认证机制，包括 API Key 认证和 MetaInfo JWT 签名认证。

## 认证方式概述

DataHub SDK 支持两种认证方式：

1. **API Key 认证** - 使用 Bearer Token 进行简单认证
2. **MetaInfo JWT 认证** - 使用 Ed25519 非对称加密签名的 JWT Token 传递元信息

两种认证方式可以同时使用，也可以单独使用，依据 DataHub 的具体需求和安全策略。

---

## 1. API Key 认证

### 1.1 获取 API Key

API Key 由 DataHub 服务端签发，获取方式：

1. 联系 DataHub 管理员申请 API Key
2. 管理员在 DataHub 后台为您的应用/用户生成唯一的 API Key
3. 将 API Key 安全地保存在配置文件或环境变量中

### 1.2 使用方式

**在 SDK 中配置：**

```python
from hezor_common.transfer.datahub_sdk import DatahubSDK

async with DatahubSDK(api_key="your-api-key-here") as sdk:
    # 使用 SDK
    result = await sdk.make_call("query_name")
```

**通过环境变量配置：**

```bash
# .env 文件
DATAHUB_API_KEY=your-api-key-here
```

```python
# 代码中使用
async with DatahubSDK(api_key=os.getenv("DATAHUB_API_KEY")) as sdk:
    pass
```

### 1.3 传输方式

SDK 在发送 HTTP 请求时，会在请求头中添加：

```http
Authorization: Bearer your-api-key-here
```

### 1.4 服务端验证

DataHub 服务端接收到请求后：

1. 提取请求头中的 `Authorization` 字段
2. 验证格式是否为 `Bearer <token>`
3. 查询数据库，验证 API Key 是否有效
4. 检查 API Key 的权限和有效期
5. 验证通过后处理请求

---

## 2. MetaInfo JWT 认证

### 2.1 认证流程概述

MetaInfo JWT 认证使用 **Ed25519 非对称加密算法**：

```
客户端                                  DataHub 服务端
   |                                         |
   | 1. 生成 Ed25519 密钥对                    |
   |    (private_key, public_key)            |
   |                                         |
   | 2. 向 DataHub 注册 public_key            |
   |-------------------------------------->  |
   |                                         | 3. 存储 public_key
   |                                         |
   | 4. 使用 private_key 签名 MetaInfo        |
   |    生成 JWT Token                        |
   |                                         |
   | 5. 发送请求，携带 JWT Token               |
   |-------------------------------------->  |
   |                                         | 6. 使用 public_key 验证签名
   |                                         | 7. 解析 MetaInfo 数据
   |                                         | 8. 处理请求
```

### 2.2 生成密钥对

使用 `hezor_common.security` 模块生成 Ed25519 密钥对：

```python
from hezor_common.security import (
    generate_key_pair,
    serialize_private_key,
    serialize_public_key,
)

# 生成密钥对
private_key, public_key = generate_key_pair()

# 序列化为 PEM 格式
private_pem = serialize_private_key(private_key, password=b"your_password")
public_pem = serialize_public_key(public_key)

# 保存到文件
with open("private_key.pem", "wb") as f:
    f.write(private_pem)

with open("public_key.pem", "wb") as f:
    f.write(public_pem)
```

**推荐做法：**
- 使用密码保护私钥文件
- 私钥文件不要上传到版本控制系统
- 定期轮换密钥对

### 2.3 向 DataHub 注册公钥

将生成的 **公钥 (public_key.pem)** 提交给 DataHub 管理员：

1. 将 `public_key.pem` 文件内容发送给管理员
2. 管理员在 DataHub 后台录入您的公钥
3. DataHub 会将公钥与您的 `caller_id` 关联存储

**注意：** 只需提交公钥，私钥必须自己保管。

### 2.4 MetaInfo 数据结构

MetaInfo 包含以下字段：

```python
from hezor_common.transfer.datahub_sdk.base.meta_info import MetaInfo

meta = MetaInfo(
    subject="主体名称",              # 数据主体，如"鮨大山"
    subject_code="主体编码",         # 唯一编码，如"wdyl_001"
    caller_id="调用者ID",            # 您的唯一标识，用于匹配公钥
    data_coverage="202401-202412",  # 数据覆盖周期
    creation_slug="报告类型slug",    # 如"single_store_profit_model"
    creation_name="报告类型名称",     # 如"单店盈利模型"
)
```

### 2.5 JWT Token 生成过程

**步骤 1：准备 Payload**

MetaInfo 对象会被转换为字典：

```python
payload = {
    "subject": "鮨大山",
    "subject_code": "wdyl_001",
    "caller_id": "user_123",
    "data_coverage": "202401-202412",
    "creation_slug": "single_store_profit_model",
    "creation_name": "单店盈利模型",
    "exp": 1706112000  # 过期时间戳 (自动添加)
}
```

**步骤 2：使用私钥签名**

SDK 内部调用：

```python
from hezor_common.security.jwt import encode

token = encode(
    private_key_path="private_key.pem",
    payload=payload,
    password=b"your_password",
    expires_in=3600  # 1小时过期
)
```

生成的 JWT Token 格式：

```
eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJzdWJqZWN0Ijoi6ats5aSn5bGxIiwic3ViamVjdF9jb2RlIjoid2R5bF8wMDEiLCJjYWxsZXJfaWQiOiJ1c2VyXzEyMyIsImRhdGFfY292ZXJhZ2UiOiIyMDI0MDEtMjAyNDEyIiwiY3JlYXRpb25fc2x1ZyI6InNpbmdsZV9zdG9yZV9wcm9maXRfbW9kZWwiLCJjcmVhdGlvbl9uYW1lIjoi5Y2V5bqX55uI5Yip5qih5Z6LIiwiZXhwIjoxNzA2MTEyMDAwfQ.signature_here
```

JWT Token 由三部分组成（用 `.` 分隔）：
1. **Header** - 加密算法信息 (`{"alg": "EdDSA", "typ": "JWT"}`)
2. **Payload** - MetaInfo 数据（Base64 编码）
3. **Signature** - Ed25519 签名（使用私钥生成）

**步骤 3：添加到请求头**

```http
X-META-INFO: eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9...
```

### 2.6 SDK 使用示例

```python
from hezor_common.transfer.datahub_sdk import DatahubSDK
from hezor_common.transfer.datahub_sdk.base.meta_info import MetaInfo

# 准备 MetaInfo
meta = MetaInfo(
    subject="鮨大山",
    subject_code="wdyl_001",
    caller_id="user_123",
    data_coverage="202401-202412",
    creation_slug="single_store_profit_model",
    creation_name="单店盈利模型",
)

# 使用 MetaInfo 和私钥进行认证
async with DatahubSDK(
    api_key="your-api-key",              # API Key 认证
    meta_info=meta,                      # MetaInfo 数据
    private_key_path="private_key.pem",  # 私钥路径
    password=b"your_password",           # 私钥密码
    meta_info_expires_in=3600,           # JWT 有效期 (秒)
) as sdk:
    result = await sdk.make_call("query_name")
```

### 2.7 服务端验证流程

DataHub 服务端接收到请求后：

**步骤 1：提取 JWT Token**

```python
jwt_token = request.headers.get("X-META-INFO")
if not jwt_token:
    return {"error": "Missing X-META-INFO header"}
```

**步骤 2：验证签名并解析 Payload**

使用预先配置的公钥验证 JWT 签名：

```python
from hezor_common.security.jwt import decode

try:
    # 验证签名并解析 payload
    # 公钥可以是预配置的文件路径或从数据库加载的公钥对象
    verified_payload = decode(
        public_key_path="public_key.pem",  # 或使用公钥对象
        token=jwt_token,
        verify=True  # 启用签名验证
    )
    
    # 验证通过，提取 MetaInfo
    subject = verified_payload["subject"]
    subject_code = verified_payload["subject_code"]
    caller_id = verified_payload["caller_id"]
    data_coverage = verified_payload["data_coverage"]
    creation_slug = verified_payload["creation_slug"]
    creation_name = verified_payload["creation_name"]
    
except jwt.ExpiredSignatureError:
    # Token 已过期
    return {"error": "JWT token has expired"}
    
except jwt.InvalidTokenError as e:
    # 签名验证失败或格式错误
    return {"error": f"Invalid JWT token: {str(e)}"}
```

**步骤 3：权限验证**

验证 `caller_id` 是否有权限访问 `subject_code` 的数据：

```python
if not has_permission(caller_id, subject_code):
    return {"error": "Permission denied"}
```

**步骤 4：处理请求**

使用 MetaInfo 中的信息处理业务逻辑。

---

## 3. 安全最佳实践

### 3.1 私钥保护

- ✅ **使用密码保护私钥文件**
- ✅ **私钥文件权限设置为 600** (`chmod 600 private_key.pem`)
- ✅ **不要将私钥提交到版本控制系统** (添加到 `.gitignore`)
- ✅ **使用环境变量或密钥管理服务存储密码**
- ❌ **不要在代码中硬编码私钥或密码**

### 3.2 API Key 保护

- ✅ **使用环境变量存储 API Key**
- ✅ **定期轮换 API Key**
- ✅ **使用 HTTPS 传输**
- ❌ **不要在日志中打印完整的 API Key**

### 3.3 JWT Token 安全

- ✅ **设置合理的过期时间** (推荐 1 小时)
- ✅ **服务端验证 `exp` 字段**
- ✅ **服务端验证 `caller_id` 权限**
- ❌ **不要在 payload 中包含敏感信息** (JWT 可被解码)

### 3.4 传输安全

- ✅ **使用 HTTPS 加密传输**
- ✅ **验证服务端证书**
- ✅ **使用内网环境时配置 NO_PROXY**

---

## 4. 故障排查

### 4.1 API Key 认证失败

**错误信息：** `401 Unauthorized` 或 `Invalid API Key`

**排查步骤：**
1. 检查 API Key 是否正确
2. 确认 API Key 是否已过期
3. 确认请求头格式：`Authorization: Bearer <api_key>`
4. 联系管理员确认 API Key 状态

### 4.2 JWT 签名验证失败

**错误信息：** `Invalid JWT signature` 或 `Signature verification failed`

**排查步骤：**
1. 确认使用的私钥与注册的公钥是匹配的密钥对
2. 检查私钥密码是否正确
3. 确认 `caller_id` 在 DataHub 后台已正确注册
4. 检查 JWT Token 是否已过期 (`exp` 字段)
5. 确认私钥文件格式正确（PEM 格式）

### 4.3 MetaInfo 解析失败

**错误信息：** `Invalid MetaInfo` 或 `Missing required field`

**排查步骤：**
1. 检查 MetaInfo 所有必需字段是否已填写
2. 确认字段值格式是否正确（如 `data_coverage` 格式）
3. 检查 SDK 日志，查看实际发送的 payload

### 4.4 权限错误

**错误信息：** `Permission denied` 或 `Access forbidden`

**排查步骤：**
1. 确认 `caller_id` 有权限访问指定的 `subject_code`
2. 联系 DataHub 管理员配置权限
3. 检查 MetaInfo 中的 `subject_code` 是否正确

---

## 5. 代码示例

### 5.1 完整的认证示例

```python
import asyncio
from pathlib import Path
from hezor_common.transfer.datahub_sdk import DatahubSDK
from hezor_common.transfer.datahub_sdk.base.meta_info import MetaInfo

async def main():
    # 准备 MetaInfo
    meta = MetaInfo(
        subject="鮨大山",
        subject_code="wdyl_001",
        caller_id="user_123",
        data_coverage="202401-202412",
        creation_slug="single_store_profit_model",
        creation_name="单店盈利模型",
    )
    
    # 完整认证配置
    async with DatahubSDK(
        base_url="http://10.8.98.9:12580",
        api_key="your-api-key",
        meta_info=meta,
        private_key_path="secrets/private_key.pem",
        password=b"your_password",
        meta_info_expires_in=3600,
    ) as sdk:
        # 执行查询
        result = await sdk.make_call(
            query="get_brands_categories",
            context="获取品牌和品类信息"
        )
        
        print(f"结果: {result}")

if __name__ == "__main__":
    asyncio.run(main())
```

### 5.2 仅使用 API Key

```python
async with DatahubSDK(api_key="your-api-key") as sdk:
    result = await sdk.make_call("query_name")
```

### 5.3 仅使用 MetaInfo JWT

```python
async with DatahubSDK(
    meta_info=meta,
    private_key_path="private_key.pem",
    password=b"password",
) as sdk:
    result = await sdk.make_call("query_name")
```

---

## 6. 相关模块

- `hezor_common.security.signature` - Ed25519 密钥对生成和序列化
- `hezor_common.security.jwt` - JWT 编码和解码
- `hezor_common.transfer.datahub_sdk.base.meta_info` - MetaInfo 数据模型
- `hezor_common.transfer.datahub_sdk.base.data_api_client` - HTTP 客户端实现

---

## 7. 参考资料

- [RFC 8032 - Edwards-Curve Digital Signature Algorithm (EdDSA)](https://tools.ietf.org/html/rfc8032)
- [RFC 7519 - JSON Web Token (JWT)](https://tools.ietf.org/html/rfc7519)
- [PyJWT Documentation](https://pyjwt.readthedocs.io/)
- [Cryptography Library Documentation](https://cryptography.io/)
