如何在PHP/Slim项目中使用thephpleague/OAuth2实现授权?
在Slim项目中用thephpleague/oauth2-server实现基础授权的分步指南
针对Slim初学者,以下是实现基础OAuth2授权的最简步骤,适合开发环境快速验证:
1. 安装依赖
首先用Composer安装所需包:
composer require league/oauth2-server slim/psr7
slim/psr7是为了满足OAuth2服务器对PSR-7请求/响应的要求。
2. 生成加密密钥
OAuth2服务器需要加密密钥保障令牌安全,用OpenSSL命令生成:
# 生成私钥(用于签署令牌) openssl genrsa -out config/keys/private.key 2048 # 生成公钥(可选,用于验证令牌) openssl rsa -in config/keys/private.key -pubout -out config/keys/public.key # 生成加密密钥(用于加密授权码等) openssl rand -hex 16 > config/keys/encryption.key
确保config/keys目录存在,生产环境要限制该目录的访问权限。
3. 初始化OAuth2服务器
在项目中创建初始化服务器的文件src/OAuthServer.php:
<?php namespace App; use League\OAuth2\Server\AuthorizationServer; use League\OAuth2\Server\CryptKey; use League\OAuth2\Server\Repositories\ClientRepositoryInterface; use League\OAuth2\Server\Repositories\ScopeRepositoryInterface; use League\OAuth2\Server\Repositories\AuthCodeRepositoryInterface; use League\OAuth2\Server\Repositories\AccessTokenRepositoryInterface; use League\OAuth2\Server\Repositories\RefreshTokenRepositoryInterface; class OAuthServer { public static function create( ClientRepositoryInterface $clientRepo, AccessTokenRepositoryInterface $tokenRepo, AuthCodeRepositoryInterface $authCodeRepo, RefreshTokenRepositoryInterface $refreshTokenRepo, ScopeRepositoryInterface $scopeRepo ): AuthorizationServer { $server = new AuthorizationServer( $clientRepo, $tokenRepo, $scopeRepo, new CryptKey(__DIR__ . '/../config/keys/private.key'), file_get_contents(__DIR__ . '/../config/keys/encryption.key') ); // 启用授权码模式(适合Web应用的基础授权场景) $server->enableGrantType( new \League\OAuth2\Server\Grant\AuthCodeGrant( $authCodeRepo, $refreshTokenRepo, new \DateInterval('PT10M') // 授权码有效期10分钟 ), new \DateInterval('PT1H') // 访问令牌有效期1小时 ); return $server; } }
4. 实现基础存储库(开发环境用内存存储)
OAuth2服务器依赖多个存储库接口,开发阶段用内存实现,生产环境需替换为数据库存储:
客户端存储库(src/Repository/ClientRepository.php)
<?php namespace App\Repository; use League\OAuth2\Server\Entities\ClientEntityInterface; use League\OAuth2\Server\Repositories\ClientRepositoryInterface; class ClientRepository implements ClientRepositoryInterface { public function getClientEntity($clientIdentifier) { // 开发环境硬编码测试客户端 if ($clientIdentifier !== 'test-client') { return null; } $client = new \League\OAuth2\Server\Entities\ClientEntity(); $client->setIdentifier('test-client'); $client->setName('Test Client'); $client->setRedirectUri('http://localhost:8080/callback'); $client->setConfidential(false); // 公开客户端(如前端应用) return $client; } public function validateClient($clientIdentifier, $clientSecret, $grantType) { // 开发环境跳过密钥验证,生产需对比存储的客户端密钥 return $clientIdentifier === 'test-client'; } }
用户存储库(src/Repository/UserRepository.php)
用于验证用户身份:
<?php namespace App\Repository; use League\OAuth2\Server\Entities\UserEntityInterface; use League\OAuth2\Server\Repositories\UserRepositoryInterface; class UserRepository implements UserRepositoryInterface { public function getUserEntityByUserCredentials($username, $password, $grantType, ClientEntityInterface $clientEntity) { // 开发环境硬编码测试用户 if ($username === 'test-user' && $password === 'test-pass') { $user = new class implements UserEntityInterface { public function getIdentifier() { return 1; } }; return $user; } return null; } }
范围存储库(src/Repository/ScopeRepository.php)
<?php namespace App\Repository; use League\OAuth2\Server\Repositories\ScopeRepositoryInterface; use League\OAuth2\Server\Entities\ClientEntityInterface; class ScopeRepository implements ScopeRepositoryInterface { public function getScopeEntityByIdentifier($scopeIdentifier) { // 开发环境只支持基础权限范围 if ($scopeIdentifier !== 'basic') { return null; } $scope = new \League\OAuth2\Server\Entities\ScopeEntity(); $scope->setIdentifier('basic'); return $scope; } public function finalizeScopes(array $scopes, $grantType, ClientEntityInterface $clientEntity, $userIdentifier = null) { return $scopes; } }
令牌存储库示例(src/Repository/AccessTokenRepository.php)
<?php namespace App\Repository; use League\OAuth2\Server\Repositories\AccessTokenRepositoryInterface; use League\OAuth2\Server\Entities\AccessTokenEntityInterface; use League\OAuth2\Server\Entities\ClientEntityInterface; class AccessTokenRepository implements AccessTokenRepositoryInterface { protected $tokens = []; public function persistNewAccessToken(AccessTokenEntityInterface $accessTokenEntity) { $this->tokens[$accessTokenEntity->getIdentifier()] = $accessTokenEntity; } public function revokeAccessToken($tokenId) { unset($this->tokens[$tokenId]); } public function isAccessTokenRevoked($tokenId) { return !isset($this->tokens[$tokenId]); } public function getNewToken(ClientEntityInterface $clientEntity, array $scopes, $userIdentifier = null) { $token = new \League\OAuth2\Server\Entities\AccessTokenEntity(); $token->setClient($clientEntity); foreach ($scopes as $scope) { $token->addScope($scope); } if ($userIdentifier !== null) { $token->setUserIdentifier($userIdentifier); } $token->setExpiryDateTime((new \DateTime())->add(new \DateInterval('PT1H'))); return $token; } }
AuthCodeRepository和RefreshTokenRepository可参照此模式实现,逻辑类似。
5. 在Slim中注册路由和服务器
修改public/index.php,初始化Slim应用并配置OAuth相关路由:
<?php use Slim\Factory\AppFactory; use App\OAuthServer; use App\Repository\{ ClientRepository, AccessTokenRepository, AuthCodeRepository, RefreshTokenRepository, ScopeRepository, UserRepository }; use League\OAuth2\Server\Exception\OAuthServerException; use Psr\Http\Message\ResponseInterface as Response; use Psr\Http\Message\ServerRequestInterface as Request; require __DIR__ . '/../vendor/autoload.php'; // 初始化Slim应用 AppFactory::setResponseFactory(new \Slim\Psr7\ResponseFactory()); $app = AppFactory::create(); // 初始化OAuth服务器 $clientRepo = new ClientRepository(); $tokenRepo = new AccessTokenRepository(); $authCodeRepo = new AuthCodeRepository(); $refreshTokenRepo = new RefreshTokenRepository(); $scopeRepo = new ScopeRepository(); $server = OAuthServer::create($clientRepo, $tokenRepo, $authCodeRepo, $refreshTokenRepo, $scopeRepo); // 授权端点(用户登录并授权) $app->get('/authorize', function (Request $request, Response $response) use ($server) { try { $authRequest = $server->validateAuthorizationRequest($request); // 开发环境模拟用户登录,生产需替换为登录页面跳转逻辑 $userRepo = new UserRepository(); $user = $userRepo->getUserEntityByUserCredentials('test-user', 'test-pass', 'authorization_code', $authRequest->getClient()); $authRequest->setUser($user); // 开发环境自动授权,生产需让用户手动确认授权 $authRequest->setAuthorizationApproved(true); $response = $server->completeAuthorizationRequest($authRequest, $response); return $response; } catch (OAuthServerException $e) { return $e->generateHttpResponse($response); } catch (\Exception $e) { $body = $response->getBody(); $body->write($e->getMessage()); return $response->withStatus(500)->withBody($body); } }); // 令牌端点(交换授权码获取访问令牌) $app->post('/token', function (Request $request, Response $response) use ($server) { try { return $server->respondToAccessTokenRequest($request, $response); } catch (OAuthServerException $e) { return $e->generateHttpResponse($response); } catch (\Exception $e) { $body = $response->getBody(); $body->write($e->getMessage()); return $response->withStatus(500)->withBody($body); } }); // 受保护的测试端点 $app->get('/protected', function (Request $request, Response $response) { $user = $request->getAttribute('oauth_user_id'); $response->getBody()->write("Hello, user #{$user}! This is a protected endpoint."); return $response; })->add(function (Request $request, $handler) use ($server) { // 令牌验证中间件 try { $request = $server->validateAuthenticatedRequest($request); return $handler->handle($request); } catch (OAuthServerException $e) { return $e->generateHttpResponse($handler->handle($request)); } }); // 运行应用 $app->run();
6. 测试流程
- 访问授权端点:
http://localhost:8080/authorize?client_id=test-client&redirect_uri=http://localhost:8080/callback&response_type=code&scope=basic
开发环境会自动完成授权,跳转到回调地址并携带授权码code。 - 发送POST请求到令牌端点
http://localhost:8080/token,用form-data提交参数:grant_type:authorization_codeclient_id:test-clientcode: 上一步获取的授权码redirect_uri:http://localhost:8080/callback
会返回包含access_token的JSON响应。
- 访问受保护端点
http://localhost:8080/protected,请求头添加Authorization: Bearer {access_token},即可看到授权后的内容。
生产环境注意事项
- 将所有内存存储库替换为数据库实现(如Doctrine或Eloquent)
- 加密密钥不要提交到版本控制,严格管理访问权限
- 替换硬编码用户凭证,实现正规的用户登录系统
- 启用HTTPS保障数据传输安全
内容的提问来源于stack exchange,提问作者Dawid
相关产品推荐
相关产品推荐

