OAuth2.0 与 oauth2-server 库的使用


做者:baiyi
连接:https://www.jianshu.com/p/83b0f6d82d6c
來源:简书php

OAuth2.0 是关于受权的开放网络标准,它容许用户已第三方应用获取该用户在某一网站的私密资源,而无需提供用户名与密码,目前已在全世界获得普遍应用。css

league/oauth2-server 是一个轻量级而且功能强大的符合 OAuth2.0 协议的 PHP 库,使用它能够构建出标准的 OAuth2.0 受权服务器。html

本文经过对 PHP 库:league/oauth2-server 进行实践的同时,理解 OAuth2.0 的工做流程与设计思路。前端

术语

了解 OAuth2.0 与 oauth2-server 的专用术语,对于理解后面内容颇有帮助。git

OAuth2.0 定义了四个角色github

  1. Client:客户端,第三方应用程序。
  2. Resource Owner:资源全部者,受权 Client 访问其账户的用户。
  3. Authorization server:受权服务器,服务商专用于处理用户受权认证的服务器。
  4. Resource server:资源服务器,服务商用于存放用户受保护资源的服务器,它能够与受权服务器是同一台服务器,也能够是不一样的服务器。

oauth2-servershell

  1. Access token:用于访问受保护资源的令牌。
  2. Authorization code:发放给应用程序的中间令牌,客户端应用使用此令牌交换 access token。
  3. Scope:授予应用程序的权限范围。
  4. JWTJson Web Token 是一种用于安全传输的数据传输格式。

运行流程

 
flowchart.png

安装

推荐使用 Composer 进行安装:数据库

composer require league/oauth2-server

根据受权模式的不一样,oauth2-server 提供了不一样的 Interface 与 Triat 帮助实现。json

本文发布时,版本号为7.3.1。数组

生成公钥与私钥

公钥与私钥用于签名和验证传输的 JWT,受权服务器使用私钥签名 JWT,资源服务器拥有公钥验证 JWT。

oauth2-server 使用 JWT 传输访问令牌(access token),方便资源服务器获取其中内容,因此须要使用非对称加密。

生成私钥,在终端中运行:

openssl genrsa -out private.key 2048

使用私钥提取私钥:

openssl rsa -in private.key -pubout -out public.key

私钥必须保密于受权服务器中,并将公钥分发给资源服务器。

生成加密密钥

加密密钥用于加密受权码(auth code)与刷新令牌(refesh token),AuthorizationServer(受权服务器启动类)接受两种加密密钥,stringdefuse/php-encryption 库的对象。

加密受权码(auth code)与刷新令牌(refesh token)只有受权权服务器使用,因此使用对称加密。

生成字符串密钥,在终端中输入:

php -r 'echo base64_encode(random_bytes(32)), PHP_EOL;'

生成对象,在项目根目录的终端中输入:

vendor/bin/generate-defuse-key

将得到的内容,传入 AuthorizationServer:

use \Defuse\Crypto\Key; $server = new AuthorizationServer( $clientRepository, $accessTokenRepository, $scopeRepository, $privateKeyPath, Key::loadFromAsciiSafeString($encryptionKey) //传入加密密钥 ); 

PHP版本支持

  • PHP 7.0
  • PHP 7.1
  • PHP 7.2

受权模式

OAuth2.0 定义了四种受权模式,以应对不一样状况时的受权。

  1. 受权码模式
  2. 隐式受权模式
  3. 密码模式
  4. 客户端模式

客户端类型

  • 保密的:
    • 客户端能够安全的存储本身与用户的凭据(例如:有所属的服务器端)
  • 公开的:
    • 客户端没法安全的存储本身与用户的凭据(例如:运行在浏览器的单页应用)

选用哪一种受权模式?

若是客户端是保密的,应使用受权码模式

若是客户端是公开的,应使用隐式受权模式

若是用户对于此客户端高度信任(例如:第一方应用程序或操做系统程序),应使用密码模式

若是客户端是以本身的名义,不与用户产生关系,应使用客户端模式

预先注册

客户端须要预先在受权服务器进行注册,用以获取 client_idclient_secret,也能够在注册是预先设定好 redirect_uri,以便于以后可使用默认的 redirect_uri

受权码模式

受权码模式是 OAuth2.0 种功能最完整,流程最严密的一种模式,若是你使用过 Google 或 QQ 登陆过第三方应用程序,应该会对这个流程的第一部分很熟悉。

流程

第一部分(用户可见)

用户访问客户端,客户端将用户导向受权服务器时,将如下参数经过 GET query 传入:

  • response_type:受权类型,必选项,值固定为:code
  • client_id:客户端ID,必选项
  • redirect_uri:重定向URI,可选项,不填写时默认预先注册的重定向URI
  • scope:权限范围,可选项,以空格分隔
  • stateCSRF令牌,可选项,但强烈建议使用,应将该值存储与用户会话中,以便在返回时验证

用户选择是否给予客户端受权

假设用户给予受权,受权服务器将用户导向客户端事先指定的 redirect_uri,并将如下参数经过 GET query 传入:

  • code:受权码(Authorization code)
  • state:请求中发送的 state,原样返回。客户端将此值与用户会话中的值进行对比,以确保受权码响应的是此客户端而非其余客户端程序

第二部分(用户不可见)

客户端已获得受权,经过 POST 请求向受权服务器获取访问令牌(access token):

  • grant_type:受权模式,值固定为:authorization_code
  • client_id:客户端ID
  • client_secret:客户端 secret
  • redirect_uri:使用与第一部分请求相同的 URI
  • code:第一部分所获的的受权码,要注意URL解码

受权服务器核对受权码与重定向 URI,确认无误后,向客户端响应下列内容:

  • token_type:令牌类型,值固定为:Bearer

  • expires_in:访问令牌的存活时间

  • access_token:访问令牌

  • refresh_token:刷新令牌,访问令牌过时后,使用刷新令牌从新获取

使用 oauth2-server 实现

初始化

OAuth2.0 只是协议,在实现上须要联系到用户与数据库存储,oauth2-server 的新版本并无指定某种数据库,但它提供了 InterfacesTraits 帮助咱们实现,这让咱们能够方便的使用任何形式的数据存储方式,这种方便的代价就是须要咱们自行建立 RepositoriesEntities

初始化 server
// 初始化存储库 $clientRepository = new ClientRepository(); // Interface: ClientRepositoryInterface $scopeRepository = new ScopeRepository(); // Interface: ScopeRepositoryInterface $accessTokenRepository = new AccessTokenRepository(); // Interface: AccessTokenRepositoryInterface $authCodeRepository = new AuthCodeRepository(); // Interface: AuthCodeRepositoryInterface $refreshTokenRepository = new RefreshTokenRepository(); // Interface: RefreshTokenRepositoryInterface $userRepository = new UserRepository(); //Interface: UserRepositoryInterface // 私钥与加密密钥 $privateKey = 'file://path/to/private.key'; //$privateKey = new CryptKey('file://path/to/private.key', 'passphrase'); // 若是私钥文件有密码 $encryptionKey = 'lxZFUEsBCJ2Yb14IF2ygAHI5N4+ZAUXXaSeeJm6+twsUmIen'; // 加密密钥字符串 // $encryptionKey = Key::loadFromAsciiSafeString($encryptionKey); //若是经过 generate-defuse-key 脚本生成的字符串,可以使用此方法传入 // 初始化 server $server = new \League\OAuth2\Server\AuthorizationServer( $clientRepository, $accessTokenRepository, $scopeRepository, $privateKey, $encryptionKey ); 
初始化受权码类型
// 受权码受权类型初始化 $grant = new \League\OAuth2\Server\Grant\AuthCodeGrant( $authCodeRepository, $refreshTokenRepository, new \DateInterval('PT10M') // 设置受权码过时时间为10分钟 ); $grant->setRefreshTokenTTL(new \DateInterval('P1M')); // 设置刷新令牌过时时间1个月 // 将受权码受权类型添加进 server $server->enableGrantType( $grant, new \DateInterval('PT1H') // 设置访问令牌过时时间1小时 ); 

DateInterval

使用

注意:这里的示例演示的是 Slim Framework 的用法,Slim 不是这个库的必要条件,只须要请求与响应符合PSR-7规范便可。

用户向客户端提出 OAuth 登陆请求,客户端将用户重定向受权服务器的地址(例如:https://example.com/authorize?response_type=code&client_id={client_id}&redirect_uri={redirect_uri}&scope{scope}&state={state}):

$app->get('/authorize', function (ServerRequestInterface $request, ResponseInterface $response) use ($server) { try { // 验证 HTTP 请求,并返回 authRequest 对象 $authRequest = $server->validateAuthorizationRequest($request); // 此时应将 authRequest 对象序列化后存在当前会话(session)中 $_SESSION['authRequest'] = serialize($authRequest); // 而后将用户重定向至登陆入口或在当前地址直接响应登陆页面 return $response->getBody()->write(file_get_contents("login.html")); } catch (OAuthServerException $exception) { // 能够捕获 OAuthServerException,将其转为 HTTP 响应 return $exception->generateHttpResponse($response); } catch (\Exception $exception) { // 其余异常 $body = new Stream(fopen('php://temp', 'r+')); $body->write($exception->getMessage()); return $response->withStatus(500)->withBody($body); } }); 

此时展现给用户的是这样的页面:


 
qq-oauth.png

用户提交登陆后,设置好用户实体(userEntity):

$app->post('/login', function (ServerRequestInterface $request, ResponseInterface $response) use ($server) { try { // 在会话(session)中取出 authRequest 对象 $authRequest = unserialize($_SESSION['authRequest']); // 设置用户实体(userEntity) $authRequest->setUser(new UserEntity(1)); // 设置权限范围 $authRequest->setScopes(['basic']) // true = 批准,false = 拒绝 $authRequest->setAuthorizationApproved(true); // 完成后重定向至客户端请求重定向地址 return $server->completeAuthorizationRequest($authRequest, $response); } catch (OAuthServerException $exception) { // 能够捕获 OAuthServerException,将其转为 HTTP 响应 return $exception->generateHttpResponse($response); } catch (\Exception $exception) { // 其余异常 $body = new Stream(fopen('php://temp', 'r+')); $body->write($exception->getMessage()); return $response->withStatus(500)->withBody($body); } }); 

客户端经过受权码请求访问令牌:

$app->post('/access_token', function (ServerRequestInterface $request, ResponseInterface $response) use ($server) { try { // 这里只须要这一行就能够,具体的判断在 Repositories 中 return $server->respondToAccessTokenRequest($request, $response); } catch (\League\OAuth2\Server\Exception\OAuthServerException $exception) { return $exception->generateHttpResponse($response); } catch (\Exception $exception) { $body = new Stream(fopen('php://temp', 'r+')); $body->write($exception->getMessage()); return $response->withStatus(500)->withBody($body); } }); 

隐式受权模式

隐式受权至关因而受权码模式的简化版本:

流程(用户可见)

用户访问客户端,客户端将用户导向受权服务器时,将如下参数经过 GET query 传入:

  • response_type:受权类型,必选项,值固定为:token
  • client_id:客户端ID,必选项
  • redirect_uri:重定向URI,可选项,不填写时默认预先注册的重定向URI
  • scope:权限范围,可选项,以空格分隔
  • stateCSRF令牌,可选项,但强烈建议使用,应将该值存储与用户会话中,以便在返回时验证

用户选择是否给予客户端受权

假设用户给予受权,受权服务器将用户导向客户端事先指定的 redirect_uri,并将如下参数经过 GET query 传入:

  • token_type:令牌类型,值固定为:Bearer
  • expires_in:访问令牌的存活时间
  • access_token:访问令牌
  • state:请求中发送的 state,原样返回。客户端将此值与用户会话中的值进行对比,以确保受权码响应的是此应用程序而非其余应用程序

整个流程与受权码模式的第一部分相似,只是受权服务器直接响应了访问令牌,跳过了受权码的步骤。它适用于没有服务器,彻底运行在前端的应用程序。

此模式下没有刷新令牌(refresh token)的返回。

使用 oauth2-server 实现

初始化 server

初始化受权码类型
// 将隐式受权类型添加进 server $server->enableGrantType( new ImplicitGrant(new \DateInterval('PT1H')), new \DateInterval('PT1H') // 设置访问令牌过时时间1小时 ); 

DateInterval

使用

注意:这里的示例演示的是 Slim Framework 的用法,Slim 不是这个库的必要条件,只须要请求与响应符合PSR-7规范便可。

$app->post('/login', function (ServerRequestInterface $request, ResponseInterface $response) use ($server) { try { // 在会话(session)中取出 authRequest 对象 $authRequest = unserialize($_SESSION['authRequest']); // 设置用户实体(userEntity) $authRequest->setUser(new UserEntity(1)); // 设置权限范围 $authRequest->setScopes(['basic']) // true = 批准,false = 拒绝 $authRequest->setAuthorizationApproved(true); // 完成后重定向至客户端请求重定向地址 return $server->completeAuthorizationRequest($authRequest, $response); } catch (OAuthServerException $exception) { // 能够捕获 OAuthServerException,将其转为 HTTP 响应 return $exception->generateHttpResponse($response); } catch (\Exception $exception) { // 其余异常 $body = new Stream(fopen('php://temp', 'r+')); $body->write($exception->getMessage()); return $response->withStatus(500)->withBody($body); } }); 

此时展现给用户的是这样的页面:


 
qq-oauth.png

用户提交登陆后,设置好用户实体(userEntity):

$app->post('/login', function (ServerRequestInterface $request, ResponseInterface $response) use ($server) { try { // 在会话(session)中取出 authRequest 对象 $authRequest = unserialize($_SESSION['authRequest']); // 设置用户实体(userEntity) $authRequest->setUser(new UserEntity(1)); // 设置权限范围 $authRequest->setScopes(['basic']) // true = 批准,false = 拒绝 $authRequest->setAuthorizationApproved(true); // 完成后重定向至客户端请求重定向地址 return $server->completeAuthorizationRequest($authRequest, $response); } catch (OAuthServerException $exception) { // 能够捕获 OAuthServerException,将其转为 HTTP 响应 return $exception->generateHttpResponse($response); } catch (\Exception $exception) { // 其余异常 $body = new Stream(fopen('php://temp', 'r+')); $body->write($exception->getMessage()); return $response->withStatus(500)->withBody($body); } }); 

密码模式

密码模式是由用户提供给客户端帐号密码来获取访问令牌,这属于危险行为,因此此模式只适用于高度信任的客户端(例如第一方应用程序)。客户端不该存储用户的帐号密码。

OAuth2 协议规定此模式不须要传 client_id & client_secret,但 oauth-server 库须要

流程

客户端要求用户提供受权凭据,一般是帐号密码

而后,客户端发送 POST 请求至受权服务器,携带如下参数:

  • grant_type:受权类型,必选项,值固定为:password
  • client_id:客户端ID,必选项
  • client_secret:客户端 secret
  • scope:权限范围,可选项,以空格分隔
  • username:用户帐号
  • password:用户密码

受权服务器响应如下内容:

  • token_type:令牌类型,值固定为:Bearer
  • expires_in:访问令牌的存活时间
  • access_token:访问令牌
  • refresh_token:刷新令牌,访问令牌过时后,使用刷新令牌从新获取

使用 oauth2-server 实现

初始化 server

初始化受权码类型
$grant = new \League\OAuth2\Server\Grant\PasswordGrant( $userRepository, $refreshTokenRepository ); $grant->setRefreshTokenTTL(new \DateInterval('P1M')); // 设置刷新令牌过时时间1个月 // 将密码受权类型添加进 server $server->enableGrantType( $grant, new \DateInterval('PT1H') // 设置访问令牌过时时间1小时 ); 

DateInterval

使用

注意:这里的示例演示的是 Slim Framework 的用法,Slim 不是这个库的必要条件,只须要请求与响应符合PSR-7规范便可。

$app->post('/access_token', function (ServerRequestInterface $request, ResponseInterface $response) use ($server) { try { // 这里只须要这一行就能够,具体的判断在 Repositories 中 return $server->respondToAccessTokenRequest($request, $response); } catch (\League\OAuth2\Server\Exception\OAuthServerException $exception) { return $exception->generateHttpResponse($response); } catch (\Exception $exception) { $body = new Stream(fopen('php://temp', 'r+')); $body->write($exception->getMessage()); return $response->withStatus(500)->withBody($body); } }); 

客户端模式

客户端模式是指以客户端的名义,而不是用户的名义,向受权服务器获取认证。在这个模式下,用户与受权服务器不产生关系,用户只能感知到的客户端,所产生的资源也都由客户端处理。

流程

客户端发送 POST 请求至受权服务器,携带如下参数:

  • grant_type:受权类型,必选项,值固定为:client_credentials
  • client_id:客户端ID,必选项
  • client_secret:客户端 secret
  • scope:权限范围,可选项,以空格分隔

受权服务器响应如下内容:

  • token_type:令牌类型,值固定为:Bearer
  • expires_in:访问令牌的存活时间
  • access_token:访问令牌

此模式下无需刷新令牌(refresh token)的返回。

使用 oauth2-server 实现

初始化 server

初始化受权码类型
// 将客户端受权类型添加进 server $server->enableGrantType( new \League\OAuth2\Server\Grant\ClientCredentialsGrant(), new \DateInterval('PT1H') // 设置访问令牌过时时间1小时 ); 

DateInterval

使用

注意:这里的示例演示的是 Slim Framework 的用法,Slim 不是这个库的必要条件,只须要请求与响应符合PSR-7规范便可。

$app->post('/access_token', function (ServerRequestInterface $request, ResponseInterface $response) use ($server) { try { // 这里只须要这一行就能够,具体的判断在 Repositories 中 return $server->respondToAccessTokenRequest($request, $response); } catch (\League\OAuth2\Server\Exception\OAuthServerException $exception) { return $exception->generateHttpResponse($response); } catch (\Exception $exception) { $body = new Stream(fopen('php://temp', 'r+')); $body->write($exception->getMessage()); return $response->withStatus(500)->withBody($body); } }); 

刷新访问令牌(access token)

访问令牌有一个较短的存活时间,在过时后,客户端经过刷新令牌来得到新的访问令牌与刷新令牌。当用户长时间不活跃,刷新令牌也过时后,就须要从新获取受权。

流程

客户端发送 POST 请求至受权服务器,携带如下参数:

  • grant_type:受权类型,必选项,值固定为:refresh_token
  • client_id:客户端ID,必选项
  • client_secret:客户端 secret
  • scope:权限范围,可选项,以空格分隔
  • refresh_token:刷新令牌

受权服务器响应如下内容:

  • token_type:令牌类型,值固定为:Bearer
  • expires_in:访问令牌的存活时间
  • access_token:访问令牌
  • refresh_token:刷新令牌,访问令牌过时后,使用刷新令牌从新获取

使用 oauth2-server 实现

初始化 server

初始化受权码类型
$grant = new \League\OAuth2\Server\Grant\RefreshTokenGrant($refreshTokenRepository); $grant->setRefreshTokenTTL(new \DateInterval('P1M')); // 新的刷新令牌过时时间1个月 // 将刷新访问令牌添加进 server $server->enableGrantType( $grant, new \DateInterval('PT1H') // 新的访问令牌过时时间1小时 ); 

DateInterval

使用
$app->post('/access_token', function (ServerRequestInterface $request, ResponseInterface $response) use ($server) { try { // 这里只须要这一行就能够,具体的判断在 Repositories 中 return $server->respondToAccessTokenRequest($request, $response); } catch (\League\OAuth2\Server\Exception\OAuthServerException $exception) { return $exception->generateHttpResponse($response); } catch (\Exception $exception) { $body = new Stream(fopen('php://temp', 'r+')); $body->write($exception->getMessage()); return $response->withStatus(500)->withBody($body); } }); 

资源服务器验证访问令牌

oauth2-server 为资源服务器提供了一个中间件用于验证访问令牌。

客户端须要在 HTTP Header 中使用 Authorization 传入访问令牌,若是经过,中间件将会在 request 中加入对应数据:

  • oauth_access_token_id:访问令牌 id
  • oauth_client_id: 客户端id
  • oauth_user_id:用户id
  • oauth_scopes:权限范围

受权不经过,则抛出 OAuthServerException::accessDenied 异常。

// 初始化 $accessTokenRepository = new AccessTokenRepository(); // Interface: AccessTokenRepositoryInterface // 受权服务器分发的公钥 $publicKeyPath = 'file://path/to/public.key'; // 建立 ResourceServer $server = new \League\OAuth2\Server\ResourceServer( $accessTokenRepository, $publicKeyPath ); // 中间件 new \League\OAuth2\Server\Middleware\ResourceServerMiddleware($server); 

若是所用路由不支持中间件,可自行实现,符合PSR-7规范便可 :

try { $request = $server->validateAuthenticatedRequest($request); } catch (OAuthServerException $exception) { return $exception->generateHttpResponse($response); } catch (\Exception $exception) { return (new OAuthServerException($exception->getMessage(), 0, 'unknown_error', 500))->generateHttpResponse($response); } 

oauth2-server 实现

oauth2-server 的实现须要咱们手动建立 RepositoriesEntities,下面展现一个项目目录示例:

- Entities - AccessTokenEntity.php - AuthCodeEntity.php - ClientEntity.php - RefreshTokenEntity.php - ScopeEntity.php - UserEntity.php - Repositories - AccessTokenRepository.php - AuthCodeRepository.php - ClientRepository.php - RefreshTokenRepository.php - ScopeRepository.php - UserRepository.php 

Repositories

Repositories 里主要是处理关于受权码、访问令牌等数据的存储逻辑,oauth2-server 提供了 Interfaces 来定义所须要实现的方法。

class AccessTokenRepository implements AccessTokenRepositoryInterface { /** * @return AccessTokenEntityInterface */ public function getNewToken(ClientEntityInterface $clientEntity, array $scopes, $userIdentifier = null) { // 建立新访问令牌时调用方法 // 须要返回 AccessTokenEntityInterface 对象 // 须要在返回前,向 AccessTokenEntity 传入参数中对应属性 // 示例代码: $accessToken = new AccessTokenEntity(); $accessToken->setClient($clientEntity); foreach ($scopes as $scope) { $accessToken->addScope($scope); } $accessToken->setUserIdentifier($userIdentifier); return $accessToken; } public function persistNewAccessToken(AccessTokenEntityInterface $accessTokenEntity) { // 建立新访问令牌时调用此方法 // 能够用于持久化存储访问令牌,持久化数据库自行选择 // 可使用参数中的 AccessTokenEntityInterface 对象,得到有价值的信息: // $accessTokenEntity->getIdentifier(); // 得到令牌惟一标识符 // $accessTokenEntity->getExpiryDateTime(); // 得到令牌过时时间 // $accessTokenEntity->getUserIdentifier(); // 得到用户标识符 // $accessTokenEntity->getScopes(); // 得到权限范围 // $accessTokenEntity->getClient()->getIdentifier(); // 得到客户端标识符 } public function revokeAccessToken($tokenId) { // 使用刷新令牌建立新的访问令牌时调用此方法 // 参数为原访问令牌的惟一标识符 // 可将其在持久化存储中过时 } public function isAccessTokenRevoked($tokenId) { // 资源服务器验证访问令牌时将调用此方法 // 用于验证访问令牌是否已被删除 // return true 已删除,false 未删除 return false; } } 
class AuthCodeRepository implements AuthCodeRepositoryInterface { /** * @return AuthCodeEntityInterface */ public function getNewAuthCode() { // 建立新受权码时调用方法 // 须要返回 AuthCodeEntityInterface 对象 return new AuthCodeEntity(); } public function persistNewAuthCode(AuthCodeEntityInterface $authCodeEntity) { // 建立新受权码时调用此方法 // 能够用于持久化存储受权码,持久化数据库自行选择 // 可使用参数中的 AuthCodeEntityInterface 对象,得到有价值的信息: // $authCodeEntity->getIdentifier(); // 得到受权码惟一标识符 // $authCodeEntity->getExpiryDateTime(); // 得到受权码过时时间 // $authCodeEntity->getUserIdentifier(); // 得到用户标识符 // $authCodeEntity->getScopes(); // 得到权限范围 // $authCodeEntity->getClient()->getIdentifier(); // 得到客户端标识符 } public function revokeAuthCode($codeId) { // 当使用受权码获取访问令牌时调用此方法 // 能够在此时将受权码从持久化数据库中删除 // 参数为受权码惟一标识符 } public function isAuthCodeRevoked($codeId) { // 当使用受权码获取访问令牌时调用此方法 // 用于验证受权码是否已被删除 // return true 已删除,false 未删除 return false; } } 
class ClientRepository implements ClientRepositoryInterface { /** * @return ClientEntityInterface */ public function getClientEntity($clientIdentifier, $grantType = null, $clientSecret = null, $mustValidateSecret = true) { // 获取客户端对象时调用方法,用于验证客户端 // 须要返回 ClientEntityInterface 对象 // $clientIdentifier 客户端惟一标识符 // $grantType 表明受权类型,根据类型不一样,验证方式也不一样 // $clientSecret 表明客户端密钥,是客户端事先在受权服务器中注册时获得的 // $mustValidateSecret 表明是否须要验证客户端密钥 $client = new ClientEntity(); $client->setIdentifier($clientIdentifier); return $client; } } 
class RefreshTokenRepository implements RefreshTokenRepositoryInterface { /** * @return RefreshTokenEntityInterface */ public function getNewRefreshToken() { // 建立新受权码时调用方法 // 须要返回 RefreshTokenEntityInterface 对象 return new RefreshTokenEntity(); } public function persistNewRefreshToken(RefreshTokenEntityInterface $refreshTokenEntity) { // 建立新刷新令牌时调用此方法 // 用于持久化存储授刷新令牌 // 可使用参数中的 RefreshTokenEntityInterface 对象,得到有价值的信息: // $refreshTokenEntity->getIdentifier(); // 得到刷新令牌惟一标识符 // $refreshTokenEntity->getExpiryDateTime(); // 得到刷新令牌过时时间 // $refreshTokenEntity->getAccessToken()->getIdentifier(); // 得到访问令牌标识符 } public function revokeRefreshToken($tokenId) { // 当使用刷新令牌获取访问令牌时调用此方法 // 原刷新令牌将删除,建立新的刷新令牌 // 参数为原刷新令牌惟一标识 // 可在此删除原刷新令牌 } public function isRefreshTokenRevoked($tokenId) { // 当使用刷新令牌获取访问令牌时调用此方法 // 用于验证刷新令牌是否已被删除 // return true 已删除,false 未删除 return false; } } 
class ScopeRepository implements ScopeRepositoryInterface { /** * @return ScopeEntityInterface */ public function getScopeEntityByIdentifier($identifier) { // 验证权限是否在权限范围中会调用此方法 // 参数为单个权限标识符 // ...... // 验证成功则返回 ScopeEntityInterface 对象 $scope = new ScopeEntity(); $scope->setIdentifier($identifier); return $scope; } public function finalizeScopes( array $scopes, $grantType, ClientEntityInterface $clientEntity, $userIdentifier = null ) { // 在建立受权码与访问令牌前会调用此方法 // 用于验证权限范围、受权类型、客户端、用户是否匹配 // 可整合进项目自身的权限控制中 // 必须返回 ScopeEntityInterface 对象可用的 scope 数组 // 示例: // $scope = new ScopeEntity(); // $scope->setIdentifier('example'); // $scopes[] = $scope; return $scopes; } } 
class UserRepository implements UserRepositoryInterface { /** * @return UserEntityInterface */ public function getUserEntityByUserCredentials( $username, $password, $grantType, ClientEntityInterface $clientEntity ) { // 验证用户时调用此方法 // 用于验证用户信息是否符合 // 能够验证是否为用户可以使用的受权类型($grantType)与客户端($clientEntity) // 验证成功返回 UserEntityInterface 对象 $user = new UserEntity(); $user->setIdentifier(1); return $user; } } 

Entities

Entities 里是 oauth2-server 处理受权与认证逻辑的类,它为咱们提供了 Interfaces 来定义须要实现的方法,同时提供了 Traits 帮助咱们实现,能够选择使用,有须要时也能够重写。

class AccessTokenEntity implements AccessTokenEntityInterface { use AccessTokenTrait, TokenEntityTrait, EntityTrait; } 
class AuthCodeEntity implements AuthCodeEntityInterface { use EntityTrait, TokenEntityTrait, AuthCodeTrait; } 
class ClientEntity implements ClientEntityInterface { use EntityTrait, ClientTrait; } 
class RefreshTokenEntity implements RefreshTokenEntityInterface { use RefreshTokenTrait, EntityTrait; } 
class ScopeEntity implements ScopeEntityInterface { use EntityTrait; // 没有 Trait 实现这个方法,须要自行实现 // oauth2-server 项目的测试代码的实现例子 public function jsonSerialize() { return $this->getIdentifier(); } } 
class UserEntity implements UserEntityInterface { use EntityTrait; } 

Interfaces

Repositories

  • League\OAuth2\Server\Repositories\AccessTokenRepositoryInterface.php

  • League\OAuth2\Server\Repositories\AuthCodeRepositoryInterface.php

  • League\OAuth2\Server\Repositories\ClientRepositoryInterface.php

  • League\OAuth2\Server\Repositories\RefreshTokenRepositoryInterface.php

  • League\OAuth2\Server\Repositories\ScopeRepositoryInterface.php

  • League\OAuth2\Server\Repositories\UserRepositoryInterface.php

Entities

  • League\OAuth2\Server\Entities\AccessTokenEntityInterface.php
  • League\OAuth2\Server\Entities\AuthCodeEntityInterface.php
  • League\OAuth2\Server\Entities\ClientEntityInterface.php
  • League\OAuth2\Server\Entities\RefreshTokenEntityInterface.php
  • League\OAuth2\Server\Entities\ScopeEntityInterface.php
  • League\OAuth2\Server\Entities\TokenInterface.php
  • League\OAuth2\Server\Entities\UserEntityInterface.php

Traits

  • League\OAuth2\Server\Entities\Traits\AccessTokenTrait.php
  • League\OAuth2\Server\Entities\Traits\AuthCodeTrait.php
  • League\OAuth2\Server\Entities\Traits\ClientTrait.php
  • League\OAuth2\Server\Entities\Traits\EntityTrait.php
  • League\OAuth2\Server\Entities\Traits\RefreshTokenTrait.php
  • League\OAuth2\Server\Entities\Traits\ScopeTrait.php
  • League\OAuth2\Server\Entities\Traits\TokenEntityTrait.php

事件

oauth2-server 预设了一些事件,目前官方文档中只有两个,余下的能够在 RequestEvent.php 文件中查看。

client.authentication.failed
$server->getEmitter()->addListener(
    'client.authentication.failed', function (\League\OAuth2\Server\RequestEvent $event) { // do something } ); 

客户端身份验证未经过时触发此事件。你能够在客户端尝试 n 次失败后禁止它一段时间内的再次尝试。

user.authentication.failed
$server->getEmitter()->addListener(
    'user.authentication.failed', function (\League\OAuth2\Server\RequestEvent $event) { // do something } ); 

用户身份验证未经过时触发此事件。你能够经过这里提醒用户重置密码,或尝试 n 次后禁止用户再次尝试。

参考文章

《oauth2-server 官方文档》(https://oauth2.thephpleague.com/)

《理解OAuth 2.0》-阮一峰(http://www.ruanyifeng.com/blog/2014/05/oauth_2_0.html

相关文章
相关标签/搜索