多源身份认证的核心逻辑
多源身份认证的本质是将不同第三方平台的用户身份和本地系统用户做关联,核心流程分为三步:首先引导用户跳转到对应第三方平台的授权页面,获取用户授权后拿到平台的授权码;然后通过授权码向第三方平台换取用户的唯一标识和基本信息;最后将第三方用户标识和本地系统用户做绑定,实现免注册登录。

为了避免不同平台的用户标识冲突,我们通常会设计一张用户第三方账号关联表,存储第三方平台的类型、第三方用户唯一ID、本地用户ID这三个核心字段,这样同一个本地用户可以绑定多个不同平台的账号,也能通过第三方标识快速找到对应的本地用户。
通用授权基类设计
我们可以先封装一个通用的OAuth2授权基类,把获取授权地址、换取token、获取用户信息的公共逻辑抽离出来,后续不同平台的接入只需要继承这个基类实现各自的接口参数即可。
<?php
/**
* OAuth2通用授权基类
*/
abstract class OAuthBase
{
// 第三方平台配置
protected $appId;
protected $appSecret;
protected $redirectUri;
public function __construct($config)
{
$this->appId = $config['app_id'];
$this->appSecret = $config['app_secret'];
$this->redirectUri = $config['redirect_uri'];
}
/**
* 获取第三方授权跳转地址
* @return string
*/
abstract public function getAuthUrl();
/**
* 通过授权码换取access_token
* @param string $code 授权码
* @return array
*/
abstract public function getAccessToken($code);
/**
* 通过access_token获取用户信息
* @param string $accessToken
* @return array
*/
abstract public function getUserInfo($accessToken);
/**
* 发起HTTP请求的公共方法
* @param string $url 请求地址
* @param array $params 请求参数
* @param string $method 请求方式 GET/POST
* @return array
*/
protected function httpRequest($url, $params = [], $method = 'GET')
{
$ch = curl_init();
if ($method == 'GET' && !empty($params)) {
$url = $url . '?' . http_build_query($params);
}
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
if ($method == 'POST') {
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $params);
}
$response = curl_exec($ch);
curl_close($ch);
return json_decode($response, true);
}
}
?>
微信开放平台登录集成
微信开放平台的网站应用登录使用的是OAuth2授权模式,首先需要在微信开放平台注册应用,拿到AppID和AppSecret,配置好授权回调域名。
微信授权实现类
<?php
require_once 'OAuthBase.php';
class WechatOAuth extends OAuthBase
{
// 微信开放平台授权地址
private $authUrl = 'https://open.weixin.qq.com/connect/qrconnect';
// 换取token地址
private $tokenUrl = 'https://api.weixin.qq.com/sns/oauth2/access_token';
// 获取用户信息地址
private $userInfoUrl = 'https://api.weixin.qq.com/sns/userinfo';
public function getAuthUrl()
{
$params = [
'appid' => $this->appId,
'redirect_uri' => urlencode($this->redirectUri),
'response_type' => 'code',
'scope' => 'snsapi_login',
'state' => md5(uniqid(rand(), true))
];
return $this->authUrl . '?' . http_build_query($params);
}
public function getAccessToken($code)
{
$params = [
'appid' => $this->appId,
'secret' => $this->appSecret,
'code' => $code,
'grant_type' => 'authorization_code'
];
return $this->httpRequest($this->tokenUrl, $params);
}
public function getUserInfo($accessToken)
{
$params = [
'access_token' => $accessToken['access_token'],
'openid' => $accessToken['openid']
];
return $this->httpRequest($this->userInfoUrl, $params);
}
}
?>
钉钉开放平台登录集成
钉钉的第三方网站登录同样基于OAuth2,需要在钉钉开放平台创建H5微应用,拿到AppKey和AppSecret,配置回调地址。
钉钉授权实现类
<?php
require_once 'OAuthBase.php';
class DingTalkOAuth extends OAuthBase
{
// 钉钉授权地址
private $authUrl = 'https://oapi.dingtalk.com/connect/oauth2/sns_authorize';
// 换取token地址
private $tokenUrl = 'https://oapi.dingtalk.com/sns/gettoken';
// 获取用户持久授权码地址
private $persistentCodeUrl = 'https://oapi.dingtalk.com/sns/get_persistent_code';
// 获取用户sns信息地址
private $snsInfoUrl = 'https://oapi.dingtalk.com/sns/get_sns_info_by_persistent_code';
public function getAuthUrl()
{
$params = [
'appid' => $this->appId,
'response_type' => 'code',
'redirect_uri' => urlencode($this->redirectUri),
'scope' => 'snsapi_login',
'state' => md5(uniqid(rand(), true))
];
return $this->authUrl . '?' . http_build_query($params);
}
public function getAccessToken($code)
{
// 第一步:获取sns_token
$tokenParams = [
'appid' => $this->appId,
'appsecret' => $this->appSecret
];
$tokenRes = $this->httpRequest($this->tokenUrl, $tokenParams);
if ($tokenRes['errcode'] != 0) {
return [];
}
$snsToken = $tokenRes['access_token'];
// 第二步:获取用户持久授权码
$codeParams = [
'tmp_auth_code' => $code
];
$header = ['Content-Type: application/json', 'access_token:' . $snsToken];
$persistentRes = $this->httpRequest($this->persistentCodeUrl, json_encode($codeParams), 'POST');
if ($persistentRes['errcode'] != 0) {
return [];
}
// 第三步:获取用户sns信息
$snsParams = [
'openid' => $persistentRes['openid'],
'persistent_code' => $persistentRes['persistent_code']
];
$userRes = $this->httpRequest($this->snsInfoUrl, json_encode($snsParams), 'POST');
return [
'access_token' => $snsToken,
'openid' => $userRes['user_info']['openid'],
'nick' => $userRes['user_info']['nick'],
'avatar' => $userRes['user_info']['avatar']
];
}
public function getUserInfo($accessToken)
{
// 钉钉在getAccessToken中已经返回用户信息,这里直接返回即可
return $accessToken;
}
}
?>
飞书开放平台登录集成
飞书的网页应用登录也遵循OAuth2规范,需要在飞书开放平台创建应用,获取App ID和App Secret,配置重定向地址。
飞书授权实现类
<?php
require_once 'OAuthBase.php';
class FeishuOAuth extends OAuthBase
{
// 飞书授权地址
private $authUrl = 'https://open.feishu.cn/open-apis/authen/v1/index';
// 换取token地址
private $tokenUrl = 'https://open.feishu.cn/open-apis/authen/v1/access_token';
// 获取用户信息地址
private $userInfoUrl = 'https://open.feishu.cn/open-apis/authen/v1/user_info';
public function getAuthUrl()
{
$params = [
'app_id' => $this->appId,
'redirect_uri' => urlencode($this->redirectUri),
'response_type' => 'code',
'scope' => 'user:base',
'state' => md5(uniqid(rand(), true))
];
return $this->authUrl . '?' . http_build_query($params);
}
public function getAccessToken($code)
{
$params = [
'app_id' => $this->appId,
'app_secret' => $this->appSecret,
'code' => $code,
'grant_type' => 'authorization_code'
];
$header = ['Content-Type: application/json'];
return $this->httpRequest($this->tokenUrl, json_encode($params), 'POST');
}
public function getUserInfo($accessToken)
{
$header = ['Authorization: Bearer ' . $accessToken['access_token']];
$res = $this->httpRequest($this->userInfoUrl, [], 'GET');
return [
'open_id' => $res['data']['user']['open_id'],
'nick_name' => $res['data']['user']['name'],
'avatar_url' => $res['data']['user']['avatar_url']
];
}
}
?>
多源身份统一关联实现
有了各个平台的授权类之后,我们需要实现用户身份的关联逻辑,这里以用户点击微信登录为例,完整的处理流程如下:
<?php
// 1. 用户点击微信登录,跳转到授权页面
$wechatConfig = [
'app_id' => '你的微信AppID',
'app_secret' => '你的微信AppSecret',
'redirect_uri' => 'http://ipipp.com/callback.php?type=wechat'
];
$wechatOAuth = new WechatOAuth($wechatConfig);
$authUrl = $wechatOAuth->getAuthUrl();
header('Location:' . $authUrl);
exit;
// 2. 回调处理页面 callback.php
$type = $_GET['type'] ?? '';
$code = $_GET['code'] ?? '';
// 根据类型实例化对应的授权类
switch ($type) {
case 'wechat':
$config = ['app_id' => '微信AppID', 'app_secret' => '微信AppSecret', 'redirect_uri' => '回调地址'];
$oauth = new WechatOAuth($config);
break;
case 'dingtalk':
$config = ['app_id' => '钉钉AppKey', 'app_secret' => '钉钉AppSecret', 'redirect_uri' => '回调地址'];
$oauth = new DingTalkOAuth($config);
break;
case 'feishu':
$config = ['app_id' => '飞书AppID', 'app_secret' => '飞书AppSecret', 'redirect_uri' => '回调地址'];
$oauth = new FeishuOAuth($config);
break;
default:
die('不支持的登录类型');
}
// 获取token和用户信息
$accessToken = $oauth->getAccessToken($code);
$userInfo = $oauth->getUserInfo($accessToken);
// 3. 关联本地用户
// 假设第三方用户唯一标识字段:微信是openid,钉钉是openid,飞书是open_id
$thirdUid = $userInfo['openid'] ?? $userInfo['open_id'];
// 查询是否已经绑定过本地用户
$bindUser = $db->query("SELECT user_id FROM user_third_bind WHERE third_type = '{$type}' AND third_uid = '{$thirdUid}'");
if ($bindUser) {
// 已经绑定,直接登录
$userId = $bindUser['user_id'];
// 写入登录状态逻辑
} else {
// 未绑定,创建新用户或者引导绑定已有账号
$newUserId = $db->insert("INSERT INTO user (nickname, avatar) VALUES ('{$userInfo['nick']}', '{$userInfo['avatar']}')");
$db->insert("INSERT INTO user_third_bind (user_id, third_type, third_uid) VALUES ({$newUserId}, '{$type}', '{$thirdUid}')");
// 写入登录状态逻辑
}
?>
注意事项
- 所有第三方平台的回调地址必须和开放平台配置的一致,否则无法拿到授权码
- 不同平台的用户唯一标识字段名称不同,需要做适配,避免标识冲突
- 授权过程中建议加上state参数做csrf防护,避免授权被劫持
- 第三方用户的基本信息如头像、昵称可能会变更,建议定期同步更新本地存储的信息
- 生产环境需要做好curl请求的错误处理,避免第三方接口异常导致系统报错