首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >H5 网页如何实现人脸核身?无需原生开发的方案

H5 网页如何实现人脸核身?无需原生开发的方案

原创
作者头像
克劳德2048
发布于 2026-10-08 14:45:04
发布于 2026-10-08 14:45:04
260
举报

摘要:

不是所有业务都有原生 App。H5 方式让人脸核身可以直接在网页里完成,无需开发客户端。本文讲清楚 H5 核身的适用条件、接入流程、签名生成方法和代码示例。


一、H5 方式的适用场景

H5 人脸核身适合以下情况:

  • 业务只有网页端,没有原生 App
  • 需要快速上线,不想走客户端发版流程
  • 用户来自微信、短信链接等外部渠道
  • 核身属于阶段性需求,不是核心高频流程

二、H5 核身的运行条件

H5 方式依赖浏览器调用摄像头,因此有明确的运行前提:

2.1 浏览器要求

浏览器

最低版本

Chrome

53+

Firefox

29+

Safari

iOS 11+

微信内置浏览器

6.5+

注意:部分浏览器不支持实时检测模式,需要引导用户更换。如果当前设备无法满足条件,前端会返回特定的错误码。

2.2 权限要求

  • 用户必须授权摄像头权限
  • 授权形式因手机型号而异,可能是弹窗、动作菜单或需要进入应用设置

三、接入流程概览

代码语言:txt
复制
前端 → 业务服务端 → 慧眼服务端
  ↓        ↓           ↓
请求核身  获取ticket   返回h5faceId
  ↓        ↓           ↓
计算签名 ← 返回签名参数
  ↓
跳转核身页面
  ↓
用户完成活体检测
  ↓
回调业务页面(带token)
  ↓
服务端拉取结果

3.1 详细步骤

步骤

执行方

动作

1

前端

用户填写身份信息,点击开始核身

2

业务服务端

调用 geth5faceid 接口获取 h5faceId

3

业务服务端

获取 NONCE 类型 ticket

4

业务服务端

计算签名 sign 并返回给前端

5

前端

构建核身 URL 并跳转

6

用户

完成活体检测(眨眼、摇头等)

7

核身页面

回调业务指定 URL,携带 token

8

业务服务端

调用 GetDetectInfoEnhanced 获取结果

四、签名生成方法

4.1 参与签名的参数

参数

说明

来源

appId

业务流程唯一标识(WBappid)

控制台申请

orderNo

订单号,字母/数字组成

业务方分配

userId

用户唯一标识

业务方分配

version

版本号,固定值 1.0.0

-

h5faceId

接口返回的唯一标识

geth5faceid 接口返回

ticket

NONCE 类型 ticket

服务端实时获取

nonce

32位随机字符串

业务方生成

4.2 签名算法步骤

代码语言:txt
复制
1. 生成一个32位的随机字符串 nonce(字母+数字)
2. 将 appId、userId、orderNo、version、h5faceId、ticket、nonce 共7个参数的值进行字典序排序
3. 将排序后的所有参数字符串拼接成一个字符串
4. 对拼接后的字符串进行 SHA1 编码,得到40位签名 sign

4.3 签名示例(Python)

代码语言:python
复制
import hashlib
import random
import string

def generate_nonce(length=32):
    """生成32位随机字符串"""
    chars = string.ascii_letters + string.digits
    return ''.join(random.choice(chars) for _ in range(length))

def generate_sign(params):
    """
    生成签名
    params: dict,包含 appId, userId, orderNo, version, h5faceId, ticket, nonce
    """
    # 1. 按字典序排序
    sorted_keys = sorted(params.keys())

    # 2. 拼接所有参数值
    concatenated = ''.join(params[key] for key in sorted_keys)

    # 3. SHA1 编码
    sign = hashlib.sha1(concatenated.encode('utf-8')).hexdigest().upper()

    return sign

# 示例参数
params = {
    'appId': 'appId001',
    'userId': 'userID19959248596551',
    'orderNo': 'aabc1457895464',
    'version': '1.0.0',
    'h5faceId': 'bwiwe1457895464',
    'ticket': 'zxc9Qfxlti9iTVgHAjwvJdAZKN3nMuUhrsPdPlPVKlcyS50N6tlLnfuFBPIucaMS',
    'nonce': 'kHoSxvLZGxSoFsjxlbzEoUzh5PAnTU7T'
}

sign = generate_sign(params)
print(f"签名: {sign}")
# 输出: 4E9DFABF938BF37BDB7A7DC25CCA1233D12D986B

4.4 签名示例(JavaScript/Node.js)

代码语言:javascript
复制
const crypto = require('crypto');

function generateSign(params) {
    // 1. 按字典序排序
    const sortedKeys = Object.keys(params).sort();

    // 2. 拼接所有参数值
    const concatenated = sortedKeys.map(key => params[key]).join('');

    // 3. SHA1 编码
    const sign = crypto.createHash('sha1').update(concatenated).digest('hex').toUpperCase();

    return sign;
}

// 示例参数
const params = {
    appId: 'appId001',
    userId: 'userID19959248596551',
    orderNo: 'aabc1457895464',
    version: '1.0.0',
    h5faceId: 'bwiwe1457895464',
    ticket: 'zxc9Qfxlti9iTVgHAjwvJdAZKN3nMuUhrsPdPlPVKlcyS50N6tlLnfuFBPIucaMS',
    nonce: 'kHoSxvLZGxSoFsjxlbzEoUzh5PAnTU7T'
};

const sign = generateSign(params);
console.log(`签名: ${sign}`);

五、服务端获取 h5faceId

5.1 调用接口

代码语言:python
复制
# Python 示例:调用 geth5faceid 接口
import requests

def get_h5_face_id(app_id, user_id, order_no, name, id_card):
    """
    上送身份信息,获取 h5faceId
    """
    url = "https://miniprogram-kyc.tencentcloudapi.com/api/server/h5/geth5faceid"

    # 生成签名(这里简化,实际需要按上述方法生成)
    nonce = generate_nonce()
    params = {
        'appId': app_id,
        'userId': user_id,
        'orderNo': order_no,
        'version': '1.0.0',
        'name': name,
        'idCard': id_card,
        'nonce': nonce,
        'ticket': get_nonce_ticket(user_id)  # 从腾讯云服务获取
    }
    params['sign'] = generate_sign(params)

    response = requests.post(url, json=params)
    result = response.json()

    if result.get('code') == 0:
        return {
            'h5faceId': result['data']['h5faceId'],
            'optimalDomain': result['data'].get('optimalDomain', 'kyc1.qcloud.com')
        }
    else:
        raise Exception(f"获取h5faceId失败: {result.get('msg')}")

5.2 获取 NONCE Ticket

代码语言:python
复制
def get_nonce_ticket(user_id):
    """
    获取 NONCE 类型的 ticket
    有效期120秒,一次性有效
    """
    # 调用腾讯云服务获取 ticket
    # 具体接口请参考官方文档
    url = "https://miniprogram-kyc.tencentcloudapi.com/api/server/getTicket"

    params = {
        'appId': APP_ID,
        'userId': user_id,
        'type': 'NONCE'
    }

    response = requests.post(url, json=params)
    result = response.json()

    if result.get('code') == 0:
        return result['data']['ticket']
    else:
        raise Exception(f"获取ticket失败: {result.get('msg')}")

六、前端调起核身

6.1 构建核身 URL

代码语言:javascript
复制
/**
 * 构建 H5 人脸核身 URL
 */
function buildVerifyUrl(params) {
    const { appId, version, nonce, orderNo, h5faceId, userId, sign, callbackUrl, optimalDomain } = params;

    // 对回调地址进行 URL Encode
    const encodedUrl = encodeURIComponent(callbackUrl);

    // 构建核身 URL
    const domain = optimalDomain || 'kyc1.qcloud.com';
    const verifyUrl = `https://${domain}/api/pc/login?` +
        `appId=${appId}` +
        `&version=${version}` +
        `&nonce=${nonce}` +
        `&orderNo=${orderNo}` +
        `&h5faceId=${h5faceId}` +
        `&url=${encodedUrl}` +
        `&userId=${userId}` +
        `&sign=${sign}`;

    return verifyUrl;
}

6.2 完整的前端调用示例

代码语言:javascript
复制
/**
 * 启动 H5 人脸核身
 */
async function startFaceVerify(userId, name, idCard) {
    try {
        // 1. 请求服务端获取核身参数
        const res = await fetch('/api/getFaceVerifyParams', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ userId, name, idCard })
        });

        const data = await res.json();
        if (data.code !== 0) {
            throw new Error(data.msg || '获取核身参数失败');
        }

        const { appId, version, nonce, orderNo, h5faceId, sign, optimalDomain } = data.data;

        // 2. 构建回调地址
        const callbackUrl = `${window.location.origin}/pages/verify-result`;

        // 3. 构建核身 URL 并跳转
        const verifyUrl = buildVerifyUrl({
            appId, version, nonce, orderNo, h5faceId, userId, sign, callbackUrl, optimalDomain
        });

        // 注意:不能直接用 <a> 标签跳转,会导致签名被预加载而失效
        // 应使用以下方式跳转
        window.location.href = verifyUrl;

    } catch (error) {
        console.error('启动核身失败:', error);
        alert(error.message);
    }
}

// 用户点击按钮触发
document.getElementById('btn-verify').addEventListener('click', () => {
    const userId = document.getElementById('user-id').value;
    const name = document.getElementById('name').value;
    const idCard = document.getElementById('id-card').value;

    if (!userId || !name || !idCard) {
        alert('请填写完整信息');
        return;
    }

    startFaceVerify(userId, name, idCard);
});

6.3 处理核身结果回调

代码语言:javascript
复制
// 在回调页面 /pages/verify-result 中处理结果
window.addEventListener('DOMContentLoaded', () => {
    // 从 URL 中获取 token 参数
    const urlParams = new URLSearchParams(window.location.search);
    const token = urlParams.get('token');

    if (token) {
        // 核身完成,通知服务端拉取结果
        getVerifyResult(token);
    } else {
        // 未获取到 token,可能用户中途退出
        showErrorMessage('未完成核身');
    }
});

async function getVerifyResult(token) {
    try {
        const res = await fetch('/api/getVerifyResult', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ token })
        });

        const data = await res.json();

        if (data.code === 0 && data.data.success) {
            // 核身成功
            showSuccessPage(data.data);
        } else {
            // 核身失败
            showErrorMessage(data.data?.message || '核身未通过');
        }
    } catch (error) {
        console.error('获取结果失败:', error);
        showErrorMessage('网络异常,请重试');
    }
}

七、常见返回码与处理

返回码

含义

处理建议

3001

当前浏览器不支持视频录制

引导更换浏览器或使用备用方案

3002

登录态异常,cookie 参数缺失

引导用户重新进入

3003

核身中途中断

引导重新发起

3004

无摄像头权限

引导重新授权摄像头

3005

当前浏览器不支持实时检测模式

建议更换浏览器

300101

报文包体问题

提示用户重新进入

签名不合法

签名计算错误或参数不一致

检查签名算法和参数

八、能力边界与替代方案

8.1 H5 方式的局限性

  • 体验不如原生:摄像头调用的稳定性和交互流畅度较差
  • 浏览器兼容性:部分老旧浏览器不支持
  • 无法离线使用:必须联网

8.2 何时选择其他方案

场景

推荐方案

对核身体验要求较高

原生 App SDK

用户设备环境复杂

原生 App SDK

核身是低频补充环节

H5(成本优势明显)

微信生态内高频使用

微信小程序

8.3 渠道支持范围

选型时应先确认目标渠道是否在支持列表内:

  • 微信 H5(浮层/普通模式)
  • PC 浏览器 H5
  • 企业微信 H5
  • 支付宝 H5
  • 移动 H5

腾讯云慧眼人脸核身支持微信 H5、PC 浏览器 H5 等多种接入渠道,可根据业务形态选择合适的方式。该系列产品正在限时特惠活动中,低至3.3折:https://cloud.tencent.com/act/pro/happynewyears

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

目录
  • 摘要:
  • 一、H5 方式的适用场景
  • 二、H5 核身的运行条件
    • 2.1 浏览器要求
    • 2.2 权限要求
  • 三、接入流程概览
    • 3.1 详细步骤
  • 四、签名生成方法
    • 4.1 参与签名的参数
    • 4.2 签名算法步骤
    • 4.3 签名示例(Python)
    • 4.4 签名示例(JavaScript/Node.js)
  • 五、服务端获取 h5faceId
    • 5.1 调用接口
    • 5.2 获取 NONCE Ticket
  • 六、前端调起核身
    • 6.1 构建核身 URL
    • 6.2 完整的前端调用示例
    • 6.3 处理核身结果回调
  • 七、常见返回码与处理
  • 八、能力边界与替代方案
    • 8.1 H5 方式的局限性
    • 8.2 何时选择其他方案
    • 8.3 渠道支持范围
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档