HTML5(H5)版本的活体检测是一个重要的安全防范产品,可以在KYC过程中防止活体欺骗攻击。
该技术被应用于各种PC端和移动端应用中,以确保进行活体检测的是一个真实的人,而不是照片、视频、面具或其他形式的生物特征攻击。
活体检测H5产品,利用浏览器的能力,无需额外的插件或软件安装,即可无缝集成到在线平台中。它利用复杂的算法分析实时视频流中微妙的动作、纹理和其他体现活体真人的指标。
在整个过程中,终端用户需要根据提示做出相应动作,如“张嘴”或“眨眼”等。活体检测的结果可以从后端检索获取。如果检测到真人,会返回一张最佳的人脸自拍照片。
集成步骤
H5活体检测的对接流程图如下:
集成H5活体检测共有3个步骤:
- 调用“获取Token API“,配置活体检测后的跳转页面URL和进行其他设置,接口返回Token和H5活体检测URL。
- 用户通过步骤1产生的H5活体检测URL,完成活体检测全过程,跳转到相应的成功和失败页面URL。
- 调用“获取结果API”在活体检测成功时获取最佳人脸自拍照,或在失败时接收详细原因。
如果是Webview对接,涉及到获取摄像头权限,具体参考“4.获取摄像头权限”部分。
1.获取Token API
Base URL
- Singapore:https://sg-credit.apitd.net/verification/kyc/h5/liveness/token/v1
- Indonesia:https://id-credit.apitd.net/verification/kyc/h5/liveness/token/v1
API
URL | 请求方式 | Content Type | 输出格式 | 字符集 |
---|---|---|---|---|
api-base-url?partner_code=xxx&partner_key=xxx | POST | application/json | JSON | UTF-8 |
认证参数
字段 | 类型 | 含义 | 建议 | 备注 |
---|---|---|---|---|
partner_code | String | 合作方标识 | 必须 | 由TD分配 |
partner_key | String | 合作方密钥 | 必须 | 由TD分配 |
请求参数
字段 | 类型 | 含义 | 建议 | 备注 |
---|---|---|---|---|
success_redirect | String | 活体检测成功后的跳转页面 | 必须 | Web页面的URL地址 |
failure_redirect | String | 活体检测失败后的跳转页面 | 必须 | Web页面的URL地址 |
language | String | 页面上显示的提示文字信息的语言 枚举值: en: 英语(English) id: 印尼语(Indonesian) es: 西班牙语(Espanol) ar: 阿拉伯语(Arabic) tl: 菲律宾语(Filipino) ko: 韩语(Korean) pt: 葡萄牙语(Portuguese) ru: 俄语(Russian) th: 泰语(Thai) tr: 土耳其语(Turkish) vi: 越南语(Vietnamese) kh: 高棉语(Khmer) zh-Hans: 简体中文(Simplified Chinese) zh-Hant: 繁体中文(Traditional Chinese) | 可选 | 1. 如果指定了语言,则显示该语言提示文字信息。 2. 如果未指定语言,则获取浏览器语言,并显示相应语言提示文字信息。 3. 如果在第2步中未获取语言,或者遇到其他异常情况,则默认显示英语en。 |
audio | Boolean | 播放动作提示语音 目前支持英语、西班牙语和印尼语,3种语音 | 可选 | 如果不设置,则默认为false,该情况下不播放提示语音。 如果设置为true: 1. 依照language中设置的提示语言,播放相应提示语音 2. 如果在第1步中未获取语言,或者遇到其他异常情况,则默认播放英语提示音 |
响应参数
字段 | 类型 | 含义 | 备注 |
---|---|---|---|
code | Integer | API 状态码 | |
message | String | 状态信息 | 在 API 异常状态下会输出具体的异常原因 |
sequence_id | String | 响应唯一码 | 用于跟踪每次请求记录的唯一标识 |
token | String | 授权码 | token授权码用于生成活体检测URL,并用于随后查询此活体检测过程的结果 |
url | String | 活体检测url | 返回H5活体检测URL,格式如下(包含跳转页面URL和配置): https://static.tongdun.net/liveness/index.html#/progress?code=token&success_redirect=[successURL]&failure_redirect=[failureURL]&language=xx&audio=true& |
响应示例
-
业务请求
{ "success_redirect": "http://www.google.com", "failure_redirect": "http://www.facebook.com", "language": "en", "audio": true }
-
业务请求成功
{
"code": 200,
"message": "success",
"sequence_id": "17119500882*****29",
"token": "a41701e4-b2a2-4f62-8cd4-9******3",
"url": "https://static.tongdun.net/liveness/index.html#/progress?code=a41701e4-b2a2-4f62-8cd4-9******3&success_redirect=http%3A%2F%2Fwww.google.com&failure_redirect=http%3A%2F%2Fwww.facebook.com&language=en&audio=true&"
}
- 业务请求失败
{
"code": 11350,
"sequence_id": "69b57131b6fb********61ccba118b60",
"message": "Internal error"
}
2.活体检测和页面跳转
活体完成之后,页面会自动跳转到客户配置的回调页面:成功页[successURL]或者失败页[failureURL]。
回调页面URL样例:
● 成功回调样例:[successURL]?code=7ac3a964-f540-4aa2-acdf-59ea1639572e&state=0
● 失败回调样例:[failureURL]?code=7ac3a964-f540-4aa2-acdf-59ea1639572e&state=2
当页面跳转后,客户回调页面接收到我们活体完成的参数state值为0或2时,就可以去调用获取结果API,查询活体检测结果。state值为非0或2的其他值时,详见以下解释和建议。
state返回值 | 含义 | 后续流程建议 |
---|---|---|
0 | 通过活体验证 | 调用获取结果API,查询活体检测结果 |
2 | 未通过活体验证,一般是活体攻击 | 调用获取结果API,查询活体检测结果 根据风险偏好,决定是否重新发起活体检测 *如果要重试,需要在第1步调用“获取Token API”重新获取token |
3 | Token无效,表示前面步骤获取的Token已经被使用过 | 重新发起活体检测,请用户重试 |
4 | 无法打开摄像头 | 摄像头无法打开,请走其他检验方式,例如人工流程 |
5、6 | 网络问题 | 重新发起活体检测,请用户重试 |
其他 | 万一回调state状态码被恶意篡改,例如改为state=8 | 根据风险偏好,选择直接终止流程,或者重新发起活体检测 |
3.获取结果 API
Base URL
- Singapore:https://sg-credit.apitd.net/verification/kyc/h5/liveness/result/v1
- Indonesia:https://id-credit.apitd.net/verification/kyc/h5/liveness/result/v1
API
URL | 请求方式 | Content Type | 输出格式 | 字符集 |
---|---|---|---|---|
api-base-url?partner_code=xxx&partner_key=xxx | POST | application/json | JSON | UTF-8 |
认证参数
字段 | 类型 | 含义 | 建议 | 备注 |
---|---|---|---|---|
partner_code | String | 合作方标识 | 必须 | 由 TD 分配 |
partner_key | String | 合作方密钥 | 必须 | 由 TD 分配 |
请求参数
字段 | 类型 | 含义 | 建议 | 备注 |
---|---|---|---|---|
token | String | 授权码 | 必须 |
响应参数
字段 | 类型 | 含义 | 备注 |
---|---|---|---|
code | Integer | API 状态码 | |
message | String | 状态信息 | 在 API 异常状态下会输出具体的异常原因 |
sequence_id | String | 响应唯一码 | 用于跟踪每次请求记录的唯一标识 |
image | String | 活体检测人脸图片 | 活体检测过程中抓拍到的最佳人脸自拍图片,base64格式; 只有在活体检测成功的情况下才返回最佳人脸自拍图片 |
API 状态码
Code | Message | 是否计费 |
---|---|---|
200 | success (live person) 成功(真人) | 是 |
12223 | No face detected 没有检测到人脸 | 否 |
12224 | Multiple faces have been detected 多张人脸 | 否 |
12225 | Detection timeout 检测超时 | 否 |
12226 | Person change detected 检测到换人 | 否 |
12227 | Token has been used Token已经被使用 | 否 |
12228 | User actively cancels liveness detection process 用户主动取消活体检测 | 否 |
12229 | Liveness detection result not found 没有找到识别结果 | 否 |
12202 | Identified as a blink attack 判断为抠眼攻击 | 是 |
12203 | Identified as a mouth movement attack 判断为抠嘴攻击 | 是 |
12204 | Identified as a partial face attack 判断为半张脸攻击 | 是 |
12205 | Identified as a video replay attack 判断为视频回放攻击 | 是 |
12206 | Identified as a black and white image 判断为黑白图片 | 是 |
12207 | Identified as a paper-based attack 判断为纸面攻击 | 是 |
12208 | Identified as a frame (including paper or phone frame) 判断为边框(包括纸面、手机等边框) | 是 |
12209 | Identified as a moire pattern attack 判断为摩尔纹攻击 | 是 |
12210 | Identified as a face superiority attack 判断为脸优攻击 | 是 |
12211 | Identified as a paper-based attack (optical flow) 判断为纸面攻击(光流) | 是 |
12212 | Identified as a mask attack 判断为面具攻击 | 是 |
12213 | Identified as an ID card attack 判断为证卡攻击 | 是 |
12214 | Identified as a 3D mask attack 判断为3D面具攻击 | 是 |
12215 | Identified as a synthetic image attack 判断为合成图像攻击 | 是 |
12216 | Identified as a black-market software attack 判断为黑产软件攻击 | 是 |
12217 | Identified as a T-type mask attack 判断为T型面具攻击 | 是 |
12218 | Identified as a blurry image 判断为模糊图片 | 是 |
12219 | Suspected deepfake image attack 疑似深伪图像攻击 | 是 |
12220 | Suspected high-resolution screen attack 疑似高清屏幕攻击 | 是 |
12221 | Light verification failed 光线校验失败 | 是 |
12222 | Injection attack 注入攻击 | 是 |
12250 | Verification error 其他活体攻击 | 是 |
11350 | Internal error 内部错误 | 否 |
响应示例
-
业务请求
{ "token":"a41701e4-b2a2-4f62-8cd4-9******3" }
-
业务请求成功
{
"code": 200,
"message": "Success",
"sequence_id": "1711950327713613G109E0A081240931",
"image": "/9j/4AAFbs64"
}
- 业务请求失败
{
"code": 11350,
"sequence_id": "69b57131b6fb********61ccba118b60",
"message": "Internal error"
}
4.获取摄像头权限
Android
1. 首先,我们需要在AndroidManifest.xml
文件中添加相机权限:
AndroidManifest.xml
文件中添加相机权限: <uses-permission android:name="android.permission.CAMERA" />
<uses-feature
android:name="android.hardware.camera"
android:required="false" />
2. 检查和请求权限
在应用中,我们需要先检查是否已经获取了相机权限,如果没有则请求权限:
private boolean checkCameraPermission() {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA}, CAMERA_PERMISSION_REQUEST_CODE);
return false;
}
return true;
}
}
// 权限已被授予
if (checkCameraPermission()) {
mWebView.loadUrl(https://活体地址.com);
}
3. 处理权限请求结果
在onRequestPermissionsResult
方法中处理权限请求结果:
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == CAMERA_PERMISSION_REQUEST_CODE) {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// 权限已被授予
mWebView.loadUrl(https://活体地址.com);
} else {
// 权限被拒绝
Toast.makeText(this, "相机权限被拒绝", Toast.LENGTH_SHORT).show();
}
}
}
iOS
1.在 info.plist 文件中设置 NSCameraUsageDescription,以允许应用访问相机:
<key>NSCameraUsageDescription</key>
<string>$(PRODUCT_NAME) need to use your front camera</string>
2.配置 Webview 实例以允许内嵌媒体播放,禁用用户操作播放,启用 JavaScript,并启用 JavaScript 自动打开窗口:
let configuration = WKWebViewConfiguration()
configuration.allowsInlineMediaPlayback = true
configuration.mediaTypesRequiringUserActionForPlayback = []
configuration.preferences.javaScriptEnabled = true
configuration.preferences.javaScriptCanOpenWindowsAutomatically = true
self.webView = WKWebView(frame: self.view.bounds, configuration: configuration)
3.在进入 H5 活体检测之前,先检查并请求相机访问权限:
// Request camera permission (in Swift)
func checkAVAuthorizationStatus(with block: @escaping((_ authed: Bool) -> Void)) {
let authStatus = AVCaptureDevice.authorizationStatus(for: .video)
if authStatus == .authorized {
block(true)
} else if authStatus == .denied || authStatus == .restricted {
block(false)
} else {
AVCaptureDevice.requestAccess(for: .video) { (result) in
DispatchQueue.main.async {
block(result)
}
}
}
}
// Usage
// Check auth status before entering H5 liveness page
self.checkAVAuthorizationStatus { authed in
if authed {
// Enter H5 liveness page directly
} else {
// Input logic to handle camera unauthorised
}
}
4.(可选)一次授权,避免每次在 H5 中弹出相机权限请求:
// Ensure your ViewController inherits WKUIDelegate.
class ViewController: UIViewController, WKUIDelegate {
...
// Validated for iOS 15+ only
@available(iOS 15.0, *)
func webView(_ webView: WKWebView, requestMediaCapturePermissionFor origin: WKSecurityOrigin, initiatedByFrame frame: WKFrameInfo, type: WKMediaCaptureType, decisionHandler: @escaping (WKPermissionDecision) -> Void) {
// Please add necessary security check like domain filter if needed.
decisionHandler(.grant)
}
}
self.webView.uiDelegate = self