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

相關文章
相關標籤/搜索