feat: 接入腾讯云与阿里云实人认证
This commit is contained in:
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';
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user