Oauth2.0 協議簡介及 php實例代碼

轉自:http://www.dahouduan.com/2017/11/21/oauth2-php/php

https://blog.csdn.net/halsonhe/article/details/81030319html

 

Oauth2.0 是一個開源的受權協議,在全世界獲得普遍應用,比較大的社交服務都支持了Oauth2.0 協議,例如 QQ,微博微信nginx

Oauth2 協議的使用場景

假設有一個叫「教程集」的網站,能夠經過讀取用戶在微信裏的好友關係查詢到還有誰也在學習教程,用戶想使用該服務,就必須讓「教程集」讀取本身在微信裏的好友關係。git

微信只有獲得用戶的受權纔會容許「教程集」 讀取用戶的好友關係,這時候傳統的方式是,用戶將微信賬號密碼提交給「教程集」,「教程集」使用用戶的賬號密碼登陸微信,再獲取到用戶的好友關係。github

可是這樣的作法有幾個缺點:sql

  • 爲了後續用戶不須要再次輸入微信賬號密碼,「教程集」不得不保存用戶的微信賬號密碼;
  • 假設用戶想收回對「教程集」的受權,只能修改密碼,可是同時會使其餘得到了用戶受權的第三方程序所有失效;
  • 用戶沒法控制「教程集」的權限範圍和有效時間,用戶將微信賬號密碼都提交給「教程集」,「教程集」得到了幾乎全部權限。
  • 假設「教程集」被破解,全部用戶的微信賬號密碼同時泄露。

Oauth2 正是用來解決以上場景遇到的問題的。數據庫

Oauth2 協議的幾個專業術語

  1. Third-party application: 第三方應用程序,又稱「客戶端」, 即上面例子中的「教程集」;
  2. HTTP service : HTTP服務提供商,即「服務提供商」, 即上面例子中的「微信」;
  3. Resource Owner: 資源全部者,即上面例子中的「用戶」;
  4. User Agent:用戶代理,在本文中即「瀏覽器」;
  5. Authorized server: 認證服務器,即服務提供商提供的專門用來作用戶認證的服務器,在上面的例子中屬於微信服務的一部分;
  6. Resource server: 資源服務器,即服務提供商提供的用戶存放用戶資源的服務器,在上面的例子中能夠視爲是一個查詢用戶好友關係的接口。

Oauth2 運行的通常流程

爲了描述方便仍是用微信舉例子:json

  1. 用打開客戶端,客戶端要求用戶給予受權。
  2. 用戶贊成給予受權。
  3. 客戶端使用上一步獲取的受權碼,向認證服務器換取令牌。
  4. 認證服務器檢查客戶端發來的受權碼,確認無誤,向客戶端發放令牌。
  5. 客戶端使用令牌,向資源服務器獲取資源。
  6. 資源服務器驗證令牌無誤,贊成向客戶端開放資源。

其中最關鍵的是第 「2」,即客戶端如何獲取用戶的受權,客戶端拿到受權碼就能夠向認證服務器換取令牌,Oauth2 提供了4種受權的方式:瀏覽器

  • 受權碼模式 (authorized code)
  • 簡化模式 (implict)
  • 密碼模式 (resource owner password credential)
  • 客戶端模式( client credentials)

下面咱們主要講下經常使用的受權碼模式。緩存

受權碼模式

受權碼模式,即咱們最經常使用的受權模式,目前微博、微信、QQ 等都是用的這種受權方式,這種受權模式是最嚴密,功能最完整的。

主要流程以下:

  1. 用戶訪問客戶端,客戶端將用戶導向(通常是 302跳轉)認證服務器,
  2. 認證服務器判斷用戶是否已登陸,若是沒有登陸,則在認證服務器提供的登陸界面進行登陸
  3. 假設已經登陸,用戶選擇是否給予客戶端受權(認證服務器通常會提供受權界面,用戶能夠選擇開放哪些資源給客戶端),假設用戶給予受權,認證服務器將導向客戶端實現指定的「重定向URI」(rediret_uri),同時在 redirect_uri 上附一個受權碼
  4. 客戶端收到受權碼,附上早先的「重定向URL」, 向認證服務器申請令牌。(這一步在後臺完成,用戶不可見)
  5. 認證服務器覈對了受權碼和重定向URI,確認無誤後,向客戶端發送訪問令牌(access_token)和更新令牌(refresh_token)。
  6. 客戶端使用 access_token 向資源服務器獲取資源。

步驟1中涉及的參數:

參數名 必填 說明
response_type 此處必須爲「code」
client_id 客戶端id
redirect_uri 重定向 URI
scope 申請的受權範圍
state 客戶端當前狀態,能夠是任意值,認證服務器會原樣返回這個參數

例子:

GET http://oauth2-server.dev/authorize.php?response_type=code&client_id=testclient&state=xyz&redirect_uri=http%3A%2F%2F127.0.0.1%3A8001%2F%2Fclient.php

步驟3 中,認證服務器迴應客戶端的URI,包含如下參數:

參數名 必填 說明
code 受權碼, 該受權碼有效期很短,例如30秒,且只能使用一次。
state 若是客戶端中請求中包含這個參數,認證服務器的響應也必須包含一樣的參數和值

例子

HTTP/1.1 302 Found
Location: http://oauth2-client.dev/client.php?code=ef2d9cd1bc71d99fa4ad193beab1bff48ec65df4&state=xyz

步驟4 中,客戶端向認證服務器申請令牌,包含如下參數:

參數名 必填 說明
grant_type 表示受權模式,此處值固定爲 」authorized_code」
code 上一步得到的受權碼
redirect_uri 重定向URI,必須跟步驟1中的該參數值保持一致。
client_id 表示客戶端id

例子:

POST /token.php HTTP/1.1
Host: 127.0.0.1:8001
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code&code=SplxlOBeZQQYbYS6WxSbIA&&redirect_uri=http%3A%2F%2F127.0.0.1%3A8001%2F%2Fclient.php

步驟5 中,認證服務器回覆 HTTP 請求,包含如下參數:

參數名 必填 說明
access_token 訪問令牌
token_type 令牌類型,能夠是 bearer 或者 mac 類型
expire_in 表示過時時間,單位爲秒,
refresh_token 用來獲取下一次的令牌訪問
scope 表示權限範圍

例子:

HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
Cache-Control: no-store
Pragma: no-cache

{
  "access_token":"2YotnFZFEjr1zCsicMWpAA",
  "token_type":"example",
  "expires_in":3600,
  "refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA"
}

Cache-Control, 表示強制此接口不緩存。

PHP 實現Oauth2.0 受權碼模式

爲了方便的在咱們本身的項目中集成 Oauth2, 能夠使用一個第三方類庫來實現 oauth2-server-php

下面經過一個Demo 來演示oauth2 的整個流程

搭建測試環境

代碼分爲客戶端、服務端兩個部分:
克隆測試代碼到本地

git clone  git@github.com:shanhuhai/oauth2-demo.git 
cd oauth2-demo
composer install

克隆完代碼後, 在 oauth2-demo/example 下面有兩個目錄分別爲 client 和 server 對應客戶端和服務端。
將這兩個目錄在nginx分別綁定 oauth2-client.dev 和 oauth2-server.dev 域名。

安裝數據庫

建立一個數據庫 ‘my_oauth2_db’, 下載這個sql 文件並導入數據庫中
Oauth2演示用數據表

打開 oauth2-demo/example/server/common.php 配置好數據庫信息。

開始測試

在你的hosts 文件中,配置

127.0.0.1 oauth2-client.dev
127.0.0.1 oauth2-server.dev

在瀏覽器打開 http://oauth2-client.dev/index.php,就能夠測試了,
默認提供的測試賬號是 「shanhuhai」, 密碼 「123123」

代碼說明

在代碼註釋中標明瞭業務流程的關鍵點,對應前文列出的主要步驟。
客戶端:

客戶端只有一個文件。

index.php

  1.  
    <!doctype html>
  2.  
    <html lang="zh-CN">
  3.  
    <head>
  4.  
    <title>用戶信息</title>
  5.  
    </head>
  6.  
    <body>
  7.  
     
  8.  
    <?php
  9.  
    require '../../vendor/autoload.php';
  10.  
    define('CLIENT_URL', 'http://oauth2-client.dev');
  11.  
    define('SERVER_URL', 'http://oauth2-server.dev');
  12.  
    define('REDIRECT_URI', CLIENT_URL.'/index.php');
  13.  
    define('RESOURCE_URL', SERVER_URL.'/resource.php');
  14.  
     
  15.  
    define('CLIENT_ID', 'testclient');
  16.  
    define('CLIENT_SECRET', 'testpass');
  17.  
     
  18.  
     
  19.  
    session_start();
  20.  
    function userInfo(){
  21.  
    if(isset($_SESSION['username'])) {
  22.  
    return $_SESSION;
  23.  
    } else {
  24.  
    return false;
  25.  
    }
  26.  
    }
  27.  
     
  28.  
     
  29.  
    if(isset($_REQUEST['logout'])) {
  30.  
    unset($_SESSION['username']);
  31.  
    session_destroy();
  32.  
    }
  33.  
     
  34.  
     
  35.  
    $userInfo = userInfo();
  36.  
    /*
  37.  
    * 接收用戶中心返回的受權碼
  38.  
    */
  39.  
    if (isset($_REQUEST['code']) && $_SERVER['REQUEST_URI']) {
  40.  
    //將認證服務器返回的受權碼從 URL 中解析出來
  41.  
    $code = substr($_SERVER['REQUEST_URI'], strpos($_SERVER['REQUEST_URI'], 'code=')+5, 40);
  42.  
     
  43.  
    // 步驟4 拿受權碼去申請令牌
  44.  
    $client = new GuzzleHttp\Client();
  45.  
    $response = $client->request('POST', SERVER_URL.'/token.php', [
  46.  
    'auth' => [CLIENT_ID, CLIENT_SECRET],
  47.  
     
  48.  
    'form_params'=> [
  49.  
    'grant_type'=>'authorization_code',
  50.  
    'code'=> $code,
  51.  
    'redirect_uri'=> REDIRECT_URI,
  52.  
    ]
  53.  
    ]);
  54.  
     
  55.  
    $response = json_decode($response->getBody(), true);
  56.  
     
  57.  
    // 將令牌緩存到 SESSION中,方便後續訪問
  58.  
    $_SESSION['access_token'] = $response['access_token'];
  59.  
     
  60.  
    // 步驟6 使用令牌獲取用戶信息
  61.  
    $response = $client->request('GET', RESOURCE_URL.'?access_token='.$_SESSION['access_token']);
  62.  
    $response = json_decode($response->getBody(), true);
  63.  
     
  64.  
    $userInfo = $response['userInfo'];
  65.  
    $_SESSION = array_merge($_SESSION, $userInfo);
  66.  
     
  67.  
    }
  68.  
     
  69.  
    // 步驟1,點擊此連接跳轉到認證中心
  70.  
    $auth_url = SERVER_URL."/authorize.php?response_type=code&client_id=testclient&state=xyz&redirect_uri=". REDIRECT_URI;
  71.  
     
  72.  
    ?>
  73.  
     
  74.  
    <?php if($userInfo): ?>
  75.  
    歡迎 <?php echo $userInfo['username'];?>, 頭像 <img src="<?php echo $userInfo['avatar']; ?>" alt="" />
  76.  
    <a href="/index.php?logout=1">退出登陸</a>
  77.  
    <?php else: ?>
  78.  
    <a href="<?php echo $auth_url ?>">使用媒體雲登陸</a>
  79.  
    <?php endif;?>
  80.  
    </body>
  81.  
    </html>
  82.  
     

服務端:

authorize.php

  1.  
    <?php
  2.  
    require_once __DIR__."/common.php";
  3.  
    $_SESSION['authorize_querystring'] = $_SERVER['QUERY_STRING'];
  4.  
     
  5.  
    // 步驟2 判斷若是沒有登陸則跳轉到登陸界面
  6.  
    if(!isset($_SESSION['username']) && strpos($_SERVER['REQUEST_URI'], 'login.php') === false) {
  7.  
    header("Location: ".SERVER_URL.'/login.php');
  8.  
    exit;
  9.  
    }
  10.  
     
  11.  
    $request = OAuth2\Request::createFromGlobals();
  12.  
     
  13.  
    $response = new \OAuth2\Response();
  14.  
     
  15.  
    if(!$server->validateAuthorizeRequest($request, $response)) {
  16.  
     
  17.  
    $response->send();
  18.  
    die;
  19.  
    }
  20.  
     
  21.  
     
  22.  
    if(empty($_POST)) {
  23.  
    // 步驟3 ,用戶已經在認證中心登陸,用戶選擇是否開放受權給客戶端
  24.  
    exit('<form method="post">
  25.  
    <label>是否受權給 '.$_GET['client_id'].'?</label><br />
  26.  
    <input type="submit" name="authorized" value="yes">
  27.  
    <input type="submit" name="authorized" value="no">
  28.  
    </form>
  29.  
    <a href="/login.php?logout=1">退出登陸</a>
  30.  
    ');
  31.  
    }
  32.  
     
  33.  
    // print the authorization code if the user has authorized your client
  34.  
    $is_authorized = ($_POST['authorized'] === 'yes');
  35.  
    $server->handleAuthorizeRequest($request, $response, $is_authorized);
  36.  
     
  37.  
    $response->send();
  38.  
     
  39.  
     

token.php

    1.  
      <?php
    2.  
       
    3.  
      // 步驟5 ,認證服務器發放令牌
    4.  
      require_once __DIR__ . '/common.php';
    5.  
      $server->handleTokenRequest(OAuth2\Request::createFromGlobals())->send();
相關文章
相關標籤/搜索