React Native KeyChain完整封装

一、完整封装代码

javascript 复制代码
/**
 * @author Dragon Wu
 * @created 2026/01/20 14:04
 * @description KeyChain的封装
 */

import APP_ENV from '@/config/env';
import * as Keychain from 'react-native-keychain';

// === 安全存储函数(Keychain基础加密)===
export const saveSecure = async (
  key: string,
  value: unknown,
  options?: Keychain.SetOptions
): Promise<boolean> => {
  const data = typeof value === 'string' ? value : JSON.stringify(value);
  const result = await Keychain.setGenericPassword(key, data, {
    service: APP_ENV.keychainService,
    accessible: Keychain.ACCESSIBLE.WHEN_UNLOCKED,
    ...(options ?? {})
  });
  return !!result;
};

// 加载普通安全存储的数据
export const loadSecure = async <T = unknown>(key: string): Promise<T | null> => {
  const creds = await Keychain.getGenericPassword({ service: APP_ENV.keychainService });
  if (!creds || creds.username !== key) return null;

  try {
    return JSON.parse(creds.password);
  } catch {
    return creds.password as T;
  }
};

export const removeSecure = async (key: string): Promise<boolean> => {
  return await Keychain.resetGenericPassword({ service: key });
};

// === 生物识别存储函数(最高安全级别)===
export const saveWithBiometry = async (
  key: string,
  value: unknown,
  options?: Keychain.SetOptions
): Promise<boolean> => {
  const data = typeof value === 'string' ? value : JSON.stringify(value);
  const result = await Keychain.setGenericPassword(key, data, {
    service: APP_ENV.keychainService,
    accessible: Keychain.ACCESSIBLE.WHEN_UNLOCKED,
    accessControl: Keychain.ACCESS_CONTROL.BIOMETRY_ANY,
    ...(options ?? {})
  });
  return result !== false;
};

// 加载生物识别存储的数据
export const loadWithBiometry = async <T = unknown>(
  key: string,
  promptOptions?: Keychain.AuthenticationPrompt
): Promise<T | null> => {
  const creds = await Keychain.getGenericPassword({
    service: APP_ENV.keychainService,
    authenticationPrompt: {
      title: '验证身份',
      cancel: '取消',
      ...(promptOptions ?? {})
    }
  });

  if (!creds || creds.username !== key) return null;

  try {
    return JSON.parse(creds.password);
  } catch {
    return creds.password as T;
  }
};

// 判断生物识别是否可用
export const isBiometryAvailable = async (): Promise<boolean> => {
  const type = await Keychain.getSupportedBiometryType();
  return type !== null;
};

// 清理所有keychain存储
export const clearAllCredentials = async (): Promise<void> => {
  await Keychain.resetGenericPassword();
};

Keychain 本地加密存储

使用文档

为什么需要 accessible 配置?

src\utils\storage\secureStorage.ts里默认配置了WHEN_UNLOCKED

accessible 选项 安全级别 适用场景
WHEN_UNLOCKED_THIS_DEVICE_ONLY 🔒🔒🔒🔒 最高 设备解锁后访问,不跨设备同步
WHEN_UNLOCKED 🔒🔒🔒 高 设备解锁后访问,可跨设备同步
AFTER_FIRST_UNLOCK 🔒🔒 中 首次解锁后即可访问
ALWAYS 🔒 低 随时可访问,包括设备锁定时
Token 类型 推荐配置 原因
access_token WHEN_UNLOCKED 平衡安全与体验,支持跨设备同步
refresh_token AFTER_FIRST_UNLOCK 允许后台刷新,避免频繁登录

生物识别安全存储

若需要使用生物识别安全存储功能,需开启以下权限配置

需要配置权限声明和最低系统版本。

📱 具体配置

iOS
  1. Info.plist 添加:
xml 复制代码
<key>NSFaceIDUsageDescription</key>
<string>App需要Face ID进行安全验证</string>
Android
  1. AndroidManifest.xml 添加:
xml 复制代码
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
  1. build.gradle 确保:
gradle 复制代码
minSdkVersion 23  // Android 6.0+

⚠️ 重要提醒

  • iOS:仅Face ID需要配置,Touch ID不需要
  • Android:从API 23(Android 6.0)开始支持
  • 模拟器:只能测试流程,无法真实验证

配置完这些,react-native-keychain的生物识别功能才能正常工作。

什么时候需要生物识别

access_token 直接用 Keychain 存储就够了,不需要生物识别。

📊 核心原因

场景 生物识别存储 Keychain普通存储 建议
每次请求API ❌ 需反复验证指纹/面容 ✅ 自动读取 Keychain
短期有效性 ⚠️ 过度保护(2小时过期) ✅ 风险可控 Keychain
实际安全需求 适合支付密码等 ✅ 已有设备级加密 Keychain

🎯 具体方案

typescript 复制代码
// ✅ 正确做法:普通Keychain存储
import { saveSecure } from './secureStorage';

// access_token - Keychain普通存储
await saveSecure('access_token', 'eyJhbGciOiJ...');

// refresh_token - 也可Keychain存储
await saveSecure('refresh_token', 'refresh_xyz');

生物识别留给真正的敏感数据:

  • 银行卡PIN码
  • 生物特征密钥
  • 医疗健康数据

Keychain的加密已经足够保护 token,频繁的生物识别验证会严重影响用户体验。

总结到此!

相关推荐
沙蒿同学10 分钟前
我把架构约定编译成了会变红的测试:Wails v2 + Go + Vue3 桌面脚手架实战
前端·后端·github
cpolar技术支持13 分钟前
本地 Playwright 测试报告怎么远程复盘?Trace Viewer 跑起来后,用 cpolar 分享失败现场
前端·自动化测试·测试工具·cpolar·playwright
wordbaby13 分钟前
企业级后台管理系统路由设计与最佳实践指南
前端
胡志辉的博客22 分钟前
【完全开源】IP 纯净度检测 可一键部署到自己的CF
前端·javascript·chrome·ip·chromium
Hilaku1 小时前
作为面试官,我最怕遇到什么样的候选人?
前端·javascript·程序员
TiDi1 小时前
Pinia优化重复请求
前端
web3d5201 小时前
01-用 Leafletjs 10 分钟搭一张水利一张图(Vue3 + Vite 实战)
前端·javascript
晚安日记wanna2 小时前
Vue2 的 defineProperty 差在哪四层追问筛掉九成候选人
前端·vue.js·面试
TiDi2 小时前
吸顶导航交互实现
前端
kisshyshy2 小时前
从Props透传到自定义Hook:系统梳理React跨层级通信与逻辑复用
前端·架构·代码规范