You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何通过已认证的其他客户端将Firebase匿名认证的电视客户端会话转换为医生的已登录会话?

如何通过已认证的其他客户端将Firebase匿名认证的电视客户端会话转换为医生的已登录会话?

这是一个非常典型的设备关联+无界面认证场景,Firebase的**自定义令牌(Custom Tokens)**结合云函数是解决这个问题的标准、安全方案,完美匹配你的需求。下面是具体的落地思路和实现步骤:


核心方案概述

你的现有QR码关联流程已经搭建了医生与TV设备的绑定关系,现在只需要通过受信任的云环境(Firebase Cloud Function)生成医生账号的自定义令牌,让TV设备用这个令牌完成登录,就能获得合法的Firebase Auth会话——既不需要在TV上显示登录表单,又能让TV调用标准的Firebase Auth方法(比如currentUser.uid)返回医生的UID。


详细实现步骤

1. 部署Firebase云函数生成自定义令牌

客户端(包括Portal和TV)不能直接使用Firebase Admin SDK生成自定义令牌(这需要敏感的服务账号密钥),所以必须通过Cloud Function(受信任的服务器环境)来处理令牌生成逻辑。我们创建一个Callable类型的云函数,它会自动验证调用者的Auth状态:

const functions = require("firebase-functions");
const admin = require("firebase-admin");
admin.initializeApp();

exports.generateTvAuthToken = functions.https.onCall(async (data, context) => {
  // 验证1:请求来自对应的TV设备(TV的匿名UID必须等于tvUid)
  const tvUid = data.tvUid;
  if (!context.auth || context.auth.uid !== tvUid) {
    throw new functions.https.HttpsError(
      "permission-denied",
      "仅当前绑定的TV设备可请求认证令牌"
    );
  }

  // 验证2:TV会话已绑定合法的医生账号
  const tvSessionRef = admin.firestore().collection("tv-sessions").doc(tvUid);
  const tvSessionSnap = await tvSessionRef.get();
  
  if (!tvSessionSnap.exists) {
    throw new functions.https.HttpsError("not-found", "未找到对应的TV会话");
  }

  const linkedUid = tvSessionSnap.data()?.linkedUid;
  if (!linkedUid) {
    throw new functions.https.HttpsError("failed-precondition", "TV未绑定医生账号");
  }

  // 验证3:确认医生已购买TV客户端权限(和你现有流程的校验逻辑一致)
  const doctorUserRef = admin.firestore().collection("users").doc(linkedUid);
  const doctorUserSnap = await doctorUserRef.get();
  
  if (!doctorUserSnap.exists || !doctorUserSnap.data()?.hasTvAddOn) {
    throw new functions.https.HttpsError(
      "permission-denied",
      "该医生未购买TV客户端权限"
    );
  }

  // 生成医生账号的自定义令牌
  const customToken = await admin.auth().createCustomToken(linkedUid);
  return { customToken };
});

2. 调整TV端逻辑,获取令牌并完成认证

当TV监听到自己的tv-sessions/{tvUid}文档出现linkedUid后,调用上述云函数获取自定义令牌,再用令牌完成登录:

// 电视端Dart代码示例
bool _isAlreadyAuthenticated = false;

void startListeningForDoctorLink() {
  final tvUid = FirebaseAuth.instance.currentUser?.uid;
  if (tvUid == null) return;

  final tvSessionDoc = FirebaseFirestore.instance.collection('tv-sessions').doc(tvUid);
  tvSessionDoc.snapshots().listen((snapshot) async {
    if (_isAlreadyAuthenticated) return;

    if (snapshot.exists) {
      final data = snapshot.data();
      final linkedUid = data?['linkedUid'];
      
      if (linkedUid != null) {
        try {
          // 调用云函数获取医生账号的自定义令牌
          final functions = FirebaseFunctions.instance;
          final result = await functions.httpsCallable('generateTvAuthToken').call({
            'tvUid': tvUid,
          });

          final customToken = result.data['customToken'];
          // 使用自定义令牌登录,切换到医生的Auth会话
          await FirebaseAuth.instance.signInWithCustomToken(customToken);
          
          _isAlreadyAuthenticated = true;
          // 此时currentUser.uid将返回医生的UID,可直接使用标准Firebase Auth方法
          print('TV当前认证用户UID: ${FirebaseAuth.instance.currentUser?.uid}');
        } catch (e) {
          print('认证绑定失败: $e');
          // 可添加UI提示,比如“绑定失败,请确认已购买TV权限并重新扫描”
        }
      }
    }
  });
}

3. 现有Portal端流程无需大改

你当前的Portal端流程(扫描QR码→登录→更新tv-sessionslinkedUid)保持不变,仅需确保:

  • Portal端通过URL中的tvSerialKey正确找到对应的tv-sessions文档(即查询tv-sessions集合中serialKey匹配的文档,拿到tvUid
  • 登录后校验医生确实拥有TV客户端权限(比如检查users/{uid}中的权限标记)

安全性与方案优势

这个方案是Firebase官方推荐的无界面认证方式,具备以下核心优势:

  1. 完全无TV登录表单:整个认证流程通过QR码关联+云函数令牌完成,完全符合你的需求。
  2. 标准Firebase Auth会话:登录后TV可以正常调用currentUser.uidsignOut()等所有标准Auth方法,和普通登录的体验完全一致。
  3. 多层安全防护
    • 云函数验证请求的TV设备是对应的绑定设备(匿名UID等于tvUid),防止其他设备冒充。
    • 强制校验医生的TV权限,避免未付费用户滥用服务。
    • 自定义令牌仅在受信任的云环境生成,不会暴露敏感的Admin SDK密钥。
  4. 自动会话管理:Firebase Auth会自动刷新ID Token保持会话有效;如果医生修改密码或主动撤销会话,TV的认证会自动失效,符合安全最佳实践。

额外优化建议

  • 添加会话过期逻辑:在tv-sessions文档中加入expiresAt字段,若设备长时间无操作自动解除绑定,提升安全性。
  • 处理解绑场景:当医生在Portal端解除TV绑定(删除linkedUid),TV监听到变化后调用signOut()回到匿名登录状态。
  • 增强错误提示:在TV端针对不同的云函数错误返回对应的用户提示(比如权限错误、会话不存在等),提升用户体验。

内容来源于stack exchange

火山引擎 最新活动