认证
Quick Reference
What通过 AWS Cognito 进行 OAuth 2.0(开放授权)认证
Why获取 Bearer Token(持有者令牌)以授权每个 API 请求——包含生产级缓存方案
Reading Time5 分钟
Difficulty初级
Prerequisites简介
为什么令牌认证很重要
| 缺乏规范的认证管理 | 采用 A55 最佳实践 |
|---|---|
| 每次 API 调用都请求新令牌——速度慢且受频率限制 | 缓存并复用令牌,有效期长达 1 小时 |
| 凭证散落在代码、配置和日志中 | 统一存储在密钥管理器中,零泄露风险 |
| 令牌过期导致生产环境静默 401 错误 | 过期前主动刷新,确保 100% 可用性 |
| 调试认证问题浪费大量集成时间 | 一个流程、一个端点、一种令牌格式——全程 JWT(JSON Web Token) |
工作原理
用 client_id + client_secret 换取 JWT 格式的 access_token(有效期 1 小时)。在每次 API(应用程序编程接口)调用时以 Authorization: Bearer <token> 形式发送该令牌。在过期前刷新。
认证流程
第 1 步——请求访问令牌
| 配置项 | 值 |
|---|---|
| 端点 | https://smart-capital.auth.us-east-1.amazoncognito.com/oauth2/token |
| 方法 | POST |
| Content-Type | application/x-www-form-urlencoded |
| 字段 | 值 |
|---|---|
grant_type | client_credentials |
client_id | 您的 OAuth 2.0 客户端 ID |
client_secret | 您的 OAuth 2.0 客户端密钥 |
- cURL
- Python
- JavaScript
curl -s -X POST \
https://smart-capital.auth.us-east-1.amazoncognito.com/oauth2/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=client_credentials" \
-d "client_id=YOUR_CLIENT_ID" \
-d "client_secret=YOUR_CLIENT_SECRET"
import requests
def get_access_token():
url = "https://smart-capital.auth.us-east-1.amazoncognito.com/oauth2/token"
payload = {
"grant_type": "client_credentials",
"client_id": "YOUR_CLIENT_ID",
"client_secret": "YOUR_CLIENT_SECRET",
}
headers = {"Content-Type": "application/x-www-form-urlencoded"}
response = requests.post(url, data=payload, headers=headers)
response.raise_for_status()
return response.json()["access_token"]
async function getAccessToken() {
const response = await fetch(
"https://smart-capital.auth.us-east-1.amazoncognito.com/oauth2/token",
{
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: new URLSearchParams({
grant_type: "client_credentials",
client_id: "YOUR_CLIENT_ID",
client_secret: "YOUR_CLIENT_SECRET",
}),
}
);
const data = await response.json();
return data.access_token;
}
第 2 步——理解令牌响应
{
"access_token": "eyJraWQiOiJLTzZ...",
"expires_in": 3600,
"token_type": "Bearer"
}
| 字段 | 类型 | 描述 |
|---|---|---|
access_token | string | 作为 Bearer 令牌在每个 API 请求中使用的 JWT |
expires_in | integer | 令牌有效期(秒),默认 3600 = 1 小时 |
token_type | string | 固定为 Bearer |
解码 JWT
开发阶段可将 access_token 粘贴到 jwt.io 查看声明、作用域和过期时间。切勿在不受信任的网站上操作生产令牌。
第 3 步——调用 API
在每个请求中发送 Authorization: Bearer <access_token>。
- cURL
- Python
- JavaScript
curl -s -X GET \
https://core-manager.a55.tech/api/v1/wallets \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json"
def call_api(access_token: str, method: str, endpoint: str, payload=None):
headers = {
"Authorization": f"Bearer {access_token}",
"Content-Type": "application/json",
}
url = f"https://core-manager.a55.tech/api/v1/{endpoint}"
response = requests.request(method, url, json=payload, headers=headers)
response.raise_for_status()
return response.json()
async function callApi(accessToken, method, endpoint, payload) {
const response = await fetch(
`https://core-manager.a55.tech/api/v1/${endpoint}`,
{
method,
headers: {
Authorization: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
body: payload ? JSON.stringify(payload) : undefined,
}
);
return response.json();
}
第 4 步——缓存和刷新令牌
令牌有效期为 1 小时。请在过期之前请求新令牌——切勿等到收到 401 错误。
- Python
- JavaScript
import time
import requests
class A55Client:
AUTH_URL = "https://smart-capital.auth.us-east-1.amazoncognito.com/oauth2/token"
API_BASE = "https://core-manager.a55.tech/api/v1"
REFRESH_MARGIN = 300 # 过期前 5 分钟刷新
def __init__(self, client_id: str, client_secret: str):
self._client_id = client_id
self._client_secret = client_secret
self._token = None
self._token_expiry = 0
def _refresh_token(self):
resp = requests.post(self.AUTH_URL, data={
"grant_type": "client_credentials",
"client_id": self._client_id,
"client_secret": self._client_secret,
}, headers={"Content-Type": "application/x-www-form-urlencoded"})
resp.raise_for_status()
data = resp.json()
self._token = data["access_token"]
self._token_expiry = time.time() + data["expires_in"] - self.REFRESH_MARGIN
@property
def token(self) -> str:
if not self._token or time.time() >= self._token_expiry:
self._refresh_token()
return self._token
def request(self, method: str, endpoint: str, **kwargs):
headers = {
"Authorization": f"Bearer {self.token}",
"Content-Type": "application/json",
}
resp = requests.request(method, f"{self.API_BASE}/{endpoint}", headers=headers, **kwargs)
resp.raise_for_status()
return resp.json()
client = A55Client("YOUR_CLIENT_ID", "YOUR_CLIENT_SECRET")
wallets = client.request("GET", "wallets")
print(wallets)
class A55Client {
#token = null;
#tokenExpiry = 0;
static AUTH_URL = "https://smart-capital.auth.us-east-1.amazoncognito.com/oauth2/token";
static API_BASE = "https://core-manager.a55.tech/api/v1";
static REFRESH_MARGIN = 300_000; // 5 分钟(毫秒)
constructor(clientId, clientSecret) {
this.clientId = clientId;
this.clientSecret = clientSecret;
}
async #refreshToken() {
const resp = await fetch(A55Client.AUTH_URL, {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: new URLSearchParams({
grant_type: "client_credentials",
client_id: this.clientId,
client_secret: this.clientSecret,
}),
});
const data = await resp.json();
this.#token = data.access_token;
this.#tokenExpiry = Date.now() + data.expires_in * 1000 - A55Client.REFRESH_MARGIN;
}
async getToken() {
if (!this.#token || Date.now() >= this.#tokenExpiry) {
await this.#refreshToken();
}
return this.#token;
}
async request(method, endpoint, payload) {
const token = await this.getToken();
const resp = await fetch(`${A55Client.API_BASE}/${endpoint}`, {
method,
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
},
body: payload ? JSON.stringify(payload) : undefined,
});
return resp.json();
}
}
const client = new A55Client("YOUR_CLIENT_ID", "YOUR_CLIENT_SECRET");
const wallets = await client.request("GET", "wallets");
console.log(wallets);
令牌管理检查清单
| 最佳实践 | 原因 |
|---|---|
| 缓存令牌最长 55 分钟 | 避免不必要的 Cognito 调用和频率限制 |
在 expires_in 到期前 5 分钟刷新 | 防止 API 调用过程中出现 401 错误 |
将 client_secret 存储在密钥管理器中 | 环境变量可能泄露在日志中;密钥管理器不会 |
| 切勿记录令牌或密钥 | 泄露的令牌可在 1 小时内获得完整 API 访问权限 |
| 全程使用 HTTPS | 明文 HTTP 中的令牌可能被拦截 |
| 定期轮换密钥 | 限制凭证泄露后的影响范围 |
client secret 是密码
将 client_secret 视为数据库密码:仅存储在后端,切勿放入浏览器、移动应用、CI 日志或公开仓库中。如果发生泄露,请立即发送邮件至 tech.services@a55.tech 进行轮换。
错误响应
| HTTP 状态码 | 含义 | 处理方式 |
|---|---|---|
401 Unauthorized | 令牌已过期或无效 | 刷新令牌并重试 |
400 Bad Request | 认证请求格式错误 | 检查 grant_type、client_id、client_secret |
429 Too Many Requests | 触发 Cognito 频率限制 | 实现指数退避策略;缓存您的令牌 |
申请凭证
发送邮件至**tech.services@a55.tech**,附上您的公司名称、技术联系人和预期使用场景,即可获取沙箱和生产环境的 client_id 和 client_secret。