keychain
iOS 钥匙串安全存储。
iOS 钥匙串:安全保存 token、密码、API Key 等敏感小字符串。
边界:只存敏感凭据,不存主题、筛选条件或大型 JSON(用 storage)。不要把 get_password() 的明文打印到日志或界面;需要展示时用「已配置 / 未配置」或掩码。
#模块概览
| 项 | 说明 |
|---|---|
| 导入 | import keychain |
| 适合做什么 | 登录 token、API Key、私密密码、多账号凭据 |
| 调用时机 | 保存/删除放在按钮回调;读取可在启动时,但不要在 body() 里反复读 |
| 推荐顺序 | 固定 service + account → set_password → 需要时 get_password → 退出时 delete_password |
| 定位键 | service 是命名空间(如 myapp.api),account 是用户名或环境名 |
#快速开始
下面脚本写入、读取、删除一条演示凭据:
已复制
import keychain
SERVICE = "demo-api"
ACCOUNT = "default"
ok = keychain.set_password(SERVICE, ACCOUNT, "secret-token")
print("保存:", ok)
token = keychain.get_password(SERVICE, ACCOUNT)
print("已配置:", bool(token)) # 不要 print(token) 明文
ok = keychain.delete_password(SERVICE, ACCOUNT)
print("删除:", ok)
for service, account in keychain.get_services():
print(service, account)
#AppUI 示例
用 SecureField 收集输入,保存后清空输入框;界面只显示状态,不展示明文 secret。
已复制
import appui
import keychain
SERVICE = "demo-api"
ACCOUNT = "default"
state = appui.State(
token="",
status="未检查",
message="输入 token 后点保存",
)
def update_token(value):
state.token = value
def save_token():
value = state.token.strip()
if not value:
state.message = "请输入 token"
return
ok = keychain.set_password(SERVICE, ACCOUNT, value)
state.batch_update(
token="",
status="已配置" if ok else "保存失败",
message="已保存到钥匙串" if ok else "保存失败,请重试",
)
def check_token():
token = keychain.get_password(SERVICE, ACCOUNT)
state.batch_update(
status="已配置" if token else "未配置",
message="凭据存在" if token else "尚未保存凭据",
)
def delete_token():
ok = keychain.delete_password(SERVICE, ACCOUNT)
state.batch_update(
status="已删除" if ok else "删除失败",
message="凭据已清除" if ok else "没有可删除的凭据",
)
def body():
return appui.NavigationStack(
appui.Form([
appui.Section("API Token", [
appui.SecureField("粘贴 token", text=state.token, on_change=update_token),
appui.Button("保存到钥匙串", action=save_token)
.button_style("bordered_prominent"),
appui.Button("检查是否已配置", action=check_token),
appui.Button("删除凭据", action=delete_token, role="destructive"),
]),
appui.Section("状态", [
appui.LabeledContent("配置", value=state.status),
appui.Text(state.message).foreground_color("secondaryLabel"),
]),
]).navigation_title("钥匙串")
)
appui.run(body, state=state)
#API 参考
#速查
| API | 作用 |
|---|---|
set_password(service, account, password) | 保存或更新 → bool |
get_password(service, account="") | 读取 → str 或 None |
delete_password(service, account="") | 删除 → bool |
get_services() | 列出本 App 已存的 (service, account) |
#读写凭据
条目由 service + account 两个字符串定位:
| 参数 | 说明 |
|---|---|
service | 稳定命名空间,如 openai、github、myapp.production |
account | 用户名、邮箱或环境名,如 default、prod |
password | 要保存的 secret 字符串 |
已复制
import keychain
keychain.set_password("myapp.api", "prod", "sk-xxx")
token = keychain.get_password("myapp.api", "prod")
if token:
# 用于请求头,不要打印到界面
headers = {"Authorization": f"Bearer {token}"}
set_password(...) — 成功返回 True,失败返回 False。
get_password(...) — 存在时返回字符串,不存在返回 None。判断用 if token:,不要假设空字符串。
delete_password(...) — 用户退出账号或切换环境时调用。
#列出凭据
get_services() — 返回 list[tuple],每项是 (service, account):
已复制
for service, account in keychain.get_services():
print(service, account)
用于设置页的「已保存账号」列表;仍不要直接展示密码明文。
#与 storage 的分工
| 数据类型 | 推荐模块 |
|---|---|
| 主题、筛选、小型 JSON | storage |
| token、密码、API Key | keychain |
| 大文件、图片 | 文件系统或媒体模块 |
#常见错误
| 错误写法 | 后果 | 修正 |
|---|---|---|
把 token 存进 storage | 敏感数据不安全 | 使用 keychain.set_password |
print(get_password(...)) | secret 进入日志 | 只打印 bool(token) 或掩码 |
每次随机 service 名 | 旧凭据读不到 | 使用固定命名空间 |
在 body() 里读并显示明文 | 刷新时泄露 secret | 只显示「已配置 / 未配置」 |
#相关文档
#预期效果
运行示例后,界面应出现文档描述的目标结果;若与预期不符,先看「失败路径」并按返回值或日志排查。