PHP 怎么身份联盟

wen PHP项目 3

本文目录导读:

PHP 怎么身份联盟

  1. 方法一:使用第三方库(最推荐、最省事)
  2. 方法二:基于 OIDC(OpenID Connect)手动实现(理解原理)
  3. 方法三:企业级(SAML 2.0)
  4. 关键安全要点(必须做)
  5. 总结建议

在 PHP 中实现身份联盟(Federation),通常指的是让用户使用其第三方账号(如 Google、微信、GitHub 等)或其他组织的身份系统(如企业 AD 账号)登录你的应用,同时你又能同步或创建对应的本地用户记录。

身份联盟的核心协议是 OAuth 2.0OpenID Connect (OIDC),以及企业场景中的 SAML

以下是 PHP 实现身份联盟的几种主流方案和核心步骤:


使用第三方库(最推荐、最省事)

对于大多数 PHP 项目(Laravel、Symfony 或原生),强烈建议使用封装完善的开源库,避免自己处理复杂的签名、加密和握手逻辑。

Laravel 框架:Socialite(OAuth) 或 Socialite Providers

  • 适合场景:对接 Google、GitHub、微信、QQ、Facebook 等社交账号。

  • 实现步骤

    1. 安装:composer require laravel/socialite

    2. config/services.php 配置第三方应用的 client_idclient_secret

    3. 创建路由,重定向到第三方:

       Route::get('/auth/redirect', function () {
           return Socialite::driver('google')->redirect();
       });
    4. 创建回调路由,获取用户信息并创建本地用户:

       Route::get('/auth/callback', function () {
           $user = Socialite::driver('google')->user();
           // 根据 $user->email 或 $user->getId() 查找或创建本地 User 模型
           $localUser = User::firstOrCreate(
               ['email' => $user->getEmail()],
               ['name' => $user->getName()]
           );
           Auth::login($localUser);
       });

非框架(原生 PHP)或企业级:league/oauth2-clientphpseclib

  • 适合场景:对接微软 Azure AD、Okta 或自建 OIDC 服务器。
  • 优点:轻量级,不依赖框架。
  • 实现:使用 league/oauth2-client 中的 GenericProvider 进行授权码流程。

基于 OIDC(OpenID Connect)手动实现(理解原理)

如果不使用框架,或者需要对接企业内部的 IdP(身份提供商),你需要手动处理以下流程:

请求授权(重定向到 IdP)

用户点击“使用企业账号登录”,你重定向到 IdP 的授权端点:

$params = [
    'client_id' => 'your_client_id',
    'redirect_uri' => 'https://your-app.com/callback',
    'response_type' => 'code',
    'scope' => 'openid profile email',
    'state' => bin2hex(random_bytes(16)), // 防止 CSRF
];
$url = 'https://login.microsoftonline.com/common/oauth2/v2.0/authorize?' . http_build_query($params);
header('Location: ' . $url);

处理回调(交换 Token)

用户同意后,IdP 带着 codestate 重定向到你的回调地址,你必须验证 state,然后向 Token 端点 POST 请求换取 access_tokenid_token

验证 ID Token(关键步骤)

ID Token 是一个 JWT,你必须严格验证它的签名和 nonce,而不能直接相信它。

use Firebase\JWT\JWT;
use Firebase\JWT\Key;
// 1. 从 IdP 的 discovery 端点获取公钥 (JWKS)
$jwks = file_get_contents('https://your-idp.com/.well-known/jwks.json');
$keys = json_decode($jwks, true);
// 2. 解析 id_token
$jwt = $idToken;
$payload = JWT::decode($jwt, new Key($publicKey, 'RS256'));
// 3. 必须验证的 Claims
if ($payload->aud !== 'your_client_id') {
    die('Invalid audience');
}
if ($payload->iss !== 'https://your-idp.com') {
    die('Invalid issuer');
}
if ($payload->exp < time()) {
    die('Token expired');
}
// 4. 使用 $payload->sub (用户唯一ID) 来绑定本地用户

本地用户同步(Provisioning)

身份联盟并不意味着你不需要本地数据库,通常你需要一个 users 表,并在其中存储 idp_provideridp_subject 字段,实现“外键”映射。

-- 核心表结构
CREATE TABLE users (
    id INT PRIMARY KEY AUTO_INCREMENT,
    email VARCHAR(255) UNIQUE,
    name VARCHAR(255),
    -- 本地自有字段
);
CREATE TABLE user_identities (
    id INT PRIMARY KEY AUTO_INCREMENT,
    user_id INT,
    provider VARCHAR(50), -- 'google', 'azure', 'github'
    provider_uid VARCHAR(255), -- IdP 返回的 sub 或 user id
    created_at TIMESTAMP
);

登录逻辑:先查 user_identities 表,provider_uid 存在则登录;不存在则创建新用户并插入关联。


企业级(SAML 2.0)

如果对接的是老牌企业系统(如使用 ADFS 或 Shibboleth),则使用 SAML 协议。

  • PHP 库:使用 onelogin/php-saml
  • 区别:SAML 使用 XML 和 SAMLResponse,流程通常是 SP(服务提供商)发起
  • 实现:配置 settings.json(包含 IdP 的实体 ID、SSO URL、X.509 证书),然后调用 $auth->login()$auth->processResponse()

关键安全要点(必须做)

在 PHP 中实现身份联盟时,最容易出安全问题的地方在于信任外部输入

  1. 只信任 HTTPS:确保所有授权码和 token 传输都走 HTTPS。
  2. 验证 state 参数:防止 CSRF 跨站请求伪造攻击。
  3. 验证 JWT 签名:必须从 IdP 的 JWKS 端点动态获取公钥,不能硬编码或跳过验证。
  4. 注意 email_verified:如果使用邮箱作为联合主键,第三方邮箱未验证可能会被用于账号接管(Account Takeover),建议无论何时都以 sub(用户唯一标识)作为关联本地用户的唯一键,而不是邮箱。
  5. 处理 Token 刷新access_token 会过期,需要提供 refresh_token 来延长会话。

总结建议

  • 有 Laravel? 直接用 Socialite,只写几行就能对接。
  • 原生 PHP?league/oauth2-clientfirebase/php-jwt + paragonie/sodium 处理。
  • 企业内部系统?onelogin/php-saml
  • 永远不要自己发明 OAuth/OIDC 协议,安全极难做对。

如果你能提供更具体的场景(比如要对接微信还是 Azure AD),我可以给你更精确的代码示例。

抱歉,评论功能暂时关闭!