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.
@@ -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)