本文目录导读:

我来为您详细讲解如何在PHP项目中实现OpenID Connect认证。
OpenID Connect基础概念
OpenID Connect (OIDC) 是建立在OAuth 2.0之上的身份认证层,主要包含:
- ID Token:JWT格式的身份令牌
- Access Token:访问令牌
- Refresh Token:刷新令牌
- UserInfo Endpoint:用户信息端点
使用官方库实现(推荐)
使用 league/oauth2-client 库
composer require league/oauth2-client
使用 jumbojett/openid-connect-php 库
composer require jumbojett/openid-connect-php
完整实现示例
1 基础配置
<?php
// config.php
class OIDCConfig {
// Provider配置
const PROVIDER_URL = 'https://your-provider.com';
const CLIENT_ID = 'your-client-id';
const CLIENT_SECRET = 'your-client-secret';
const REDIRECT_URI = 'https://your-app.com/callback.php';
// 请求的权限范围
const SCOPES = ['openid', 'profile', 'email', 'offline_access'];
}
?>
2 使用jumbojett库的完整实现
<?php
// login.php - 发起认证请求
require_once 'vendor/autoload.php';
require_once 'config.php';
use Jumbojett\OpenIDConnectClient;
session_start();
// 创建OIDC客户端
$oidc = new OpenIDConnectClient(
OIDCConfig::PROVIDER_URL,
OIDCConfig::CLIENT_ID,
OIDCConfig::CLIENT_SECRET
);
// 设置重定向URI
$oidc->setRedirectURL(OIDCConfig::REDIRECT_URI);
// 设置请求的作用域
$oidc->addScope(OIDCConfig::SCOPES);
// 添加认证参数(可选)
$oidc->addAuthParam(['prompt' => 'login']);
// 保存状态用于防CSRF
$_SESSION['oidc_state'] = $oidc->getState();
// 发起认证请求
$oidc->authenticate();
?>
<?php
// callback.php - 处理认证回调
require_once 'vendor/autoload.php';
require_once 'config.php';
use Jumbojett\OpenIDConnectClient;
session_start();
// 验证state防止CSRF
if (isset($_GET['state']) && $_SESSION['oidc_state'] !== $_GET['state']) {
die('State mismatch, possible CSRF attack');
}
try {
$oidc = new OpenIDConnectClient(
OIDCConfig::PROVIDER_URL,
OIDCConfig::CLIENT_ID,
OIDCConfig::CLIENT_SECRET
);
$oidc->setRedirectURL(OIDCConfig::REDIRECT_URI);
// 处理认证回调
$oidc->authenticate();
// 获取用户信息
$userInfo = $oidc->requestUserInfo();
// 获取ID Token
$idToken = $oidc->getIdToken();
// 获取Access Token
$accessToken = $oidc->getAccessToken();
// 获取Refresh Token(如果配置了offline_access)
$refreshToken = $oidc->getRefreshToken();
// 保存用户会话
$_SESSION['user'] = [
'sub' => $userInfo->sub,
'email' => $userInfo->email ?? '',
'name' => $userInfo->name ?? '',
'given_name' => $userInfo->given_name ?? '',
'family_name' => $userInfo->family_name ?? '',
'picture' => $userInfo->picture ?? '',
'access_token' => $accessToken,
'id_token' => $idToken,
'refresh_token' => $refreshToken,
'login_time' => time()
];
// 重定向到受保护的页面
header('Location: /dashboard.php');
exit;
} catch (Exception $e) {
error_log('OIDC authentication error: ' . $e->getMessage());
header('Location: /login-error.php?error=' . urlencode($e->getMessage()));
exit;
}
?>
3 使用league/oauth2-client实现
<?php
// oidc_league.php - 使用league/oauth2-client
require_once 'vendor/autoload.php';
require_once 'config.php';
use League\OAuth2\Client\Provider\GenericProvider;
session_start();
class OIDCOAuth2Client {
private $provider;
public function __construct() {
$this->provider = new GenericProvider([
'clientId' => OIDCConfig::CLIENT_ID,
'clientSecret' => OIDCConfig::CLIENT_SECRET,
'redirectUri' => OIDCConfig::REDIRECT_URI,
'urlAuthorize' => OIDCConfig::PROVIDER_URL . '/authorize',
'urlAccessToken' => OIDCConfig::PROVIDER_URL . '/token',
'urlResourceOwnerDetails' => OIDCConfig::PROVIDER_URL . '/userinfo',
'scopes' => OIDCConfig::SCOPES
]);
}
public function authenticate() {
// 检查授权回调
if (!isset($_GET['code'])) {
// 获取授权URL
$authorizationUrl = $this->provider->getAuthorizationUrl();
$_SESSION['oauth2state'] = $this->provider->getState();
header('Location: ' . $authorizationUrl);
exit;
}
// 验证state
if (empty($_GET['state']) || ($_GET['state'] !== $_SESSION['oauth2state'])) {
unset($_SESSION['oauth2state']);
exit('Invalid state');
}
try {
// 获取访问令牌
$accessToken = $this->provider->getAccessToken('authorization_code', [
'code' => $_GET['code']
]);
// 获取用户信息
$resourceOwner = $this->provider->getResourceOwner($accessToken);
$userData = $resourceOwner->toArray();
// 保存会话
$_SESSION['user'] = [
'id' => $userData['sub'],
'email' => $userData['email'],
'name' => $userData['name'],
'access_token' => $accessToken->getToken(),
'expires' => $accessToken->getExpires()
];
return true;
} catch (Exception $e) {
error_log('Authentication failed: ' . $e->getMessage());
return false;
}
}
}
// 使用示例
$client = new OIDCOAuth2Client();
if ($client->authenticate()) {
header('Location: /dashboard.php');
exit;
} else {
header('Location: /login-error.php');
exit;
}
?>
4 用户会话管理和中间件
<?php
// auth_middleware.php - 认证中间件
class AuthMiddleware {
public static function requireLogin() {
session_start();
if (!self::isLoggedIn()) {
header('Location: /login.php');
exit;
}
// 检查令牌是否过期
if (self::isTokenExpired()) {
self::refreshToken();
}
return $_SESSION['user'];
}
public static function isLoggedIn() {
return isset($_SESSION['user']['id']);
}
public static function isTokenExpired() {
if (!isset($_SESSION['user']['token_expires'])) {
return true;
}
return time() >= $_SESSION['user']['token_expires'];
}
public static function refreshToken() {
// 使用refresh token刷新
require_once 'vendor/autoload.php';
$refreshToken = $_SESSION['user']['refresh_token'] ?? null;
if (!$refreshToken) {
self::logout();
return false;
}
try {
$oidc = new OpenIDConnectClient(
OIDCConfig::PROVIDER_URL,
OIDCConfig::CLIENT_ID,
OIDCConfig::CLIENT_SECRET
);
// 刷新令牌
$newToken = $oidc->refreshToken($refreshToken);
// 更新会话
$_SESSION['user']['access_token'] = $newToken->getAccessToken();
$_SESSION['user']['token_expires'] = time() + $newToken->getExpiresIn();
$_SESSION['user']['refresh_token'] = $newToken->getRefreshToken();
return true;
} catch (Exception $e) {
error_log('Token refresh failed: ' . $e->getMessage());
self::logout();
return false;
}
}
public static function logout() {
session_destroy();
header('Location: /login.php');
exit;
}
}
?>
5 安全最佳实践
<?php
// security.php - 安全配置
class Security {
// 验证ID Token
public static function validateIdToken($idToken) {
$parts = explode('.', $idToken);
if (count($parts) !== 3) {
return false;
}
list($header, $payload, $signature) = $parts;
// 解码payload
$decodedPayload = json_decode(base64_decode(strtr($payload, '-_', '+/')), true);
// 验证过期时间
if ($decodedPayload['exp'] < time()) {
return false;
}
// 验证issuer
if ($decodedPayload['iss'] !== OIDCConfig::PROVIDER_URL) {
return false;
}
// 验证audience
if ($decodedPayload['aud'] !== OIDCConfig::CLIENT_ID) {
return false;
}
return $decodedPayload;
}
// 生成安全的随机状态
public static function generateState() {
return bin2hex(random_bytes(32));
}
// CSRF防护
public static function verifyCSRF($token) {
return hash_equals($_SESSION['csrf_token'] ?? '', $token);
}
}
// 认证成功后的用户授权检查
class Authorization {
public static function checkPermission($user, $requiredRole) {
$userRoles = $user['roles'] ?? [];
return in_array($requiredRole, $userRoles);
}
public static function requireRole($role) {
$user = AuthMiddleware::requireLogin();
if (!self::checkPermission($user, $role)) {
http_response_code(403);
die('Access denied');
}
return $user;
}
}
?>
6 完整的登录页面示例
<!-- login.php -->
<?php
require_once 'config.php';
session_start();
?>
<!DOCTYPE html>
<html>
<head>登录</title>
<meta charset="utf-8">
<style>
body { font-family: Arial, sans-serif; margin: 40px auto; max-width: 400px; }
.login-btn { display: block; width: 100%; padding: 15px; background: #4CAF50; color: white; text-decoration: none; text-align: center; border-radius: 5px; }
.error { color: red; margin: 10px 0; }
</style>
</head>
<body>
<h1>用户登录</h1>
<?php if (isset($_GET['error'])): ?>
<div class="error"><?php echo htmlspecialchars($_GET['error']); ?></div>
<?php endif; ?>
<a href="/initiate_oidc.php" class="login-btn">使用OIDC账户登录</a>
</body>
</html>
<?php
// initiate_oidc.php - 发起OIDC认证
require_once 'vendor/autoload.php';
require_once 'config.php';
session_start();
$oidc = new OpenIDConnectClient(
OIDCConfig::PROVIDER_URL,
OIDCConfig::CLIENT_ID,
OIDCConfig::CLIENT_SECRET
);
$oidc->setRedirectURL(OIDCConfig::REDIRECT_URI);
$oidc->addScope(['openid', 'profile', 'email']);
$oidc->authenticate();
?>
配置文件示例
<?php
// config_example.php - 配置示例
return [
'oidc' => [
'provider_url' => 'https://your-identity-provider.com',
'client_id' => 'your-client-id',
'client_secret' => 'your-client-secret',
'redirect_uri' => 'https://your-app.com/callback.php',
'scopes' => ['openid', 'profile', 'email'],
'response_type' => 'code',
'grant_type' => 'authorization_code',
'allow_refresh_tokens' => true
],
'session' => [
'name' => 'oidc_session',
'lifetime' => 3600, // 1小时
'secure' => true,
'http_only' => true,
'same_site' => 'Lax'
],
'cache' => [
'enabled' => true,
'ttl' => 300 // 5分钟缓存JWT
]
];
?>
测试和调试
<?php
// test_oidc.php - 测试脚本
require_once 'vendor/autoload.php';
function testOIDCConfiguration() {
// 测试OIDC配置
echo "测试OIDC配置...\n";
// 1. 检查发现端点
$issuer = OIDCConfig::PROVIDER_URL;
$wellKnownUrl = $issuer . '/.well-known/openid-configuration';
$ch = curl_init($wellKnownUrl);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
if (!$response) {
echo "❌ 无法访问OIDC发现端点\n";
return false;
}
$config = json_decode($response, true);
if (!isset($config['authorization_endpoint'], $config['token_endpoint'], $config['userinfo_endpoint'])) {
echo "❌ OIDC发现端点配置不完整\n";
return false;
}
echo "✅ OIDC提供者配置正确\n";
return true;
}
testOIDCConfiguration();
?>
这就是一个完整的PHP OpenID Connect认证实现,关键要点:
- 选择合适的库:建议使用
jumbojett/openid-connect-php因为它专门针对OIDC实现 - 安全第一:始终验证state、令牌签名和过期时间
- 会话管理:合理管理access token和refresh token的生命周期
- 错误处理:提供友好的错误提示和日志记录
需要根据您的具体OIDC提供者(如Keycloak、Auth0、Azure AD等)调整配置。