feat: 接入腾讯云与阿里云实人认证

This commit is contained in:
kate522 committed 2026-08-01 13:21:21 +08:00
1 parent 92e445dbd4
commit 4de67311cc
37 files changed
+2410 -1158

No files matched your search

@@ -0,0 +1,195 @@
<?php
/**
* Alibaba Cloud Cloudauth provider.
*
* @package AKSRT
*/
namespace AKSRT\Identity\Provider;
use AKSRT\Identity\SettingsRuntime;
defined('ABSPATH') || exit;
final class AliyunProvider implements ProviderInterface {
public function key(): string { return 'aliyun'; }
public function capabilities(): array { return ['personal' => true, 'enterprise' => true]; }
public function validate_config() {
$cfg = SettingsRuntime::get();
foreach (['access_key_id', 'access_key_secret'] as $key) {
if (empty($cfg['credentials'][$key])) {
return new \WP_Error('aksrt_identity_aliyun_credentials', __('阿里云实人认证凭证不完整', 'aksrt'));
}
}
if (empty($cfg['personal_flow'])) {
return new \WP_Error('aksrt_identity_aliyun_scene', __('请配置阿里云实人认证 SceneId', 'aksrt'));
}
if (!empty($cfg['enterprise_flow']) && preg_match('/[^A-Za-z0-9_-]/', (string) $cfg['enterprise_flow'])) {
return new \WP_Error('aksrt_identity_aliyun_enterprise_scene', __('阿里云企业认证 SceneCode 格式无效', 'aksrt'));
}
return true;
}
public function start_personal(array $request): ProviderResult {
return $this->init_face_verify($request);
}
public function start_enterprise(array $request): ProviderResult {
$cfg = SettingsRuntime::get();
// Ref: https://help.aliyun.com/zh/id-verification/enterprise-factor-validation-v2
foreach (['company_name', 'company_code', 'person_name', 'id_number'] as $key) {
if (empty($request[$key])) {
return ProviderResult::failed('aliyun_enterprise_fields', __('企业认证资料不完整', 'aksrt'));
}
}
$response = $this->rpc('cloudauth.aliyuncs.com', '2022-11-25', 'EntElementVerifyV2', [
'SceneCode' => (string) ($cfg['enterprise_flow'] ?: $cfg['personal_flow']),
'MerchantBizId' => preg_replace('/[^A-Za-z0-9]/', '', (string) $request['application_uuid']),
'MerchantUserId' => preg_replace('/[^A-Za-z0-9]/', '', 'U' . (string) ($request['user_id'] ?? '0')),
'UserAuthorization' => '1',
'InfoVerifyType' => 'ENT_4META',
'LicenseNo' => preg_replace('/\s+/', '', (string) $request['company_code']),
'EntName' => (string) $request['company_name'],
'LegalPersonName' => (string) $request['person_name'],
'LegalPersonCertNo' => preg_replace('/\s+/', '', (string) $request['id_number']),
]);
if (is_wp_error($response)) {
return ProviderResult::failed($response->get_error_code(), $response->get_error_message());
}
if (!$this->enterprise_passed($response)) {
return ProviderResult::failed(
sanitize_text_field((string) ($response['Code'] ?? $response['ResultCode'] ?? 'enterprise_failed')),
sanitize_text_field((string) ($response['Message'] ?? __('企业要素或法人关系核验未通过', 'aksrt')))
);
}
return $this->init_face_verify($request);
}
public function query(array $application): ProviderResult {
// Ref: https://api.aliyun.com/document/Cloudauth/2019-03-07/DescribeFaceVerify
$response = $this->rpc('cloudauth.aliyuncs.com', '2019-03-07', 'DescribeFaceVerify', [
'SceneId' => (string) ($application['provider_flow'] ?? ''),
'CertifyId' => (string) ($application['provider_session_id'] ?? ''),
]);
if (is_wp_error($response)) {
return ProviderResult::pending(['result_code' => $response->get_error_code(), 'message' => __('认证结果暂未取回,请稍后重试', 'aksrt')]);
}
$code = sanitize_text_field((string) ($response['Code'] ?? ''));
if ($code !== '' && $code !== '200') {
return ProviderResult::failed($code, sanitize_text_field((string) ($response['Message'] ?? '')));
}
$passed = $response['ResultObject']['Passed'] ?? $response['Passed'] ?? null;
$success = $response['ResultObject']['Success'] ?? $response['Success'] ?? null;
if ((string) ($response['Code'] ?? '') === '200' && ($success === true || $success === 'True') && $passed === 'T') {
return ProviderResult::passed(['request_id' => (string) ($response['RequestId'] ?? ''), 'result_code' => 'passed']);
}
$status = strtoupper((string) ($response['ResultObject']['SubCode'] ?? $response['ResultObject']['Status'] ?? ''));
if ($status === '' || in_array($status, ['PROCESSING', 'INIT', 'PENDING'], true)) {
return ProviderResult::pending(['result_code' => $status ?: 'processing', 'message' => __('阿里云仍在处理认证结果', 'aksrt')]);
}
return ProviderResult::failed($status, sanitize_text_field((string) ($response['ResultObject']['MaterialInfo'] ?? __('实人认证未通过', 'aksrt'))));
}
public function verify_callback(\WP_REST_Request $request): bool {
$token = sanitize_text_field((string) ($request->get_param('CallbackToken') ?: $request->get_param('callbackToken')));
$session = sanitize_text_field((string) ($request->get_param('certifyId') ?: $request->get_param('CertifyId')));
if ($token === '' || $session === '') {
return false;
}
global $wpdb;
$table = \AKSRT\Core\DBMigrator::table('identity_application');
$uuid = (string) $wpdb->get_var($wpdb->prepare(
"SELECT application_uuid FROM {$table} WHERE provider = 'aliyun' AND provider_session_id = %s LIMIT 1",
$session
));
return $uuid !== '' && hash_equals(hash_hmac('sha256', $uuid, wp_salt('nonce')), $token);
}
public function parse_callback(\WP_REST_Request $request): ProviderResult {
$session = sanitize_text_field((string) ($request->get_param('certifyId') ?: $request->get_param('CertifyId')));
$outer_order = sanitize_text_field((string) ($request->get_param('outerOrderNo') ?: $request->get_param('OuterOrderNo')));
return ProviderResult::pending(['provider_session_id' => $session, 'outer_order_no' => $outer_order, 'result_code' => 'callback_received']);
}
private function init_face_verify(array $request): ProviderResult {
// Ref: https://api.aliyun.com/document/Cloudauth/2019-03-07/InitFaceVerify
$cfg = SettingsRuntime::get();
$callback_token = hash_hmac('sha256', (string) ($request['application_uuid'] ?? ''), wp_salt('nonce'));
$params = [
'SceneId' => (string) $cfg['personal_flow'],
'OuterOrderNo' => (string) ($request['application_uuid'] ?? wp_generate_uuid4()),
'ProductCode' => (string) $cfg['product_code'],
'CertifyUrlType' => 'H5',
'CertType' => 'IDENTITY_CARD',
'CertName' => (string) ($request['person_name'] ?? ''),
'CertNo' => preg_replace('/\s+/', '', (string) ($request['id_number'] ?? '')),
'ReturnUrl' => esc_url_raw((string) ($request['return_url'] ?? '')),
'CallbackUrl' => rest_url('aksrt/v1/identity/callback/aliyun'),
'CallbackToken' => $callback_token,
];
$response = $this->rpc('cloudauth.aliyuncs.com', '2019-03-07', 'InitFaceVerify', $params);
if (is_wp_error($response)) {
return ProviderResult::failed($response->get_error_code(), $response->get_error_message());
}
$code = sanitize_text_field((string) ($response['Code'] ?? ''));
if ($code !== '' && $code !== '200') {
return ProviderResult::failed($code, sanitize_text_field((string) ($response['Message'] ?? '')));
}
$object = is_array($response['ResultObject'] ?? null) ? $response['ResultObject'] : [];
$certify_id = sanitize_text_field((string) ($object['CertifyId'] ?? $response['CertifyId'] ?? ''));
$url = esc_url_raw((string) ($object['CertifyUrl'] ?? $object['Url'] ?? $response['CertifyUrl'] ?? ''));
if ($certify_id === '' || $url === '') {
return ProviderResult::failed('aliyun_invalid_response', __('阿里云未返回有效认证会话', 'aksrt'));
}
return ProviderResult::pending([
'provider_session_id' => $certify_id,
'request_id' => sanitize_text_field((string) ($response['RequestId'] ?? '')),
'redirect_url' => $url,
'result_code' => 'session_created',
'expires_at' => gmdate('Y-m-d H:i:s', time() + ((int) $cfg['session_ttl_minutes'] * MINUTE_IN_SECONDS)),
]);
}
/** @return array<string,mixed>|\WP_Error */
private function rpc(string $host, string $version, string $action, array $business) {
$cfg = SettingsRuntime::get();
$endpoint = 'https://' . $host . '/';
// The official PHP SDK uses ACS3-HMAC-SHA256 by default. This zero-dependency
// fallback deliberately uses the documented RPC v2 signature until the SDK
// packages can be installed and locked in this deployment.
$params = array_merge([
'Format' => 'JSON',
'Version' => $version,
'AccessKeyId' => (string) $cfg['credentials']['access_key_id'],
'SignatureMethod' => 'HMAC-SHA1',
'SignatureNonce' => wp_generate_uuid4(),
'SignatureVersion' => '1.0',
'Timestamp' => gmdate('Y-m-d\TH:i:s\Z'),
'Action' => $action,
], $business);
ksort($params);
$canonical = http_build_query($params, '', '&', PHP_QUERY_RFC3986);
$string_to_sign = 'POST&%2F&' . rawurlencode($canonical);
$params['Signature'] = base64_encode(hash_hmac('sha1', $string_to_sign, (string) $cfg['credentials']['access_key_secret'] . '&', true));
$response = wp_remote_post($endpoint, [
'timeout' => 20,
'headers' => ['Content-Type' => 'application/x-www-form-urlencoded'],
'body' => $params,
]);
if (is_wp_error($response)) {
return new \WP_Error('aliyun_network_error', __('阿里云接口请求失败', 'aksrt'));
}
$decoded = json_decode((string) wp_remote_retrieve_body($response), true);
return is_array($decoded) ? $decoded : new \WP_Error('aliyun_invalid_json', __('阿里云返回格式无效', 'aksrt'));
}
private function enterprise_passed(array $response): bool {
$result = is_array($response['Result'] ?? null) ? $response['Result'] : [];
return (string) ($response['Code'] ?? '') === 'Success'
&& (string) ($result['BizCode'] ?? '') === '1'
&& (string) ($result['Status'] ?? '') === '1';
}
}