4、账户认证机制:本地 PIN 码认证、生物识别集成(指纹/人脸)、第三方 OAuth 登录

账户认证,说白了就是「你怎么证明你是你」。

在车上,这个场景比手机复杂得多。你想想看,手机是你私有的,但车可能是全家共用。我做过好几个车载项目,每次聊到认证,产品经理总会问一句:「能不能既安全又方便?」嗯,这俩词儿放一起,本身就是个挑战。

4.1 本地 PIN 码认证:最基础的防线

PIN 码认证,是车载系统里最古老、也最稳妥的方式。它不依赖网络,不依赖传感器,只要有个触摸屏就能用。

核心设计要点:

  • 长度限制:我个人习惯是 4-8 位数字。太短不安全,太长用户记不住。在车上,用户可能正在开车,输入太长的 PIN 码会分散注意力。
  • 错误次数锁定:这个必须做。我曾经见过一个项目,没做锁定机制,结果测试时被人暴力破解了。建议连续输错 5 次后,锁定 30 秒;再错 5 次,锁定 5 分钟。
  • 安全存储:PIN 码绝对不能明文存。用 Android 的 KeyStore 或者 EncryptedSharedPreferences 来存储哈希值。我一般用 PBKDF2 或者 bcrypt 做哈希,别用 MD5,那是上个世纪的东西。

避坑指南:我曾经在某个项目中,直接把 PIN 码存到了 SharedPreferences 里。结果测试工程师用 root 过的设备一读,全暴露了。从那以后,我所有敏感数据都走 KeyStore。

代码示例:PIN 码哈希存储

// 使用 PBKDF2 生成安全的 PIN 码哈希
fun hashPin(pin: String, salt: ByteArray): String {
    val spec = PBEKeySpec(pin.toCharArray(), salt, 10000, 256)
    val factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256")
    val hash = factory.generateSecret(spec).encoded
    return Base64.encodeToString(hash, Base64.NO_WRAP)
}

// 验证 PIN 码
fun verifyPin(inputPin: String, storedHash: String, salt: ByteArray): Boolean {
    val inputHash = hashPin(inputPin, salt)
    return inputHash == storedHash
}

4.2 生物识别集成:指纹与人脸

生物识别,说白了就是「用你的身体当密码」。在车上,这个需求越来越强烈。你想想看,手里拿着东西,或者戴着口罩,输 PIN 码多麻烦?

指纹识别:

  • 硬件要求:需要车机有指纹传感器。目前主流方案是电容式或光学式。我建议用电容式,识别速度快,而且不怕湿手。
  • Android 集成:用 BiometricManagerBiometricPrompt。Android 9 以上都支持。注意要检查设备是否支持指纹,以及用户是否已经注册了指纹。
  • 安全等级:指纹属于「弱生物特征」,因为指纹可以被复制。所以,指纹只能用于解锁和本地操作,不能用于支付或敏感操作。

我的经验:指纹识别在冬天容易出问题。用户手干、脱皮,或者戴手套,识别率会下降。我建议在指纹失败后,自动回退到 PIN 码认证,别让用户卡在那儿。

人脸识别:

  • 硬件要求:需要车内有摄像头。最好是红外摄像头,这样在暗光环境下也能用。普通 RGB 摄像头在晚上基本是废的。
  • Android 集成:同样用 BiometricManager。Android 10 以上支持人脸识别。注意要区分「弱人脸」和「强人脸」。弱人脸(2D 摄像头)容易被照片欺骗,强人脸(3D 结构光或红外)才安全。
  • 隐私问题:人脸数据是敏感信息。我建议所有生物特征数据都存储在本地,不要上传到云端。Android 的 BiometricPrompt 默认就是本地处理,这点很好。

注意:人脸识别在车上有个特殊问题——驾驶员可能戴着墨镜或口罩。我曾经在测试时发现,戴墨镜后识别率直接降到 30%。解决方案是:允许用户选择「仅使用眼部区域」或者「仅使用下半脸」进行匹配。

代码示例:生物识别认证

// 检查设备是否支持生物识别
val biometricManager = BiometricManager.from(context)
when (biometricManager.canAuthenticate()) {
    BiometricManager.BIOMETRIC_SUCCESS -> {
        // 支持生物识别
    }
    BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE -> {
        // 没有硬件
    }
    BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED -> {
        // 用户没有注册生物特征
    }
}

// 启动生物识别认证
val promptInfo = BiometricPrompt.PromptInfo.Builder()
    .setTitle("账户认证")
    .setSubtitle("请验证您的身份")
    .setNegativeButtonText("使用 PIN 码")
    .build()

val biometricPrompt = BiometricPrompt(this, executor, object : BiometricPrompt.AuthenticationCallback() {
    override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
        // 认证成功
    }
    override fun onAuthenticationFailed() {
        // 认证失败
    }
})

biometricPrompt.authenticate(promptInfo)

4.3 第三方 OAuth 登录:打通生态

OAuth 登录,说白了就是「用微信、QQ、或者 Google 账号登录」。在车上,这个功能越来越重要。你想想看,用户手机上已经登录了各种账号,为什么要在车上再注册一个?

为什么需要 OAuth?

  • 降低注册门槛:用户不用再记一套账号密码。我见过很多用户,因为嫌注册麻烦,直接放弃使用车载服务。
  • 数据同步:比如用 Google 账号登录,可以同步日历、联系人、导航历史。用微信登录,可以同步音乐收藏、播客订阅。
  • 安全性:OAuth 2.0 协议本身是成熟的。用户不需要把密码交给车机,而是通过授权码的方式完成认证。

实现要点:

  • WebView 还是 Custom Tab?:我建议用 Custom Tab(Chrome Custom Tabs)。WebView 容易被注入,而且用户体验差。Custom Tab 是浏览器渲染的,安全性和一致性都好很多。
  • Token 管理:OAuth 认证后会返回 Access Token 和 Refresh Token。Access Token 有效期短(通常 1 小时),Refresh Token 有效期长(通常 30 天)。我建议把 Token 存在 EncryptedSharedPreferences 里,别用普通 SharedPreferences。
  • 离线处理:车机可能没有网络。如果 Token 过期了,又没有网络刷新,怎么办?我建议在本地缓存一份用户信息,允许用户离线使用基础功能,等网络恢复后再刷新 Token。

避坑指南:我曾经在某个项目中,直接用 WebView 加载 OAuth 登录页面。结果发现,某些第三方平台(比如微信)会检测 WebView 并拒绝服务。后来换成 Custom Tab,问题就解决了。所以,能用 Custom Tab 就别用 WebView。

代码示例:OAuth 登录流程

// 使用 Chrome Custom Tab 打开 OAuth 登录页面
val builder = CustomTabsIntent.Builder()
val customTabsIntent = builder.build()
customTabsIntent.launchUrl(context, Uri.parse("https://accounts.google.com/o/oauth2/auth?client_id=YOUR_CLIENT_ID&redirect_uri=YOUR_REDIRECT_URI&response_type=code&scope=email%20profile"))

// 在 Activity 中接收回调
override fun onNewIntent(intent: Intent) {
    super.onNewIntent(intent)
    val uri = intent.data
    if (uri != null && uri.scheme == "yourapp" && uri.host == "callback") {
        val code = uri.getQueryParameter("code")
        // 用 code 换取 Access Token
        exchangeCodeForToken(code)
    }
}

// 用 Retrofit 换取 Token
fun exchangeCodeForToken(code: String) {
    val client = OkHttpClient()
    val requestBody = FormBody.Builder()
        .add("grant_type", "authorization_code")
        .add("code", code)
        .add("client_id", "YOUR_CLIENT_ID")
        .add("client_secret", "YOUR_CLIENT_SECRET")
        .add("redirect_uri", "yourapp://callback")
        .build()
    val request = Request.Builder()
        .url("https://oauth2.googleapis.com/token")
        .post(requestBody)
        .build()
    client.newCall(request).enqueue(object : Callback {
        override fun onResponse(call: Call, response: Response) {
            // 解析 Token 并存储
        }
        override fun onFailure(call: Call, e: IOException) {
            // 处理错误
        }
    })
}

4.4 三种认证方式的对比

认证方式 安全性 便捷性 适用场景 硬件要求
PIN 码 离线场景、备用方案
指纹识别 快速解锁、本地操作 指纹传感器
人脸识别 高(3D)/ 低(2D) 无接触场景、驾驶员识别 摄像头(建议红外)
OAuth 登录 第三方服务、数据同步 网络连接

我个人建议,在车载系统中,把这三种方式结合起来。PIN 码作为兜底方案,生物识别作为主要解锁方式,OAuth 用于打通生态。你想想看,用户上车,人脸识别自动解锁,然后 OAuth 同步他的音乐和导航,整个过程行云流水。这才是好的用户体验。

最后说一句:认证机制的设计,本质上是安全与便捷的博弈。别为了安全牺牲体验,也别为了便捷放弃安全。找到那个平衡点,才是真本事。