本文目录导读:

我来详细讲解PHP项目中OAuth2.0授权流程的完整实现。
使用OAuth2.0客户端库
推荐使用社区成熟的库,如 league/oauth2-client 或 phpseclib/oauth。
安装依赖
composer require league/oauth2-client
完整授权流程实现
1 配置OAuth2服务提供商
config/oauth.php
<?php
return [
'github' => [
'clientId' => 'your-client-id',
'clientSecret' => 'your-client-secret',
'redirectUri' => 'https://your-domain/callback/github',
'urlAuthorize' => 'https://github.com/login/oauth/authorize',
'urlAccessToken' => 'https://github.com/login/oauth/access_token',
'urlResourceOwnerDetails' => 'https://api.github.com/user',
'scopes' => ['read:user', 'user:email']
],
'google' => [
'clientId' => 'your-google-client-id',
'clientSecret' => 'your-google-client-secret',
'redirectUri' => 'https://your-domain/callback/google',
'urlAuthorize' => 'https://accounts.google.com/o/oauth2/v2/auth',
'urlAccessToken' => 'https://oauth2.googleapis.com/token',
'urlResourceOwnerDetails' => 'https://www.googleapis.com/oauth2/v2/userinfo',
'scopes' => ['https://www.googleapis.com/auth/userinfo.email', 'https://www.googleapis.com/auth/userinfo.profile']
]
];
2 OAuth服务类
services/OAuthService.php
<?php
namespace App\Services;
use League\OAuth2\Client\Provider\AbstractProvider;
use League\OAuth2\Client\Token\AccessToken;
use App\Models\User;
use App\Repositories\UserRepository;
use GuzzleHttp\Client as HttpClient;
class OAuthService
{
private $provider;
private $userRepository;
private $config;
public function __construct(UserRepository $userRepository)
{
$this->userRepository = $userRepository;
$this->config = require __DIR__ . '/../config/oauth.php';
}
/**
* 获取授权URL
*/
public function getAuthorizationUrl(string $providerName): string
{
$provider = $this->getProvider($providerName);
// 生成state参数防止CSRF
$state = bin2hex(random_bytes(32));
$_SESSION['oauth_state'] = $state;
$_SESSION['oauth_provider'] = $providerName;
$authorizationUrl = $provider->getAuthorizationUrl([
'state' => $state,
'scope' => $this->config[$providerName]['scopes']
]);
return $authorizationUrl;
}
/**
* 处理回调,获取access token和用户信息
*/
public function handleCallback(string $providerName, string $code, string $state): array
{
// 验证state
if (!isset($_SESSION['oauth_state']) || $state !== $_SESSION['oauth_state']) {
throw new \Exception('Invalid state parameter');
}
$provider = $this->getProvider($providerName);
try {
// 用授权码换取access token
$accessToken = $provider->getAccessToken('authorization_code', [
'code' => $code
]);
// 使用access token获取用户信息
$resourceOwner = $provider->getResourceOwner($accessToken);
$userData = $resourceOwner->toArray();
// 查找或创建用户
$user = $this->findOrCreateUser($providerName, $userData, $accessToken);
// 清除session中的state
unset($_SESSION['oauth_state']);
unset($_SESSION['oauth_provider']);
return [
'user' => $user,
'access_token' => $accessToken->getToken(),
'refresh_token' => $accessToken->getRefreshToken(),
'expires' => $accessToken->getExpires()
];
} catch (\League\OAuth2\Client\Provider\Exception\IdentityProviderException $e) {
throw new \Exception('OAuth Error: ' . $e->getMessage());
}
}
/**
* 刷新access token
*/
public function refreshToken(string $providerName, string $refreshToken): AccessToken
{
$provider = $this->getProvider($providerName);
$newAccessToken = $provider->getAccessToken('refresh_token', [
'refresh_token' => $refreshToken
]);
return $newAccessToken;
}
/**
* 查找或创建用户
*/
private function findOrCreateUser(string $provider, array $userData, AccessToken $accessToken): User
{
$email = $userData['email'] ?? $userData['login'] . '@github.oauth';
// 根据提供商ID查找已有用户
$user = $this->userRepository->findByOAuthProvider($provider, $userData['id']);
if (!$user) {
// 创建新用户
$user = $this->userRepository->create([
'name' => $userData['name'] ?? $userData['login'],
'email' => $email,
'password' => password_hash(bin2hex(random_bytes(16)), PASSWORD_DEFAULT),
'email_verified_at' => now(),
'oauth_provider' => $provider,
'oauth_id' => (string)$userData['id'],
'avatar_url' => $userData['avatar_url'] ?? null
]);
}
// 更新access token信息
$this->userRepository->updateOAuthToken($user->id, [
'access_token' => $accessToken->getToken(),
'refresh_token' => $accessToken->getRefreshToken(),
'token_expires_at' => date('Y-m-d H:i:s', $accessToken->getExpires())
]);
return $user;
}
/**
* 获取OAuth2提供商实例
*/
private function getProvider(string $name): AbstractProvider
{
$config = $this->config[$name];
switch ($name) {
case 'github':
return new \League\OAuth2\Client\Provider\Github([
'clientId' => $config['clientId'],
'clientSecret' => $config['clientSecret'],
'redirectUri' => $config['redirectUri']
]);
case 'google':
return new \League\OAuth2\Client\Provider\Google([
'clientId' => $config['clientId'],
'clientSecret' => $config['clientSecret'],
'redirectUri' => $config['redirectUri']
]);
default:
throw new \InvalidArgumentException("Unsupported provider: {$name}");
}
}
}
3 控制器实现
controllers/AuthController.php
<?php
namespace App\Controllers;
use App\Services\OAuthService;
use App\Repositories\UserRepository;
class AuthController
{
private $oauthService;
public function __construct()
{
$this->oauthService = new OAuthService(new UserRepository());
}
/**
* 跳转到OAuth提供商授权页面
*/
public function redirect(string $provider)
{
$authorizationUrl = $this->oauthService->getAuthorizationUrl($provider);
header('Location: ' . $authorizationUrl);
exit;
}
/**
* 处理OAuth回调
*/
public function callback(string $provider)
{
try {
$code = $_GET['code'];
$state = $_GET['state'];
$result = $this->oauthService->handleCallback($provider, $code, $state);
// 创建会话
$_SESSION['user_id'] = $result['user']->id;
$_SESSION['access_token'] = $result['access_token'];
// 返回JSON或重定向
header('Content-Type: application/json');
echo json_encode([
'success' => true,
'message' => '登录成功',
'user' => $result['user']
]);
} catch (\Exception $e) {
http_response_code(400);
echo json_encode([
'success' => false,
'message' => $e->getMessage()
]);
}
}
/**
* 用户登出
*/
public function logout()
{
session_destroy();
header('Location: /login');
exit;
}
}
4 路由配置
routes/web.php
<?php
// 授权入口
Route::get('/auth/{provider}', 'AuthController@redirect');
// 回调处理
Route::get('/callback/{provider}', 'AuthController@callback');
// 登录页面(可选)
Route::get('/login', function() {
echo '<a href="/auth/github">使用GitHub登录</a><br>';
echo '<a href="/auth/google">使用Google登录</a>';
});
// 登出
Route::post('/logout', 'AuthController@logout');
自定义OAuth提供商
如果需要支持自定义API的OAuth2:
<?php
namespace App\OAuth;
use League\OAuth2\Client\Provider\AbstractProvider;
use League\OAuth2\Client\Provider\Exception\IdentityProviderException;
use Psr\Http\Message\ResponseInterface;
class CustomProvider extends AbstractProvider
{
private $apiDomain;
public function __construct(array $options = [], array $collaborators = [])
{
parent::__construct($options, $collaborators);
$this->apiDomain = $options['apiDomain'] ?? 'https://api.example.com';
}
public function getBaseAuthorizationUrl(): string
{
return $this->apiDomain . '/oauth/authorize';
}
public function getBaseAccessTokenUrl(array $params): string
{
return $this->apiDomain . '/oauth/token';
}
public function getResourceOwnerDetailsUrl(AccessToken $token): string
{
return $this->apiDomain . '/api/user';
}
protected function getDefaultScopes(): array
{
return ['basic'];
}
protected function checkResponse(ResponseInterface $response, $data): void
{
if ($response->getStatusCode() >= 400) {
throw new IdentityProviderException(
$data['error'] ?? $response->getReasonPhrase(),
$response->getStatusCode(),
$response
);
}
}
protected function createResourceOwner(array $response, AccessToken $token): CustomResourceOwner
{
return new CustomResourceOwner($response);
}
}
安全注意事项
<?php
// 1. 始终使用HTTPS
if (!isset($_SERVER['HTTPS']) || $_SERVER['HTTPS'] !== 'on') {
header('Location: https://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']);
exit;
}
// 2. State参数验证
// 在回调处理中验证state
// 3. 存储access token安全
// 使用加密存储
$encryptedToken = openssl_encrypt(
$token,
'aes-256-cbc',
$_ENV['TOKEN_ENCRYPTION_KEY'],
0,
$iv
);
// 4. 定期刷新token
if ($accessToken->hasExpired()) {
$newToken = $oauthService->refreshToken($provider, $refreshToken);
}
// 5. 限制重定向URI
// 在provider配置中只允许特定的回调URL
完整示例流程图
用户点击"使用GitHub登录"
↓
跳转到GitHub授权页面
↓
用户确认授权
↓
GitHub重定向回你的回调URL
↓
用授权码换取access token
↓
使用access token获取用户信息
↓
查找或创建本地用户
↓
创建用户会话
↓
返回成功响应或重定向到首页
这个实现涵盖了OAuth2.0授权码流程的完整步骤,包括state验证、token管理、用户信息同步等关键部分。