PHP下的Oauth2.0嘗試 - OpenID Connect

OpenID Connect

OpenID Connect簡介

OpenID Connect是基於OAuth 2.0規範族的可互操作的身份驗證協議。它使用簡單的REST / JSON消息流來實現,和之前任何一種身份認證協議相比,開發者可以輕鬆集成。
OpenID Connect允許開發者驗證跨網站和應用的用戶,而無需擁有和管理密碼文件。OpenID Connect允許所有類型的客戶,包括基於瀏覽器的JavaScript和本機移動應用程序,啓動登錄流動和接收可驗證斷言對登錄用戶的身份。

OpenID的歷史是什麼?

OpenID Connect是OpenID的第三代技術。首先是原始的OpenID,它不是商業應用,但讓行業領導者思考什麼是可能的。OpenID 2.0設計更爲完善,提供良好的安全性保證。然而,其自身存在一些設計上的侷限性,最致命的是其中依賴方必須是網頁,但不能是本機應用程序;此外它還要依賴XML,這些都會導致一些應用問題。
OpenID Connect的目標是讓更多的開發者使用,並擴大其使用範圍。幸運的是,這個目標並不遙遠,現在有很好的商業和開源庫來幫助實現身份驗證機制。

OIDC基礎

簡要而言,OIDC是一種安全機制,用於應用連接到身份認證服務器(Identity Service)獲取用戶信息,並將這些信息以安全可靠的方法返回給應用。
在最初,因爲OpenID1/2經常和OAuth協議(一種授權協議)一起提及,所以二者經常被搞混。

OpenID是Authentication,即認證,對用戶的身份進行認證,判斷其身份是否有效,也就是讓網站知道“你是你所聲稱的那個用戶”;
OAuth是Authorization,即授權,在已知用戶身份合法的情況下,經用戶授權來允許某些操作,也就是讓網站知道“你能被允許做那些事情”。
由此可知,授權要在認證之後進行,只有確定用戶身份只有才能授權。

(身份驗證)+ OAuth 2.0 = OpenID Connect

OpenID Connect是“認證”和“授權”的結合,因爲其基於OAuth協議,所以OpenID-Connect協議中也包含了client_id、client_secret還有redirect_uri等字段標識。這些信息被保存在“身份認證服務器”,以確保特定的客戶端收到的信息只來自於合法的應用平臺。這樣做是目的是爲了防止client_id泄露而造成的惡意網站發起的OIDC流程。

在OAuth中,這些授權被稱爲scope。OpenID-Connect也有自己特殊的scope--openid ,它必須在第一次請求“身份鑑別服務器”(Identity Provider,簡稱IDP)時發送過去。

實現

證書

# 生成私鑰 private key
$ openssl genrsa -out privkey.pem 2048

# 私鑰生成公鑰 public key
$ openssl rsa -in privkey.pem -pubout -out pubkey.pem

調整server

private function server()
{
    $pdo = new \PDO('mysql:host=121.41.24.216;dbname=oauth_test;charset=utf8mb4', "admin", "sxx170615");
    $storage = new \OAuth2\Storage\Pdo($pdo);

    $config = [
        'use_openid_connect' => true, //openid 必須設置
        'issuer' => 'sxx.qkl.local'
    ];

    $server = new \OAuth2\Server($storage, $config);

    // 第二個參數,必須設置值爲public_key
    $server->addStorage($this->getKeyStorage(), 'public_key');

    // 添加 Authorization Code 授予類型
    $server->addGrantType(new \OAuth2\GrantType\AuthorizationCode($storage));

    // 添加 Client Credentials 授予類型  一般三方應用都是直接通過client_id & client_secret直接請求獲取access_token
    $server->addGrantType(new \OAuth2\GrantType\ClientCredentials($storage));

    return $server;
}

private function getKeyStorage()
{
    $rootCache = dirname(APP_PATH) . "/cert/oauth/";
    $publicKey  = file_get_contents($rootCache.'pubkey.pem');
    $privateKey = file_get_contents($rootCache.'privkey.pem');

    // create storage
    $keyStorage = new \OAuth2\Storage\Memory(array('keys' => array(
        'public_key'  => $publicKey,
        'private_key' => $privateKey,
    )));

    return $keyStorage;
}

授權

public function authorize()
{
    // scope增加openid
    // 該頁面請求地址類似:
    // http://sxx.qkl.local/v2/oauth/authorize?response_type=code&client_id=testclient&state=xyz&redirect_uri=http://sxx.qkl.local/v2/oauth/cb&scope=basic%20get_user_info%20upload_pic%20openid
    //獲取server對象
    $server = $this->server();
    $request = \OAuth2\Request::createFromGlobals();
    $response = new \OAuth2\Response();

    // 驗證 authorize request
    // 這裏會驗證client_id,redirect_uri等參數和client是否有scope
    if (!$server->validateAuthorizeRequest($request, $response)) {
        $response->send();
        die;
    }

    // 顯示授權登錄頁面
    if (empty($_POST)) {
        //獲取client類型的storage
        //不過這裏我們在server裏設置了storage,其實都是一樣的storage->pdo.mysql
        $pdo = $server->getStorage('client');
        //獲取oauth_clients表的對應的client應用的數據
        $clientInfo = $pdo->getClientDetails($request->query('client_id'));
        $this->assign('clientInfo', $clientInfo);
        $this->display('authorize');
        die();
    }

    $is_authorized = true;
    // 當然這部分常規是基於自己現有的帳號系統驗證
    if (!$uid = $this->checkLogin($request)) {
        $is_authorized = false;
    }

    // 這裏是授權獲取code,並拼接Location地址返回相應
    // Location的地址類似:http://sxx.qkl.local/v2/oauth/cb?code=69d78ea06b5ee41acbb9dfb90500823c8ac0241d&state=xyz
    $server->handleAuthorizeRequest($request, $response, $is_authorized, $uid);
    if ($is_authorized) {
        // 這裏會創建Location跳轉,你可以直接獲取相關的跳轉url,用於debug
        $parts = parse_url($response->getHttpHeader('Location'));
        var_dump($parts);
        parse_str($parts['query'], $query);

        // 拉取oauth_authorization_codes記錄的信息,包含id_token
        $code = $server->getStorage('authorization_code')
            ->getAuthorizationCode($query['code']);
        var_dump($code);
    }
//        $response->send();
}

clipboard.png

curl獲取

# 使用 HTTP Basic Authentication
$ curl -u testclient:123456 http://sxx.qkl.local/v2/oauth/token -d 'grant_type=client_credentials'

# 使用 POST Body 請求
$ curl http://sxx.qkl.local/v2/oauth/token -d 'grant_type=client_credentials&client_id=testclient&client_secret=123456'

postman獲取access_token

clipboard.png

總結

access_token 用於授權
id_token(通常爲JWT) 用於認證

通常我們
首先,需要使用id_token登錄
然後,你會得到一個access_token
最後,使用access_token來訪問授權相關接口。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章