app_store_dev_api 0.3.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.DS_Store +0 -0
- data/.claude/settings.local.json +2 -19
- data/Gemfile +5 -0
- data/Gemfile.lock +6 -0
- data/docs/bundle_id_capabilities.md +382 -0
- data/docs/bundle_ids.md +417 -0
- data/docs/creating_api_keys.md +137 -0
- data/docs/generating_tokens.md +189 -0
- data/docs/openapi.oas4.3.json +230597 -0
- data/docs/pindo_usage_reference.md +234 -0
- data/docs/revoking_api_keys.md +118 -0
- data/install_local.sh +73 -62
- data/lib/.DS_Store +0 -0
- data/lib/app_store_dev_api/client/builder.rb +1 -1
- data/lib/app_store_dev_api/client/options.rb +1 -1
- data/lib/app_store_dev_api/client.rb +1 -1
- data/lib/app_store_dev_api/version.rb +1 -1
- data/lib/config/{schema_v4.2.json → schema_v4.3.json} +0 -10
- data/push_all.sh +6 -0
- data/release_remote.sh +27 -8
- data/scripts/comprehensive_validation.rb +1 -1
- data/scripts/final_validation_report.rb +1 -1
- data/test_library.rb +160 -0
- metadata +13 -4
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
# 生成 API 请求 Token
|
|
2
|
+
|
|
3
|
+
> 官方文档: https://developer.apple.com/documentation/appstoreconnectapi/generating-tokens-for-api-requests
|
|
4
|
+
|
|
5
|
+
## 概述
|
|
6
|
+
|
|
7
|
+
App Store Connect API 使用 **JSON Web Token (JWT)** 进行身份认证。每次 API 请求都需要在 HTTP 请求头中携带有效的 JWT Token。Token 使用 ES256(ECDSA P-256)算法签名,有效期最长 20 分钟。
|
|
8
|
+
|
|
9
|
+
## JWT Token 结构
|
|
10
|
+
|
|
11
|
+
JWT Token 由三部分组成,以 `.` 分隔:
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
{Header}.{Payload}.{Signature}
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
### 1. Header(头部)
|
|
18
|
+
|
|
19
|
+
```json
|
|
20
|
+
{
|
|
21
|
+
"alg": "ES256",
|
|
22
|
+
"kid": "2X9R4HXF34",
|
|
23
|
+
"typ": "JWT"
|
|
24
|
+
}
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
| 字段 | 类型 | 必填 | 说明 |
|
|
28
|
+
|------|------|------|------|
|
|
29
|
+
| `alg` | string | 是 | 签名算法,固定为 `ES256` |
|
|
30
|
+
| `kid` | string | 是 | API 密钥 ID(Key ID) |
|
|
31
|
+
| `typ` | string | 是 | Token 类型,固定为 `JWT` |
|
|
32
|
+
|
|
33
|
+
### 2. Payload(载荷)
|
|
34
|
+
|
|
35
|
+
```json
|
|
36
|
+
{
|
|
37
|
+
"iss": "57246542-96fe-1a63-e053-0824d011072a",
|
|
38
|
+
"iat": 1711416000,
|
|
39
|
+
"exp": 1711417200,
|
|
40
|
+
"aud": "appstoreconnect-v1"
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
| 字段 | 类型 | 必填 | 说明 |
|
|
45
|
+
|------|------|------|------|
|
|
46
|
+
| `iss` | string | 是 | Issuer ID - 你的团队发行者 ID |
|
|
47
|
+
| `iat` | number | 是 | Issued At - Token 签发时间(Unix 时间戳,秒) |
|
|
48
|
+
| `exp` | number | 是 | Expiration - Token 过期时间(Unix 时间戳,秒) |
|
|
49
|
+
| `aud` | string | 是 | Audience - 固定为 `appstoreconnect-v1` |
|
|
50
|
+
| `scope` | array | 否 | 权限范围(仅个人密钥需要指定) |
|
|
51
|
+
|
|
52
|
+
### 3. Signature(签名)
|
|
53
|
+
|
|
54
|
+
使用 API 私钥(`.p8` 文件)以 ES256 算法对 `{Header}.{Payload}` 进行签名。
|
|
55
|
+
|
|
56
|
+
## Token 有效期规则
|
|
57
|
+
|
|
58
|
+
- **最长有效期**: 20 分钟(1200 秒)
|
|
59
|
+
- `exp` 不得超过 `iat + 1200`
|
|
60
|
+
- 过期后的 Token 会被 API 拒绝,返回 `401 Unauthorized`
|
|
61
|
+
- 建议在 Token 过期前主动刷新
|
|
62
|
+
|
|
63
|
+
## 请求头格式
|
|
64
|
+
|
|
65
|
+
```http
|
|
66
|
+
Authorization: Bearer eyJhbGciOiJFUzI1NiIsImtpZCI6IjJYOVI0SFhGMzQiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiI1NzI0NjU0Mi05NmZlLTFhNjMtZTA1My0wODI0ZDAxMTA3MmEiLCJpYXQiOjE3MTE0MTYwMDAsImV4cCI6MTcxMTQxNzIwMCwiYXVkIjoiYXBwc3RvcmVjb25uZWN0LXYxIn0.{signature}
|
|
67
|
+
Content-Type: application/json
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## 本项目的实现
|
|
71
|
+
|
|
72
|
+
`app_store_dev_api` gem 内置了完整的 JWT Token 生成机制,无需手动处理。
|
|
73
|
+
|
|
74
|
+
### 核心实现 (`Client::Authorization`)
|
|
75
|
+
|
|
76
|
+
```ruby
|
|
77
|
+
# lib/app_store_dev_api/client/authorization.rb
|
|
78
|
+
|
|
79
|
+
module AppStoreDevApi
|
|
80
|
+
class Client
|
|
81
|
+
class Authorization
|
|
82
|
+
AUDIENCE = 'appstoreconnect-v1'
|
|
83
|
+
ALGORITHM = 'ES256'
|
|
84
|
+
|
|
85
|
+
def initialize(options)
|
|
86
|
+
@key_id = options.fetch(:key_id)
|
|
87
|
+
@issuer_id = options.fetch(:issuer_id)
|
|
88
|
+
@private_key = OpenSSL::PKey.read(options.fetch(:private_key))
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def payload
|
|
92
|
+
current_time = Time.now.to_i
|
|
93
|
+
{
|
|
94
|
+
iss: @issuer_id, # Issuer ID
|
|
95
|
+
iat: current_time, # 签发时间
|
|
96
|
+
exp: current_time + 20 * 60, # 20 分钟后过期
|
|
97
|
+
aud: AUDIENCE # appstoreconnect-v1
|
|
98
|
+
}
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def header_fields
|
|
102
|
+
{
|
|
103
|
+
kid: @key_id, # API Key ID
|
|
104
|
+
typ: 'JWT'
|
|
105
|
+
}
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def token
|
|
109
|
+
JWT.encode(payload, @private_key, ALGORITHM, header_fields)
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### 请求认证流程
|
|
117
|
+
|
|
118
|
+
```ruby
|
|
119
|
+
# lib/app_store_dev_api/base.rb
|
|
120
|
+
|
|
121
|
+
def headers
|
|
122
|
+
{
|
|
123
|
+
'Authorization' => "Bearer #{@authorization.token}",
|
|
124
|
+
'Content-Type' => 'application/json'
|
|
125
|
+
}
|
|
126
|
+
end
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
每次 API 请求时,gem 会自动生成新的 JWT Token 并附加到请求头中。
|
|
130
|
+
|
|
131
|
+
## 其他语言实现参考
|
|
132
|
+
|
|
133
|
+
### Python
|
|
134
|
+
|
|
135
|
+
```python
|
|
136
|
+
import jwt
|
|
137
|
+
import time
|
|
138
|
+
|
|
139
|
+
# 读取私钥
|
|
140
|
+
with open('AuthKey_2X9R4HXF34.p8', 'r') as f:
|
|
141
|
+
private_key = f.read()
|
|
142
|
+
|
|
143
|
+
# 生成 Token
|
|
144
|
+
header = {
|
|
145
|
+
"alg": "ES256",
|
|
146
|
+
"kid": "2X9R4HXF34",
|
|
147
|
+
"typ": "JWT"
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
payload = {
|
|
151
|
+
"iss": "57246542-96fe-1a63-e053-0824d011072a",
|
|
152
|
+
"iat": int(time.time()),
|
|
153
|
+
"exp": int(time.time()) + 20 * 60,
|
|
154
|
+
"aud": "appstoreconnect-v1"
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
token = jwt.encode(payload, private_key, algorithm="ES256", headers=header)
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### cURL
|
|
161
|
+
|
|
162
|
+
```bash
|
|
163
|
+
# 假设 TOKEN 变量中已有生成的 JWT Token
|
|
164
|
+
curl -H "Authorization: Bearer $TOKEN" \
|
|
165
|
+
-H "Content-Type: application/json" \
|
|
166
|
+
https://api.appstoreconnect.apple.com/v1/apps
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
## 常见错误
|
|
170
|
+
|
|
171
|
+
| 错误 | 原因 | 解决方案 |
|
|
172
|
+
|------|------|----------|
|
|
173
|
+
| `401 NOT_AUTHORIZED` | Token 过期或无效 | 检查 Token 是否过期,重新生成 |
|
|
174
|
+
| `401 NOT_AUTHORIZED` | 私钥不匹配 | 确认使用了正确的 `.p8` 私钥文件 |
|
|
175
|
+
| `401 NOT_AUTHORIZED` | Issuer ID 错误 | 在 App Store Connect 中核实 Issuer ID |
|
|
176
|
+
| `403 FORBIDDEN` | API 密钥权限不足 | 确认密钥角色拥有对应 API 的访问权限 |
|
|
177
|
+
| `429 RATE_LIMIT_EXCEEDED` | 请求频率过高 | 降低请求频率,实施退避策略 |
|
|
178
|
+
|
|
179
|
+
## 最佳实践
|
|
180
|
+
|
|
181
|
+
1. **Token 复用** - 在有效期内复用同一 Token,避免每次请求都生成新 Token
|
|
182
|
+
2. **提前刷新** - 在 Token 过期前 1-2 分钟主动刷新,避免请求失败
|
|
183
|
+
3. **安全存储** - 不要在日志、URL 参数或前端代码中暴露 Token
|
|
184
|
+
4. **错误处理** - 收到 `401` 响应时自动刷新 Token 并重试
|
|
185
|
+
|
|
186
|
+
## 相关文档
|
|
187
|
+
|
|
188
|
+
- [创建 API 密钥](creating_api_keys.md)
|
|
189
|
+
- [撤销 API 密钥](revoking_api_keys.md)
|