本文目录导读:

我来详细介绍PHP实现OIDC(OpenID Connect)认证的几种方法:
使用官方OIDC库
使用 jumbojett/openid-connect-php(最流行)
// 安装 composer require jumbojett/openid-connect-php
<?php
require_once 'vendor/autoload.php';
use Jumbojett\OpenIDConnectClient;
// 初始化OIDC客户端
$oidc = new OpenIDConnectClient(
'https://your-provider.com', // 认证服务器URL
'your-client-id', // 客户端ID
'your-client-secret' // 客户端密钥
);
// 配置作用域
$oidc->addScope(['openid', 'profile', 'email']);
// 设置重定向URI
$oidc->setRedirectURL('https://your-app.com/callback.php');
// 认证用户
$oidc->authenticate();
// 获取用户信息
$userInfo = $oidc->requestUserInfo();
$name = $userInfo->name;
$email = $userInfo->email;
// 获取ID Token
$idToken = $oidc->getIdToken();
回调处理示例
<?php
// callback.php
session_start();
require_once 'vendor/autoload.php';
use Jumbojett\OpenIDConnectClient;
try {
$oidc = new OpenIDConnectClient(
'https://your-provider.com',
'your-client-id',
'your-client-secret'
);
$oidc->setRedirectURL('https://your-app.com/callback.php');
// 处理认证回调
$oidc->authenticate();
// 验证并获取用户信息
$userInfo = $oidc->requestUserInfo();
// 保存用户会话
$_SESSION['user'] = [
'name' => $userInfo->name,
'email' => $userInfo->email,
'sub' => $userInfo->sub
];
header('Location: dashboard.php');
exit;
} catch (Exception $e) {
// 错误处理
error_log('OIDC Authentication Error: ' . $e->getMessage());
header('Location: error.php');
exit;
}
使用Laravel框架
使用 laravel/socialite
// 安装 composer require laravel/socialite
// app/Services/OIDCService.php
<?php
namespace App\Services;
use Laravel\Socialite\Facades\Socialite;
class OIDCService
{
public function redirectToProvider()
{
return Socialite::driver('oidc')->redirect();
}
public function handleProviderCallback()
{
try {
$user = Socialite::driver('oidc')->user();
// 获取用户信息
$name = $user->getName();
$email = $user->getEmail();
// 存储用户信息到数据库
// ...
return $user;
} catch (\Exception $e) {
throw new \Exception('OIDC认证失败: ' . $e->getMessage());
}
}
}
配置文件 config/services.php
'oidc' => [
'client_id' => env('OIDC_CLIENT_ID'),
'client_secret' => env('OIDC_CLIENT_SECRET'),
'redirect' => env('OIDC_REDIRECT_URI'),
'authorize_url' => env('OIDC_AUTHORIZE_URL'),
'token_url' => env('OIDC_TOKEN_URL'),
'userinfo_url' => env('OIDC_USERINFO_URL'),
'jwks_url' => env('OIDC_JWKS_URL'),
],
手动实现OIDC认证
完整的OIDC流程实现
<?php
// OIDCClient.php
class OIDCClient {
private $clientId;
private $clientSecret;
private $authorizeUrl;
private $tokenUrl;
private $userInfoUrl;
private $redirectUri;
private $jwksUrl;
public function __construct($config) {
$this->clientId = $config['client_id'];
$this->clientSecret = $config['client_secret'];
$this->authorizeUrl = $config['authorize_url'];
$this->tokenUrl = $config['token_url'];
$this->userInfoUrl = $config['userinfo_url'];
$this->redirectUri = $config['redirect_uri'];
$this->jwksUrl = $config['jwks_url'];
}
// 生成认证URL
public function getAuthorizationUrl() {
$params = [
'response_type' => 'code',
'client_id' => $this->clientId,
'redirect_uri' => $this->redirectUri,
'scope' => 'openid profile email',
'state' => $this->generateState(),
'nonce' => bin2hex(random_bytes(16))
];
// 保存state到session用于CSRF防护
$_SESSION['oidc_state'] = $params['state'];
$_SESSION['oidc_nonce'] = $params['nonce'];
return $this->authorizeUrl . '?' . http_build_query($params);
}
// 处理回调
public function handleCallback($code, $state) {
// 验证state
if ($state !== ($_SESSION['oidc_state'] ?? '')) {
throw new Exception('Invalid state parameter');
}
// 交换code获取token
$tokens = $this->exchangeCode($code);
// 验证ID Token
$claims = $this->verifyIdToken($tokens['id_token']);
// 获取用户信息
$userInfo = $this->getUserInfo($tokens['access_token']);
return [
'tokens' => $tokens,
'claims' => $claims,
'user_info' => $userInfo
];
}
// 交换认证码
private function exchangeCode($code) {
$ch = curl_init($this->tokenUrl);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, [
'grant_type' => 'authorization_code',
'code' => $code,
'redirect_uri' => $this->redirectUri,
'client_id' => $this->clientId,
'client_secret' => $this->clientSecret
]);
$response = curl_exec($ch);
curl_close($ch);
return json_decode($response, true);
}
// 验证JWT
private function verifyIdToken($idToken) {
$claims = $this->decodeJWTPayload($idToken);
// 验证claims
if ($claims['iss'] !== $this->authorizeUrl) {
throw new Exception('Invalid issuer');
}
if (time() < $claims['iat'] || time() > $claims['exp']) {
throw new Exception('Token expired');
}
if ($claims['aud'] !== $this->clientId) {
throw new Exception('Invalid audience');
}
// 验证nonce
if ($claims['nonce'] !== ($_SESSION['oidc_nonce'] ?? '')) {
throw new Exception('Invalid nonce');
}
return $claims;
}
// 解码JWT
private function decodeJWTPayload($jwt) {
$parts = explode('.', $jwt);
$payload = base64_decode(str_replace(['-', '_'], ['+', '/'], $parts[1]));
return json_decode($payload, true);
}
// 获取用户信息
private function getUserInfo($accessToken) {
$ch = curl_init($this->userInfoUrl);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Authorization: Bearer ' . $accessToken
]);
$response = curl_exec($ch);
curl_close($ch);
return json_decode($response, true);
}
private function generateState() {
return bin2hex(random_bytes(16));
}
}
验证JWT令牌
使用 firebase/php-jwt
<?php
// 安装
composer require firebase/php-jwt
// JWT验证示例
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
class JWTVerifier {
private $jwks;
private $issuer;
private $audience;
public function verify($token) {
$tks = explode('.', $token);
$header = json_decode(base64_decode($tks[0]), true);
$payload = json_decode(base64_decode($tks[1]), true);
// 获取匹配的密钥
$key = $this->getKeyFromJWKS($header['kid']);
try {
$decoded = JWT::decode($token, new Key($key['n'], 'RS256'));
// 验证其他claims
return $this->verifyClaims($decoded);
} catch (Exception $e) {
throw new Exception('JWT验证失败: ' . $e->getMessage());
}
}
private function verifyClaims($token) {
// 验证发行者
if ($token->iss !== $this->issuer) {
throw new Exception('无效的发行者');
}
// 验证受众
if (isset($token->aud) && !in_array($this->audience, (array)$token->aud)) {
throw new Exception('无效的受众');
}
// 验证过期时间
if (isset($token->exp) && time() >= $token->exp) {
throw new Exception('令牌已过期');
}
return $token;
}
}
完整认证流程示例
<?php
// index.php - 登录入口
session_start();
require_once 'OIDCClient.php';
$config = [
'client_id' => 'your-client-id',
'client_secret' => 'your-client-secret',
'authorize_url' => 'https://provider.com/authorize',
'token_url' => 'https://provider.com/token',
'userinfo_url' => 'https://provider.com/userinfo',
'redirect_uri' => 'https://your-app.com/callback.php',
'jwks_url' => 'https://provider.com/.well-known/jwks.json'
];
$auth = new OIDCClient($config);
// 检查用户是否已认证
if (isset($_SESSION['user'])) {
echo "欢迎, " . $_SESSION['user']['name'];
} else {
$authUrl = $auth->getAuthorizationUrl();
echo "<a href='{$authUrl}'>使用OIDC登录</a>";
}
<?php
// callback.php - 回调处理
session_start();
require_once 'OIDCClient.php';
$config = [...]; // 同上的配置
$auth = new OIDCClient($config);
try {
if (isset($_GET['code'])) {
$result = $auth->handleCallback($_GET['code'], $_GET['state']);
$_SESSION['user'] = [
'sub' => $result['user_info']['sub'],
'name' => $result['user_info']['name'] ?? '',
'email' => $result['user_info']['email'] ?? '',
'access_token' => $result['tokens']['access_token']
];
header('Location: dashboard.php');
} else if (isset($_GET['error'])) {
throw new Exception('认证失败: ' . $_GET['error_description']);
}
} catch (Exception $e) {
echo "错误: " . $e->getMessage();
}
最佳实践建议
-
安全考虑
- 始终使用HTTPS
- 验证state和nonce参数
- 妥善保管client_secret
- 使用短生命周期的token
-
性能优化
- 缓存JWKS密钥
- 使用Redis等缓存token验证结果
-
错误处理
- 统一错误处理机制
- 记录详细日志
-
测试
- 使用Mock Provider测试
- 集成测试认证流程
选择哪种方法取决于你的PHP框架和使用场景,生产环境建议使用成熟的库(如第一个方案)以获得更好的安全性和可靠性。