getAccessToken($_GET['state'] ?? '', $code, $_GET['state'] ?? null); $userInfo = $oauth->getUserInfo(); return [ 'provider' => 'baidu', 'provider_id' => (string) ($userInfo['userid'] ?? ''), 'username' => $userInfo['username'] ?? '', 'name' => $userInfo['realname'] ?? $userInfo['username'] ?? '', 'email' => $userInfo['email'] ?? '', 'avatar_url' => $userInfo['portrait'] ? ('https://himg.bdimg.com/sys/portrait/item/' . $userInfo['portrait'] . '.jpg') : '', ]; } private function get_alipay_user_data(array $config, string $code, string $redirect_uri): array { $oauth = new \Yurun\OAuthLogin\Alipay\OAuth2( $config['client_id'], $config['client_secret'], $redirect_uri ); $oauth->getAccessToken($_GET['state'] ?? '', $code, $_GET['state'] ?? null); $userInfo = $oauth->getUserInfo(); return [ 'provider' => 'alipay', 'provider_id' => (string) ($userInfo['user_id'] ?? ''), 'username' => $userInfo['user_name'] ?? '', 'name' => $userInfo['user_name'] ?? '', 'email' => $userInfo['email'] ?? '', 'avatar_url' => $userInfo['avatar'] ?? '', 'gender' => ($userInfo['gender'] ?? '') === 'm' ? 'male' : (($userInfo['gender'] ?? '') === 'f' ? 'female' : ''), ]; } private function get_google_auth_url(array $config, string $redirect_uri, string $state): string { $params = http_build_query([ 'client_id' => $config['client_id'], 'redirect_uri' => $redirect_uri, 'response_type' => 'code', 'scope' => 'openid email profile', 'state' => $state, 'access_type' => 'offline', ]); return 'https://accounts.google.com/o/oauth2/v2/auth?' . $params; } private function get_google_user_data(array $config, string $code, string $redirect_uri): array { // 换取 token $response = wp_remote_post('https://oauth2.googleapis.com/token', [ 'body' => [ 'code' => $code, 'client_id' => $config['client_id'], 'client_secret' => $config['client_secret'], 'redirect_uri' => $redirect_uri, 'grant_type' => 'authorization_code', ], ]); if (is_wp_error($response)) { throw new \Exception($response->get_error_message()); } $token_data = json_decode(wp_remote_retrieve_body($response), true); if (empty($token_data['access_token'])) { throw new \Exception(__('Google token 获取失败', 'aksrt')); } // 获取用户信息 $user_response = wp_remote_get('https://www.googleapis.com/oauth2/v2/userinfo', [ 'headers' => ['Authorization' => 'Bearer ' . $token_data['access_token']], ]); if (is_wp_error($user_response)) { throw new \Exception($user_response->get_error_message()); } $user_info = json_decode(wp_remote_retrieve_body($user_response), true); return [ 'provider' => 'google', 'provider_id' => (string) ($user_info['id'] ?? ''), 'username' => $user_info['email'] ?? '', 'name' => $user_info['name'] ?? '', 'email' => $user_info['email'] ?? '', 'email_verified' => !empty($user_info['verified_email']), 'avatar_url' => $user_info['picture'] ?? '', 'locale' => $user_info['locale'] ?? '', ]; } /** * 微信公众号 OAuth2 授权 URL * * 使用 snsapi_userinfo scope 获取用户信息(需已认证服务号或订阅号+验证)。 */ private function get_weixingzh_auth_url(array $config, string $redirect_uri, string $state): string { $params = http_build_query([ 'appid' => $config['client_id'], 'redirect_uri' => $redirect_uri, 'response_type' => 'code', 'scope' => 'snsapi_userinfo', 'state' => $state, ]); return 'https://open.weixin.qq.com/connect/oauth2/authorize?' . $params . '#wechat_redirect'; } /** * 微信公众号 OAuth2 获取用户信息 * * 1. code → access_token + openid * 2. access_token + openid → userinfo */ private function get_weixingzh_user_data(array $config, string $code, string $redirect_uri): array { // 1. 换取 access_token $token_url = add_query_arg([ 'appid' => $config['client_id'], 'secret' => $config['client_secret'], 'code' => $code, 'grant_type' => 'authorization_code', ], 'https://api.weixin.qq.com/sns/oauth2/access_token'); $response = wp_remote_get($token_url); if (is_wp_error($response)) { throw new \Exception($response->get_error_message()); } $token_data = json_decode(wp_remote_retrieve_body($response), true); if (empty($token_data['access_token']) || empty($token_data['openid'])) { $errmsg = $token_data['errmsg'] ?? __('微信公众号 token 获取失败', 'aksrt'); throw new \Exception($errmsg); } // 2. 获取用户信息 $user_url = add_query_arg([ 'access_token' => $token_data['access_token'], 'openid' => $token_data['openid'], 'lang' => 'zh_CN', ], 'https://api.weixin.qq.com/sns/userinfo'); $user_response = wp_remote_get($user_url); if (is_wp_error($user_response)) { throw new \Exception($user_response->get_error_message()); } $user_info = json_decode(wp_remote_retrieve_body($user_response), true); return [ 'provider' => 'weixingzh', 'provider_id' => (string) ($user_info['openid'] ?? $token_data['openid']), 'username' => $user_info['nickname'] ?? '', 'name' => $user_info['nickname'] ?? '', 'email' => '', 'email_verified' => false, 'avatar_url' => $user_info['headimgurl'] ?? '', 'locale' => 'zh_CN', 'unionid' => $user_info['unionid'] ?? '', ]; } private function resolve_callback_provider(): string { $state = $_GET['state'] ?? ''; if ($state === '') return ''; $rest = (strpos($state, 'bind_') === 0) ? substr($state, 5) : $state; $sep = strpos($rest, '_'); if ($sep !== false && $sep < strlen($rest) - 1) { return sanitize_key(substr($rest, 0, $sep)); } return ''; } private function get_enabled_providers(): array { if (class_exists('\AKSRT\Settings\Settings') && \AKSRT\Settings\Settings::oauth_proxy_mode() === 'client') { return \AKSRT\Settings\Settings::oauth_proxy_client_providers(); } $providers = get_option('aksrt_oauth', []); $enabled = []; foreach (['github', 'gitee', 'qq', 'google', 'weibo', 'weixin', 'baidu', 'alipay', 'weixingzh'] as $key) { if (!empty($providers[$key]['enabled']) && !empty($providers[$key]['client_id'])) { $enabled[] = $key; continue; } // BC: also check aksrt_settings flat keys if (class_exists('\AKSRT\Settings\Settings')) { $s = \AKSRT\Settings\Settings::get_all(); $switch_key = 'oauth_' . $key . '_s'; if (!empty($s[$switch_key])) { $id_key = $key . '_appid'; $sec_key = $key . '_appsecret'; if ($key === 'weixingzh') { $sec_key = 'weixingzh_appsecret'; } elseif ($key === 'github' || $key === 'gitee' || $key === 'qq') { $sec_key = $key . '_appkey'; } if (!empty($s[$id_key]) && !empty($s[$sec_key])) { $enabled[] = $key; } } } } // Rainbow aggregate (clogin) providers as virtual keys: clogin_qq, clogin_weixin... if (class_exists('\AKSRT\Settings\Settings')) { $s = \AKSRT\Settings\Settings::get_all(); if (!empty($s['clogin_s']) && !empty($s['clogin_url']) && !empty($s['clogin_id']) && !empty($s['clogin_key'])) { $raw = $s['clogin_oauth_s'] ?? $s['clogin_types'] ?? ['qq', 'weixin', 'weibo', 'alipay']; if (is_string($raw)) { $raw = preg_split('/[,,\s]+/', $raw) ?: []; } if (!is_array($raw)) { $raw = []; } foreach ($raw as $ctype) { $ctype = sanitize_key((string) $ctype); if ($ctype === '') { continue; } $enabled[] = 'clogin_' . $ctype; } } } return array_values(array_unique($enabled)); } /** * 获取 OAuth 提供商数据(供模板使用) * 返回 [{name, label, icon, url}, ...] */ public function get_auth_providers_data(): array { $platform_names = [ 'github' => 'GitHub', 'gitee' => 'Gitee', 'qq' => 'QQ', 'google' => 'Google', 'weibo' => 'Weibo', 'weixin' => 'WeChat', 'baidu' => 'Baidu', 'alipay' => 'Alipay', 'weixingzh' => '微信公众号', ]; $result = []; foreach ($this->get_enabled_providers() as $key) { $url = $this->get_auth_url($key); if (empty($url)) continue; $label = $platform_names[$key] ?? ''; $icon_key = $key; if (str_starts_with($key, 'clogin_')) { $ctype = substr($key, 7); $label = ($platform_names[$ctype] ?? strtoupper($ctype)) . ' · 聚合'; $icon_key = $ctype === 'weixin' ? 'weixin' : $ctype; } if ($label === '') { $label = ucfirst($key); } $result[] = [ 'name' => $key, 'label' => $label, 'icon' => function_exists('aksrt_brand_icon') ? aksrt_brand_icon((string) $icon_key) : '', 'url' => esc_url($url), ]; } return $result; } private function get_provider_config(string $provider): ?array { $providers = get_option('aksrt_oauth', []); $config = $providers[$provider] ?? null; if (!empty($config['client_id']) && !empty($config['client_secret'])) { return $config; } // BC: fallback to aksrt_settings flat keys if (class_exists('\AKSRT\Settings\Settings')) { $s = \AKSRT\Settings\Settings::get_all(); $id_key = $provider . '_appid'; $sec_key = $provider . '_appsecret'; if ($provider === 'github' || $provider === 'gitee' || $provider === 'qq') { $sec_key = $provider . '_appkey'; } elseif ($provider === 'google') { $id_key = 'google_client_id'; $sec_key = 'google_client_secret'; } $client_id = (string) ($s[$id_key] ?? ''); $client_secret = (string) ($s[$sec_key] ?? ''); if ($client_id !== '' && $client_secret !== '') { return [ 'client_id' => $client_id, 'client_secret' => $client_secret, 'redirect_uri' => home_url('/oauth/callback/'), ]; } } return null; } private function find_or_create_user($provider, $user_data) { $email = $user_data['email'] ?? ''; if (!empty($user_data['provider_id'])) { $by_provider = $this->find_user_by_provider($provider, (string) $user_data['provider_id']); if ($by_provider instanceof \WP_User) { $bindings = get_user_meta($by_provider->ID, 'aksrt_oauth_bindings', true) ?: []; if (!is_array($bindings)) $bindings = []; if (!in_array($provider, $bindings, true)) { $bindings[] = $provider; update_user_meta($by_provider->ID, 'aksrt_oauth_bindings', $bindings); } if (!empty($user_data['avatar_url'])) { update_user_meta($by_provider->ID, 'aksrt_oauth_avatar', esc_url_raw($user_data['avatar_url'])); } return $by_provider; } } if (!empty($email)) { $existing = get_user_by('email', $email); if ($existing && !$existing instanceof \WP_Error) { $bindings = get_user_meta($existing->ID, 'aksrt_oauth_bindings', true) ?: []; if (!is_array($bindings)) $bindings = []; if (!in_array($provider, $bindings, true)) { $bindings[] = $provider; update_user_meta($existing->ID, 'aksrt_oauth_bindings', $bindings); } if (!empty($user_data['provider_id'])) { update_user_meta($existing->ID, "aksrt_{$provider}_id", (string) $user_data['provider_id']); } if (!empty($user_data['avatar_url'])) { update_user_meta($existing->ID, 'aksrt_oauth_avatar', esc_url_raw($user_data['avatar_url'])); } return $existing; } } return new \WP_Error( 'oauth_no_match', __('需要绑定已有账号', 'aksrt'), [ 'provider' => $provider, 'user_data' => $user_data, ] ); } // ============================================ // 前端UI方法 // ============================================ public function login_button(): void { echo $this->render_social_buttons(__('使用以下方式快速登录', 'aksrt')); } public function register_button(): void { echo $this->render_social_buttons(__('使用以下方式快速注册', 'aksrt')); } public function render_social_buttons(string $title = ''): string { $items = $this->get_auth_providers_data(); if (empty($items)) { return ''; } ob_start(); ?>
render_social_buttons(); } public function login_message(string $message): string { return $message . self::get_instance()->render_social_buttons(); } public function register_message(string $message): string { return $message . self::get_instance()->render_social_buttons(__('或使用社交账号快速注册', 'aksrt')); } /** * @return array{enabled:bool,url:string,id:string,key:string,types:string[]} */ private function get_clogin_settings(): array { $s = class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::get_all() : []; $raw = $s['clogin_oauth_s'] ?? $s['clogin_types'] ?? ['qq', 'weixin', 'weibo', 'alipay']; if (is_string($raw)) { $raw = preg_split('/[,,\s]+/', $raw) ?: []; } if (!is_array($raw)) { $raw = []; } $types = []; foreach ($raw as $t) { $t = sanitize_key((string) $t); if ($t !== '') { $types[] = $t; } } return [ 'enabled' => !empty($s['clogin_s']), 'url' => esc_url_raw((string) ($s['clogin_url'] ?? '')), 'id' => (string) ($s['clogin_id'] ?? ''), 'key' => (string) ($s['clogin_key'] ?? ''), 'types' => array_values(array_unique($types)), ]; } private function get_clogin_login_url(string $type, string $action = 'login'): string { $cfg = $this->get_clogin_settings(); $type = sanitize_key($type); if (!$cfg['enabled'] || $cfg['url'] === '' || $cfg['id'] === '' || $cfg['key'] === '' || $type === '') { return ''; } if (!empty($cfg['types']) && !in_array($type, $cfg['types'], true)) { return ''; } $args = ['type' => $type]; if ($action === 'bind' && is_user_logged_in()) { $args['bind'] = 1; } $rurl = isset($_GET['redirect_to']) ? esc_url_raw(wp_unslash((string) $_GET['redirect_to'])) : ''; if ($rurl !== '') { $args['rurl'] = $rurl; } return add_query_arg($args, home_url('/oauth/clogin/')); } public function handle_clogin_routes(): void { $route = get_query_var('aksrt_oauth'); if ($route === 'clogin') { $this->handle_clogin_login(); return; } if ($route === 'clogin_callback') { $this->handle_clogin_callback(); } } private function handle_clogin_login(): void { $cfg = $this->get_clogin_settings(); $type = isset($_GET['type']) ? sanitize_key(wp_unslash((string) $_GET['type'])) : ''; if (!$cfg['enabled'] || $cfg['url'] === '' || $cfg['id'] === '' || $cfg['key'] === '' || $type === '') { wp_die(esc_html__('暂未启用此登录方式', 'aksrt')); } if (!empty($cfg['types']) && !in_array($type, $cfg['types'], true)) { wp_die(esc_html__('此登录方式未启用', 'aksrt')); } if (!class_exists(CLoginClient::class)) { wp_die(esc_html__('聚合登录组件未加载', 'aksrt')); } $client = new CLoginClient([ 'apiurl' => $cfg['url'], 'appid' => $cfg['id'], 'appkey' => $cfg['key'], 'callback' => home_url('/oauth/clogin/callback'), ]); $resp = $client->login($type); if (!empty($resp['code']) && (int) $resp['code'] !== 0 && !empty($resp['msg'])) { wp_die(esc_html(sprintf(__('处理失败:%s', 'aksrt'), (string) $resp['msg']))); } if (empty($resp['url'])) { wp_die(esc_html__('无法获取聚合登录地址', 'aksrt')); } if (!session_id()) { @session_start(); } $_SESSION['AKSRT_CLOGIN_STATE'] = $client->state; $_SESSION['AKSRT_CLOGIN_TYPE'] = $type; $_SESSION['AKSRT_CLOGIN_RURL'] = !empty($_GET['rurl']) ? esc_url_raw(wp_unslash((string) $_GET['rurl'])) : ''; $_SESSION['AKSRT_CLOGIN_BIND'] = !empty($_GET['bind']) ? 1 : 0; wp_redirect(esc_url_raw((string) $resp['url'])); exit; } private function handle_clogin_callback(): void { if (!session_id()) { @session_start(); } $state = isset($_GET['state']) ? (string) wp_unslash($_GET['state']) : ''; $code = isset($_GET['code']) ? (string) wp_unslash($_GET['code']) : ''; if ($code === '' || $state === '' || empty($_SESSION['AKSRT_CLOGIN_STATE']) || !hash_equals((string) $_SESSION['AKSRT_CLOGIN_STATE'], $state)) { wp_die(esc_html__('环境异常,state 验证失败,请刷新后重试', 'aksrt')); } $cfg = $this->get_clogin_settings(); if (!$cfg['enabled']) { wp_die(esc_html__('暂未启用此功能', 'aksrt')); } $client = new CLoginClient([ 'apiurl' => $cfg['url'], 'appid' => $cfg['id'], 'appkey' => $cfg['key'], ]); $info = $client->callback($code); if (!empty($info['code']) && (int) $info['code'] !== 0 && !empty($info['msg'])) { wp_die(esc_html((string) $info['msg'])); } $openid = (string) ($info['social_uid'] ?? ''); $api_type = (string) ($info['type'] ?? ($_SESSION['AKSRT_CLOGIN_TYPE'] ?? '')); $provider_type = $client->from_api_type($api_type); if ($openid === '' || $provider_type === '') { wp_die(esc_html__('无法获取用户信息', 'aksrt')); } $provider = 'clogin_' . sanitize_key($provider_type); $user_data = [ 'provider' => $provider, 'provider_id' => $openid, 'username' => (string) ($info['nickname'] ?? ''), 'name' => (string) ($info['nickname'] ?? ''), 'email' => '', 'avatar_url' => (string) ($info['faceimg'] ?? ''), ]; if ($user_data['avatar_url'] !== '' && str_contains($user_data['avatar_url'], 'no_portrait')) { $user_data['avatar_url'] = ''; } $is_bind = !empty($_SESSION['AKSRT_CLOGIN_BIND']); $rurl = !empty($_SESSION['AKSRT_CLOGIN_RURL']) ? (string) $_SESSION['AKSRT_CLOGIN_RURL'] : ''; unset($_SESSION['AKSRT_CLOGIN_STATE'], $_SESSION['AKSRT_CLOGIN_TYPE'], $_SESSION['AKSRT_CLOGIN_RURL'], $_SESSION['AKSRT_CLOGIN_BIND']); try { if ($is_bind) { $uid = get_current_user_id(); if ($uid <= 0) { wp_safe_redirect(function_exists('aksrt_get_login_url') ? aksrt_get_login_url() : wp_login_url()); exit; } $existing = $this->find_user_by_provider($provider, $openid); if ($existing && (int) $existing->ID !== $uid) { throw new \Exception(__('该第三方账号已被其他用户绑定', 'aksrt')); } $this->bind_provider_to_user($uid, $provider, $user_data); $redirect = function_exists('aksrt_get_user_center_url') ? aksrt_get_user_center_url() : home_url('/'); wp_safe_redirect(add_query_arg(['tab' => 'settings', 'oauth_action' => 'bind', 'oauth_bound' => $provider], $redirect)); exit; } $user = $this->find_or_create_user($provider, $user_data); if (is_wp_error($user) && $user->get_error_code() === 'oauth_no_match') { // Auto-create account for aggregate login when no match (zibll-like). $login = 'u' . wp_generate_password(10, false, false); $uid = wp_insert_user([ 'user_login' => $login, 'user_pass' => wp_generate_password(24, true, true), 'display_name' => $user_data['name'] !== '' ? $user_data['name'] : $login, 'nickname' => $user_data['name'] !== '' ? $user_data['name'] : $login, 'role' => get_option('default_role', 'subscriber'), ]); if (is_wp_error($uid)) { throw new \Exception($uid->get_error_message()); } $this->bind_provider_to_user((int) $uid, $provider, $user_data); $user = get_user_by('id', (int) $uid); } if (is_wp_error($user) || !$user instanceof \WP_User) { throw new \Exception(is_wp_error($user) ? $user->get_error_message() : __('登录失败', 'aksrt')); } wp_set_current_user($user->ID); wp_set_auth_cookie($user->ID, true); do_action('wp_login', $user->user_login, $user); $redirect = $rurl !== '' ? $rurl : (function_exists('aksrt_get_user_center_url') ? aksrt_get_user_center_url() : home_url('/')); wp_safe_redirect(add_query_arg(['oauth_action' => 'login', 'oauth_bound' => $provider], $redirect)); exit; } catch (\Exception $e) { wp_die(esc_html__('登录失败:', 'aksrt') . '