Laravel Socialite 详解

不久以前 Dearmadman 曾写过一篇 使用 Laravel Socialite 集成微信登陆 的文章,可是彷佛仍是有些同窗不太明白,询问着如何集成 QQ 登陆,那么,本篇咱们就来剖析一下 Laravel Socialite 的详细内容。让各位同窗都得到 Laravel Socialite 所提供的驱动适配能力。php

Laravel Socialite

Laravel Socialite 为第三方应用的 OAuth 认证提供了很是丰富友好的接口,咱们使用它能够很是方便快捷的对相似微信、微博等第三方登陆进行集成。git

那么首先,你须要知道 Laravel Socialite 主要是用于那些 OAuth 受权登陆的。这些场景下的第三方应用好比微信,QQ,微博等,都使用了 OAuth 协议为用户提供受权其它应用获取用户信息及权限的能力。github

Open Authorization

OAuth(开放受权)是一个开放标准,容许用户让第三方应用访问该用户在某一网站上存储的私密的资源(如照片,视频,联系人列表),而无需将用户名和密码提供给第三方应用。json

OAuth 容许用户提供一个令牌,而不是用户名和密码来访问他们存放在特定服务提供者的数据。每个令牌受权一个特定的网站(例如,视频编辑网站)在特定的时段(例如,接下来的 2 小时内)内访问特定的资源(例如仅仅是某一相册中的视频)。这样,OAuth 让用户能够受权第三方网站访问他们存储在另外服务提供者的某些特定信息,而非全部内容。数组

咱们的应用与使用 OAuth 标准的第三方应用的交互流程通常是这样的:微信

  • 展现跳转到第三方应用登陆的连接app

  • 用户点击连接跳转到第三方应用登陆并进行受权ide

  • 在用户受权后第三方应用会跳转到咱们所指定的应用回调资源地址并伴随用于交互 AccessToken 的 Code函数

  • 咱们的应用拿到 Code 后自主请求第三方应用并使用 Code 获取该用户的 AccessTokenjsonp

  • 获取 AccessToken 以后的应用便可自主的从第三方应用中获取用户的资源信息

详解

对于以上流程,咱们来分析一下 Laravel Socialite 针对上面的每一步都作了些什么,咱们将构建一个 QQProvider 来做为演示示例:

第三方应用的登陆连接

显而易见的,第一步咱们须要构建用于跳转到第三方登陆的 URL,这里须要提供一些额外的参数来进行应用的识别,好比,当你为你的应用注册 QQ 登陆网站接入应用时,QQ 互联会为你提供一组数据用来验证登陆来源的合法性,一般这些关键数据为如下几项:

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

在 Laravel Socialite 中这些关键数据映射到了 AbstractProvider 顶级抽象中,你能够将这些配置项定义到 services.php 配置文件中:

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

咱们将会在后续注册扩展驱动时使用这些配置。

若是须要为第三方应用登陆构建自定义的驱动,那么你须要先继承 AbstractProvider,而且实现 ProviderInterface 接口,由于国内流行的应用都使用的 OAuth 2.0 协议,好比 QQ,就是使用的这种协议,因此咱们须要继承 LaravelSocialiteTwoAbstractProvider 抽象类,而且实现 ProviderInterface 接口。

ProviderInterface 接口限定了两个方法: redirectuser,其中 redirect 方法就是返回构建后的跳转响应,用于跳转到第三方应用进行受权登陆,而 user 方法就是用来在用户赞成受权登陆以后,获取第三方应用中用户的信息数据的。这些方法已经在 AbstractProvider 抽象类中提供了实现。因此基本上你不须要再去实现它。

Socialite 经过三个函数的配合来构造跳转 URL,它们分别是 getAuthUrlbuildAuthUrlFromBasegetCodeFields

  • getAuthUrl($state): 返回构造完成后的跳转 URL,它须要调用 BuildAuthUrlFromBase 函数,咱们只须要在这里指定第三方应用所规定的应用登陆受权的地址便可。

  • BuildAuthUrlFromBase($url, $state): 它主要是为受权地址构建相应的查询参数,将驱动配置中的关键数据以及第三方应用所规定的数据填充到 URL 中。在它的内部须要调用 getCodeFields 函数,咱们只须要在这里返回第三方应用所规定的应用登陆受权的地址与查询参数合并便可。

  • getCodeFields($state = null): 将驱动配置信息映射到第三方应用登陆验证所需的参数中。

使用 Code 换取 AccessToken

当用户赞成登陆受权以后,第三方应用会将地址跳转到 redirect 所配置的地址中,并携带 code 参数,你须要使用 code 来换取 access_token。而后使用 access_token 换取用户的信息数据。这就是集成登陆的整个流程。

AbstractProvider 中已经为 user 方法提供了基本的实现,而且它将常常变化的部分提取到了其它的方法中,这样咱们在开发新驱动的时候,基本不须要重写 user 方法。咱们来看一些为了完成这些操做,咱们须要用到哪些方法。

首先,在登陆回调的路由中,咱们会得到请求中携带的 code 参数,而后须要使用 code 来换取 access_token,咱们先来看一下底层 AbstractProvideruser 的实现:

/**
 * {@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'));
}

其中用到的关键方法有:

  • getAccessTokenResponse($code): 它须要使用 code 来换取 access_token 响应的主体,它应该返回一个包含了 access_token 的数组。根据不一样应用所规定的响应格式,你可能会须要在这里作出一些适配,那么,在集成自定义驱动时,重写这个方法就能够了。

  • getUserByToken($token): 使用 access_token 向第三方应用获取用户的数据。它应该返回一个数组。

  • mapUserToObject(array $user): 它应该在使用 access_token 换取用户数据以后,将用户数据数组存储到 LaravelSocialiteTwoUser 实例中的 user 属性中,并将相应的数据映射到实例的属性。

因此流程上也就很清晰了,那么扩展自定义的驱动也就很简单了,调用 getAccessTokenResponse 方法时,须要指定换取 token 的 URL,因此咱们须要重写 getTokenUrl 方法:

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

在构建换取 token 的 URL 时,还要组合相应的参数,因此咱们须要重写 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 换取用户数据的业务逻辑就能够了,它应该返回一个数组:

/**
 * {@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 集成微信登陆 中咱们已经谈到过 Socialite 自定义驱动的实现机制:

<?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 协议的流程进行了高度的抽象,并将相应的功能解耦,因此在自定义扩展时你并不须要作出太多,若是你想要了解的更多,你能够参考 larastarscn/socialite 的源码,它只是简单的为微信,QQ,微博的登陆提供了扩展驱动。

PS: 欢迎关注简书 Laravel 专题,也欢迎 Laravel 相关文章的投稿 :),做者知识技能水平有限,若是你有更好的设计方案欢迎讨论交流,若是有错误的地方也请批评指正,在此表示感谢谢谢 :)

相关文章
相关标签/搜索