Laravel Socialite 詳解

不久之前 Dearmadman 曾寫過一篇 使用 Laravel Socialite 集成微信登錄 的文章,但是似乎還是有些同學(xué)不太明白,詢問著如何集成 QQ 登錄,那么,本篇我們就來剖析一下 Laravel Socialite 的詳細(xì)內(nèi)容。讓各位同學(xué)都獲得 Laravel Socialite 所提供的驅(qū)動適配能力。

Laravel Socialite

Laravel Socialite 為第三方應(yīng)用的 OAuth 認(rèn)證提供了非常豐富友好的接口,我們使用它可以非常方便快捷的對類似微信、微博等第三方登錄進(jìn)行集成。

那么首先,你需要知道 Laravel Socialite 主要是用于那些 OAuth 授權(quán)登錄的。這些場景下的第三方應(yīng)用比如微信,QQ,微博等,都使用了 OAuth 協(xié)議為用戶提供授權(quán)其它應(yīng)用獲取用戶信息及權(quán)限的能力。

Open Authorization

OAuth(開放授權(quán))是一個開放標(biāo)準(zhǔn),允許用戶讓第三方應(yīng)用訪問該用戶在某一網(wǎng)站上存儲的私密的資源(如照片,視頻,聯(lián)系人列表),而無需將用戶名和密碼提供給第三方應(yīng)用。

OAuth 允許用戶提供一個令牌,而不是用戶名和密碼來訪問他們存放在特定服務(wù)提供者的數(shù)據(jù)。每一個令牌授權(quán)一個特定的網(wǎng)站(例如,視頻編輯網(wǎng)站)在特定的時段(例如,接下來的 2 小時內(nèi))內(nèi)訪問特定的資源(例如僅僅是某一相冊中的視頻)。這樣,OAuth 讓用戶可以授權(quán)第三方網(wǎng)站訪問他們存儲在另外服務(wù)提供者的某些特定信息,而非所有內(nèi)容。

我們的應(yīng)用與使用 OAuth 標(biāo)準(zhǔn)的第三方應(yīng)用的交互流程一般是這樣的:

  • 展示跳轉(zhuǎn)到第三方應(yīng)用登錄的鏈接
  • 用戶點擊鏈接跳轉(zhuǎn)到第三方應(yīng)用登錄并進(jìn)行授權(quán)
  • 在用戶授權(quán)后第三方應(yīng)用會跳轉(zhuǎn)到我們所指定的應(yīng)用回調(diào)資源地址并伴隨用于交互 AccessToken 的 Code
  • 我們的應(yīng)用拿到 Code 后自主請求第三方應(yīng)用并使用 Code 獲取該用戶的 AccessToken
  • 獲取 AccessToken 之后的應(yīng)用即可自主的從第三方應(yīng)用中獲取用戶的資源信息

詳解

對于以上流程,我們來分析一下 Laravel Socialite 針對上面的每一步都做了些什么,我們將構(gòu)建一個 QQProvider 來作為演示示例:

第三方應(yīng)用的登錄鏈接

顯而易見的,第一步我們需要構(gòu)建用于跳轉(zhuǎn)到第三方登錄的 URL,這里需要提供一些額外的參數(shù)來進(jìn)行應(yīng)用的識別,比如,當(dāng)你為你的應(yīng)用注冊 QQ 登錄網(wǎng)站接入應(yīng)用時,QQ 互聯(lián)會為你提供一組數(shù)據(jù)用來驗證登錄來源的合法性,通常這些關(guān)鍵數(shù)據(jù)為以下幾項:

{
  appid: 'xxxxxxxxx',
  app_secret: 'xxxxxxxxx',
  redirect: 'xxxxxxxxxxx'
}

在 Laravel Socialite 中這些關(guān)鍵數(shù)據(jù)映射到了 AbstractProvider 頂級抽象中,你可以將這些配置項定義到 services.php 配置文件中:

'qq' => [
    'client_id' => 'your-qq-app-id',
    'client_secret' => 'your-qq-app-secret',
    'redirect' => 'http://your-callback-url',
],

我們將會在后續(xù)注冊擴展驅(qū)動時使用這些配置。

如果需要為第三方應(yīng)用登錄構(gòu)建自定義的驅(qū)動,那么你需要先繼承 AbstractProvider,并且實現(xiàn) ProviderInterface 接口,因為國內(nèi)流行的應(yīng)用都使用的 OAuth 2.0 協(xié)議,比如 QQ,就是使用的這種協(xié)議,所以我們需要繼承 Laravel\Socialite\Two\AbstractProvider 抽象類,并且實現(xiàn) ProviderInterface 接口。

ProviderInterface 接口限定了兩個方法: redirectuser,其中 redirect 方法就是返回構(gòu)建后的跳轉(zhuǎn)響應(yīng),用于跳轉(zhuǎn)到第三方應(yīng)用進(jìn)行授權(quán)登錄,而 user 方法就是用來在用戶同意授權(quán)登錄之后,獲取第三方應(yīng)用中用戶的信息數(shù)據(jù)的。這些方法已經(jīng)在 AbstractProvider 抽象類中提供了實現(xiàn)。所以基本上你不需要再去實現(xiàn)它。

Socialite 通過三個函數(shù)的配合來構(gòu)造跳轉(zhuǎn) URL,它們分別是 getAuthUrl,buildAuthUrlFromBasegetCodeFields

  • getAuthUrl($state): 返回構(gòu)造完成后的跳轉(zhuǎn) URL,它需要調(diào)用 BuildAuthUrlFromBase 函數(shù),我們只需要在這里指定第三方應(yīng)用所規(guī)定的應(yīng)用登錄授權(quán)的地址即可。
  • BuildAuthUrlFromBase($url, $state): 它主要是為授權(quán)地址構(gòu)建相應(yīng)的查詢參數(shù),將驅(qū)動配置中的關(guān)鍵數(shù)據(jù)以及第三方應(yīng)用所規(guī)定的數(shù)據(jù)填充到 URL 中。在它的內(nèi)部需要調(diào)用 getCodeFields 函數(shù),我們只需要在這里返回第三方應(yīng)用所規(guī)定的應(yīng)用登錄授權(quán)的地址與查詢參數(shù)合并即可。
  • getCodeFields($state = null): 將驅(qū)動配置信息映射到第三方應(yīng)用登錄驗證所需的參數(shù)中。

使用 Code 換取 AccessToken

當(dāng)用戶同意登錄授權(quán)之后,第三方應(yīng)用會將地址跳轉(zhuǎn)到 redirect 所配置的地址中,并攜帶 code 參數(shù),你需要使用 code 來換取 access_token。然后使用 access_token 換取用戶的信息數(shù)據(jù)。這就是集成登錄的整個流程。

AbstractProvider 中已經(jīng)為 user 方法提供了基本的實現(xiàn),并且它將經(jīng)常變化的部分提取到了其它的方法中,這樣我們在開發(fā)新驅(qū)動的時候,基本不需要重寫 user 方法。我們來看一些為了完成這些操作,我們需要用到哪些方法。

首先,在登錄回調(diào)的路由中,我們會獲得請求中攜帶的 code 參數(shù),然后需要使用 code 來換取 access_token,我們先來看一下底層 AbstractProvideruser 的實現(xiàn):

/**
 * {@inheritdoc}
 */
public function user()
{
    if ($this->hasInvalidState()) {
        throw new InvalidStateException;
    }

    $response = $this->getAccessTokenResponse($this->getCode());

    $user = $this->mapUserToObject($this->getUserByToken(
        $token = Arr::get($response, 'access_token')
    ));

    return $user->setToken($token)
                ->setRefreshToken(Arr::get($response, 'refresh_token'))
                ->setExpiresIn(Arr::get($response, 'expires_in'));
}

其中用到的關(guān)鍵方法有:

  • getAccessTokenResponse($code): 它需要使用 code 來換取 access_token 響應(yīng)的主體,它應(yīng)該返回一個包含了 access_token 的數(shù)組。根據(jù)不同應(yīng)用所規(guī)定的響應(yīng)格式,你可能會需要在這里做出一些適配,那么,在集成自定義驅(qū)動時,重寫這個方法就可以了。
  • getUserByToken($token): 使用 access_token 向第三方應(yīng)用獲取用戶的數(shù)據(jù)。它應(yīng)該返回一個數(shù)組。
  • mapUserToObject(array $user): 它應(yīng)該在使用 access_token 換取用戶數(shù)據(jù)之后,將用戶數(shù)據(jù)數(shù)組存儲到 Laravel\Socialite\Two\User 實例中的 user 屬性中,并將相應(yīng)的數(shù)據(jù)映射到實例的屬性。

所以流程上也就很清晰了,那么擴展自定義的驅(qū)動也就很簡單了,調(diào)用 getAccessTokenResponse 方法時,需要指定換取 token 的 URL,所以我們需要重寫 getTokenUrl 方法:

/**
 * {@inheritdoc}.
 */
public function getTokenUrl()
{
    return 'https://graph.qq.com/oauth2.0/token';
}

在構(gòu)建換取 token 的 URL 時,還要組合相應(yīng)的參數(shù),所以我們需要重寫 getTokenFields 方法:

/**
* {@inheritdoc}.
*/
protected function getTokenFields($code)
{
    return [
        'client_id' => $this->clientId, 'client_secret' => $this->clientSecret,
        'code' => $code, 'grant_type' => 'authorization_code',
        'redirect_uri' => $this->redirectUrl,
    ];
}

然后,在 getUserByToken 方法中完成使用 token 換取用戶數(shù)據(jù)的業(yè)務(wù)邏輯就可以了,它應(yīng)該返回一個數(shù)組:

/**
 * {@inheritdoc}.
 */
public function getUserByToken($token)
{
    $response = $this->getHttpClient()->get('https://graph.qq.com/oauth2.0/me', [
        'query' => [
            'access_token' => $token,
        ],
    ]);

    $openId = json_decode($this->jsonpToJson($response->getBody()->getContents()), true)['openid'];
    $this->setOpenId($openId);

    $response = $this->getHttpClient()->get('https://graph.qq.com/user/get_user_info', [
        'query' => [
            'oauth_consumer_key' => $this->clientId, 'access_token' => $token,
            'openid' => $openId, 'format' => 'json'
        ]
    ]);


    return json_decode($response->getBody(), true);

}

最后就是注冊了,在 使用 Laravel Socialite 集成微信登錄 中我們已經(jīng)談到過 Socialite 自定義驅(qū)動的實現(xiàn)機制:


<?php

namespace Crowdfunding\Providers\Socialite;

use Illuminate\Support\ServiceProvider;

class WechatServiceProvider extends ServiceProvider
{
    public function boot()
    {
       $this->app->make('Laravel\Socialite\Contracts\Factory')->extend('wechat', function ($app) {
            $config = $app['config']['services.wechat'];
            return new WechatProvider(
                $app['request'], $config['client_id'],
                $config['client_secret'], $config['redirect']
            );
       });
    }
    public function register()
    {

    }
}

Laravel Socialite 對 OAuth 協(xié)議的流程進(jìn)行了高度的抽象,并將相應(yīng)的功能解耦,所以在自定義擴展時你并不需要做出太多,如果你想要了解的更多,你可以參考 larastarscn/socialite 的源碼,它只是簡單的為微信,QQ,微博的登錄提供了擴展驅(qū)動。

PS: 歡迎關(guān)注簡書 Laravel 專題,也歡迎 Laravel 相關(guān)文章的投稿 :),作者知識技能水平有限,如果你有更好的設(shè)計方案歡迎討論交流,如果有錯誤的地方也請批評指正,在此表示感謝謝謝 :)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容