PythonIDE Docs
中文
简体中文

安全凭据库

Keychain 存储敏感字段,读取前使用系统验证。

演示 keychain 存储敏感字段,读取前用 biometric.authenticate_with_passcode 校验。

#预期效果

运行后会出现安全凭据库,敏感字段保存到 Keychain,读取前会先走系统验证。

#完整示例

python
import appui
import biometric
import haptics
import keychain

state = appui.State(
    service="pythonide.demo",
    account="default",
    secret="",
    revealed="",
    status="未保存",
    auth_type="",
)


def update_service(value):
    state.service = value


def update_account(value):
    state.account = value


def update_secret(value):
    state.secret = value


def refresh_auth_type():
    state.auth_type = biometric.biometric_type()


def save_secret():
    if not state.service.strip() or not state.account.strip() or not state.secret:
        state.status = "请填写服务名、账号和 Token"
        haptics.notification("warning")
        return
    keychain.set_password(state.service.strip(), state.account.strip(), state.secret)
    state.batch_update(status="已保存到 Keychain", secret="", revealed="")
    haptics.notification("success")


def reveal_secret():
    result = biometric.authenticate_with_passcode("验证身份以读取 Keychain 凭据")
    if not result.get("success"):
        state.status = f"验证失败:{result.get('error', 'canceled')}"
        haptics.notification("error")
        return
    value = keychain.get_password(state.service.strip(), state.account.strip())
    if value is None:
        state.batch_update(status="未找到对应凭据", revealed="")
        haptics.notification("warning")
        return
    state.batch_update(status="读取成功", revealed=value)
    haptics.notification("success")


def delete_secret():
    keychain.delete_password(state.service.strip(), state.account.strip())
    state.batch_update(status="已删除", secret="", revealed="")
    haptics.notification("success")


def body():
    return appui.NavigationStack(
        appui.Form(
            [
                appui.Section(
                    [
                        appui.LabeledContent("当前验证方式", value=state.auth_type or "未知"),
                        appui.Button("刷新验证能力", action=refresh_auth_type),
                    ],
                    header="设备验证",
                    footer="读取敏感凭据前使用系统验证;不要自己实现密码弹窗替代系统验证。",
                ),
                appui.Section(
                    [
                        appui.TextField("服务名", text=state.service, on_change=update_service),
                        appui.TextField("账号", text=state.account, on_change=update_account),
                        appui.SecureField("Token 或密码", text=state.secret, on_change=update_secret),
                    ],
                    header="凭据",
                ),
                appui.Section(
                    [
                        appui.Button("保存到 Keychain", action=save_secret)
                            .button_style("bordered_prominent"),
                        appui.Button("验证并读取", action=reveal_secret),
                        appui.Button("删除", action=delete_secret).foreground_color("systemRed"),
                    ],
                    header="操作",
                ),
                appui.Section(
                    [
                        appui.LabeledContent("状态", value=state.status),
                        appui.Text(state.revealed or "验证后显示读取结果")
                            .font("callout")
                            .foreground_color("secondaryLabel"),
                    ],
                    header="结果",
                ),
            ]
        ).navigation_title("凭据库")
    )


appui.run(body, state=state, presentation="sheet")

#关键技巧

  • 敏感字符串进 keychain,不要写入 storage
  • 写入/读取/删除放在按钮动作里;get_password 返回 None 时要能恢复 UI 状态。
  • 录入用 SecureField;展示前走系统生物识别/密码验证。