andyjessop/laravel-facebook

Laravel 5.1 的 Facebook PHP SDK v5 集成

dev-master 2015-08-05 14:19 UTC

This package is not auto-updated.

Last update: 2024-10-02 10:10:26 UTC


README

安装

{
    "require": {
        "andyjessop/laravel-facebook": "dev-master"
    }
}

服务提供者

在你的应用配置中,将 LaravelFacebookServiceProvider 添加到提供者数组中。

'providers' => [
    AndyJessop\LaravelFacebook\LaravelFacebookServiceProvider::class,
    ];

外观(可选)

如果你想使用外观,将其添加到应用配置中的别名数组。

'aliases' => [
    'Facebook' => SammyK\LaravelFacebook\FacebookFacade::class,
    ];

但使用此包有 更好的方式,即 不要使用外观

IoC 容器

IoC 容器会自动为您解析 LaravelFacebookSdk 依赖。您可以通过多种方式从 IoC 容器中获取 LaravelFacebookSdk 实例。

// Directly from App::make();
$fb = App::make('SammyK\LaravelFacebookSdk\LaravelFacebookSdk');

// From a constructor
class FooClass {
    public function __construct(SammyK\LaravelFacebookSdk\LaravelFacebookSdk $fb) {
       // . . .
    }
}

// From a method
class BarClass {
    public function barMethod(SammyK\LaravelFacebookSdk\LaravelFacebookSdk $fb) {
       // . . .
    }
}

// Or even a closure
Route::get('/facebook/login', function(SammyK\LaravelFacebookSdk\LaravelFacebookSdk $fb) {
    // . . .
});

配置文件

在 Facebook 中 创建应用 后,您需要提供应用 ID 和密钥。首先发布配置文件。

$ php artisan vendor:publish --provider="SammyK\LaravelFacebookSdk\LaravelFacebookSdkServiceProvider" --tag="config"

文件在哪里? Laravel 5 将配置文件发布到 config/laravel-facebook-sdk.php

必需的配置值

您需要将配置文件中的 app_idapp_secret 值更新为 您的应用 ID 和密钥

默认情况下,配置文件将查找环境变量以获取您的应用 ID 和密钥。建议您使用环境变量来存储此信息,以保护您的应用密钥免受攻击者侵害。请确保更新您的 /.env 文件以包含您的应用 ID 和密钥。

FACEBOOK_APP_ID=1234567890
FACEBOOK_APP_SECRET=SomeFooAppSecret

同步图节点与 Laravel 模型

如果您在用户的表中有一个 facebook_user_id 列,您可以将 SyncableGraphNodeTrait 添加到您的 User 模型中,以便图 API 的用户节点自动与您的模型同步。

class User extends Eloquent implements UserInterface {
    use SammyK\LaravelFacebookSdk\SyncableGraphNodeTrait;

    protected static $graph_node_field_aliases = [
        'id' => 'facebook_user_id',
    ];
}

有关 从 Facebook 保存数据到数据库 的更多信息。

通过重定向进行用户登录示例

这是一个完整的示例,说明您如何使用 重定向方法 将用户登录到您的应用。

此示例还演示了如何 将短期访问令牌交换为长期访问令牌,如果条目不存在,则将其保存到您的 users 表中。

最后,它将使用 Laravel 内置的用户身份验证将用户登录。

// Generate a login URL
Route::get('/facebook/login', function(SammyK\LaravelFacebookSdk\LaravelFacebookSdk $fb)
{
    // Send an array of permissions to request
    $login_url = $fb->getLoginUrl(['email']);

    // Obviously you'd do this in blade :)
    echo '<a href="' . $login_url . '">Login with Facebook</a>';
});

// Endpoint that is redirected to after an authentication attempt
Route::get('/facebook/callback', function(SammyK\LaravelFacebookSdk\LaravelFacebookSdk $fb)
{
    // Obtain an access token.
    try {
        $token = $fb->getAccessTokenFromRedirect();
    } catch (Facebook\Exceptions\FacebookSDKException $e) {
        dd($e->getMessage());
    }

    // Access token will be null if the user denied the request
    // or if someone just hit this URL outside of the OAuth flow.
    if (! $token) {
        // Get the redirect helper
        $helper = $fb->getRedirectLoginHelper();

        if (! $helper->getError()) {
            abort(403, 'Unauthorized action.');
        }

        // User denied the request
        dd(
            $helper->getError(),
            $helper->getErrorCode(),
            $helper->getErrorReason(),
            $helper->getErrorDescription()
        );
    }

    if (! $token->isLongLived()) {
        // OAuth 2.0 client handler
        $oauth_client = $fb->getOAuth2Client();

        // Extend the access token.
        try {
            $token = $oauth_client->getLongLivedAccessToken($token);
        } catch (Facebook\Exceptions\FacebookSDKException $e) {
            dd($e->getMessage());
        }
    }

    $fb->setDefaultAccessToken($token);

    // Save for later
    Session::put('fb_user_access_token', (string) $token);

    // Get basic info on the user from Facebook.
    try {
        $response = $fb->get('/me?fields=id,name,email');
    } catch (Facebook\Exceptions\FacebookSDKException $e) {
        dd($e->getMessage());
    }

    // Convert the response to a `Facebook/GraphNodes/GraphUser` collection
    $facebook_user = $response->getGraphUser();

    // Create the user if it does not exist or update the existing entry.
    // This will only work if you've added the SyncableGraphNodeTrait to your User model.
    $user = App\User::createOrUpdateGraphNode($facebook_user);

    // Log the user into Laravel
    Auth::login($user);

    return redirect('/')->with('message', 'Successfully logged in with Facebook');
});

Facebook 登录

当我们说“使用 Facebook 登录”时,我们的真正意思是“获取一个用户访问令牌,以便代表用户调用图 API。”这是通过 Facebook 通过 OAuth 2.0 实现的。有几种方法可以使用 Facebook PHP SDK 所称的 "辅助程序" 来使用 Facebook 进行用户登录。

支持的四种登录方法如下

  1. 通过重定向进行登录(OAuth 2.0)
  2. 通过 JavaScript 进行登录(使用 JS SDK cookie)
  3. 通过应用画布进行登录(使用签名请求)
  4. 通过页面选项卡进行登录(使用签名请求)

从重定向登录

将用户登录到您的应用的最常见方式之一是使用重定向URL。

想法是您生成一个唯一的URL,用户点击它。一旦用户点击链接,他们将被重定向到Facebook,要求他们授予您的应用请求的任何权限。一旦用户响应,Facebook将把用户重定向回您指定的回调URL,并带有成功响应或错误响应。

可以使用SDK的getRedirectLoginHelper()方法获得重定向助手。

生成登录URL

您可以像使用Facebook PHP SDK v5一样获取登录URL。

Route::get('/facebook/login', function(SammyK\LaravelFacebookSdk\LaravelFacebookSdk $fb) {
    $login_link = $fb
            ->getRedirectLoginHelper()
            ->getLoginUrl('https://exmaple.com/facebook/callback', ['email', 'user_events']);

    echo '<a href="' . $login_link . '">Log in with Facebook</a>';
});

但是,如果您在配置文件中设置了default_redirect_uri回调URL,则可以使用getLoginUrl()包装方法,该方法将默认回调URL(default_redirect_uri)和权限范围(default_scope)设置为配置文件中设置的值。

$login_link = $fb->getLoginUrl();

或者,您可以将权限和自定义回调URL传递给包装器以覆盖默认配置。

注意:由于权限列表有时会更改,但回调URL通常保持不变,因此权限数组是getLoginUrl()包装方法中的第一个参数,这与SDK的getRedirectLoginHelper()->getLoginUrl($url, $permissions)方法相反。

$login_link = $fb->getLoginUrl(['email', 'user_status'], 'https://exmaple.com/facebook/callback');
// Or, if you want to default to the callback URL set in the config
$login_link = $fb->getLoginUrl(['email', 'user_status']);

从回调URL获取访问令牌

用户点击上面的登录链接并确认或拒绝应用权限请求后,将被重定向到指定的回调URL。

在回调URL上获取访问令牌的标准“SDK”方法如下

Route::get('/facebook/callback', function(SammyK\LaravelFacebookSdk\LaravelFacebookSdk $fb) {
    try {
        $token = $fb
            ->getRedirectLoginHelper()
            ->getAccessToken();
    } catch (Facebook\Exceptions\FacebookSDKException $e) {
        // Failed to obtain access token
        dd($e->getMessage());
    }
});

LaravelFacebookSdk中的getRedirectLoginHelper()->getAccessToken()有一个包装方法,称为getAccessTokenFromRedirect(),它默认将回调URL设置为当前URL。

Route::get('/facebook/callback', function(SammyK\LaravelFacebookSdk\LaravelFacebookSdk $fb) {
    try {
        $token = $fb->getAccessTokenFromRedirect();
    } catch (Facebook\Exceptions\FacebookSDKException $e) {
        // Failed to obtain access token
        dd($e->getMessage());
    }

    // $token will be null if the user denied the request
    if (! $token) {
        // User denied the request
    }
});

从JavaScript登录

如果您使用的是JavaScript SDK,则可以从JavaScript SDK设置的cookie中获取访问令牌。

默认情况下,JavaScript SDK不会设置cookie,因此您必须在初始化SDK时明确启用它,使用cookie: true

FB.init({
  appId      : 'your-app-id',
  cookie     : true,
  version    : 'v2.4'
});

使用FB.login()通过JavaScript SDK登录用户后,您可以从JavaScript SDK设置的cookie中获取用户访问令牌。

Route::get('/facebook/javascript', function(SammyK\LaravelFacebookSdk\LaravelFacebookSdk $fb) {
    try {
        $token = $fb->getJavaScriptHelper()->getAccessToken();
    } catch (Facebook\Exceptions\FacebookSDKException $e) {
        // Failed to obtain access token
        dd($e->getMessage());
    }

    // $token will be null if no cookie was set or no OAuth data
    // was found in the cookie's signed request data
    if (! $token) {
        // User hasn't logged in using the JS SDK yet
    }
});

从App Canvas登录

TokenMismatchException:默认情况下,您的canvas应用会在尝试在Facebook中查看时抛出TokenMismatchException。请参阅如何解决这个问题

如果您的应用位于Facebook应用canvas的上下文中,则可以从第一次页面加载时向您的应用POST的已签名请求中获取访问令牌。

注意:canvas助手仅从Facebook收到的已签名请求数据中获取现有的访问令牌。如果访问您的应用的用户尚未授权您的应用或其访问令牌已过期,则getAccessToken()方法将返回null。在这种情况下,您需要使用重定向JavaScript登录用户。

使用SDK的canvas助手从已签名请求数据中获取访问令牌。

Route::get('/facebook/canvas', function(SammyK\LaravelFacebookSdk\LaravelFacebookSdk $fb) {
    try {
        $token = $fb->getCanvasHelper()->getAccessToken();
    } catch (Facebook\Exceptions\FacebookSDKException $e) {
        // Failed to obtain access token
        dd($e->getMessage());
    }

    // $token will be null if the user hasn't authenticated your app yet
    if (! $token) {
        // . . .
    }
});

从页面Tab登录

TokenMismatchException:默认情况下,您的页面Tab在尝试在Facebook中查看时将抛出TokenMismatchException。请参阅如何解决这个问题

如果您的应用程序存在于Facebook页面标签页的上下文中,这与应用程序画布相同,"从应用程序画布登录"方法也可以用来获取访问令牌。但是页面标签页的已签名请求中还有额外的数据。

SDK提供页面标签页辅助工具,用于从页面标签页上下文中的已签名请求数据中获取访问令牌。

Route::get('/facebook/page-tab', function(SammyK\LaravelFacebookSdk\LaravelFacebookSdk $fb) {
    try {
        $token = $fb->getPageTabHelper()->getAccessToken();
    } catch (Facebook\Exceptions\FacebookSDKException $e) {
        // Failed to obtain access token
        dd($e->getMessage());
    }

    // $token will be null if the user hasn't authenticated your app yet
    if (! $token) {
        // . . .
    }
});

其他授权请求

Facebook支持两种其他类型的授权URL - 重请求和重新认证。

重请求

重请求(或重新请求?)会再次询问用户之前拒绝的权限。使用重请求URL比仅用正常登录链接重定向用户更为重要,因为

一旦有人拒绝了一个权限,登录对话框将不会再次询问他们,除非你明确告诉对话框你正在重新请求一个被拒绝的权限。 - Facebook 文档

你可以使用 getReRequestUrl() 方法生成重请求URL。

$rerequest_link = $fb->getReRequestUrl(['email'], 'https://exmaple.com/facebook/login');
// Or, if you want to default to the callback URL set in the config
$rerequest_link = $fb->getReRequestUrl(['email']);

重新认证

重新认证通过要求用户再次输入他们的Facebook账户密码来强制用户确认他们的身份。这在在您的Web应用程序中更改或查看敏感数据之前添加另一层安全措施时很有用。

你可以使用 getReAuthenticationUrl() 方法生成重新认证URL。

$re_authentication_link = $fb->getReAuthenticationUrl(['email'], 'https://exmaple.com/facebook/login');
// Or, if you want to default to the callback URL set in the config
$re_authentication_link = $fb->getReAuthenticationUrl(['email']);
// Or without permissions
$re_authentication_link = $fb->getReAuthenticationUrl();

保存访问令牌

在大多数情况下,除非你计划在用户不浏览您的应用程序时(例如,例如凌晨3点的CRON作业)代表用户向Graph API发出请求,否则你不需要将访问令牌保存到数据库中。

获得访问令牌后,您可以将其存储在会话中,以便用于后续请求。

Session::put('facebook_access_token', (string) $token);

然后,在每个调用Graph API的脚本中,您可以从中检索令牌并将其设置为默认值。

$token = Session::get('facebook_access_token');
$fb->setDefaultAccessToken($token);

在数据库中保存从Facebook获取的数据

将Graph API接收到的数据保存到数据库有时可能是一项繁琐的任务。由于Graph API以可预测的格式返回数据,因此 SyncableGraphNodeTrait 可以使将数据保存到数据库的过程变得容易。

任何实现了 SyncableGraphNodeTrait 的Eloquent模型都将应用 createOrUpdateGraphNode() 方法。这个方法确实使直接从Facebook返回的数据创建或更新本地数据库变得容易。

use SammyK\LaravelFacebookSdk\SyncableGraphNodeTrait;

class Event extends Eloquent {
    use SyncableGraphNodeTrait;
}

例如,如果你有一个名为 Event 的Eloquent模型,以下是你可以如何从Graph API获取特定事件并将其作为新条目插入数据库或使用新信息更新现有条目的方法。

$response = $fb->get('/some-event-id?fields=id,name');
$eventNode = $response->getGraphEvent();

// A method called createOrUpdateGraphNode() on the `Event` eloquent model
// will create the event if it does not exist or it will update the existing
// record based on the ID from Facebook.
$event = Event::createOrUpdateGraphNode($eventNode);

createOrUpdateGraphNode() 将自动将返回的字段名映射到你的数据库中的列名。例如,如果你的 events 表的列名与 事件 节点的字段名不匹配,你可以 映射字段

字段映射

由于你的数据库中的列名可能与Graph节点中的字段名不匹配,因此你可以使用 $graph_node_field_aliases 静态变量映射你的 User 模型中的字段名。

数组的 keys 是Graph节点上的字段名。数组的 values 是本地数据库中的列名。

use SammyK\LaravelFacebookSdk\SyncableGraphNodeTrait;

class User extends Eloquent implements UserInterface
{
    use SyncableGraphNodeTrait;

    protected static $graph_node_field_aliases = [
        'id' => 'facebook_user_id',
        'name' => 'full_name',
        'graph_node_field_name' => 'database_column_name',
    ];
}

将用户登录到 Laravel

Laravel Facebook SDK使得使用Laravel内置的认证驱动程序登录用户变得容易。

更新用户表

为了使Facebook身份验证与Laravel内置的身份验证一起工作,您需要将Facebook用户的ID存储在您的用户表中。

当然,您还需要为用户的其他任何信息创建一个列。

如果您需要在用户不在浏览您的应用时代表用户进行请求(例如凌晨3点的cron作业),则可以将访问令牌存储在数据库中。但通常您不需要在数据库中存储访问令牌。

您需要生成一个迁移来修改您的users表并添加任何新列。

注意:请确保将<name-of-users-table>更改为您的用户表名称。

$ php artisan make:migration add_facebook_columns_to_users_table --table="<name-of-users-table>"

现在更新迁移文件以包含您想要在用户上保存的新字段。至少您需要保存Facebook用户ID。

use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class AddFacebookColumnsToUsersTable extends Migration
{
    public function up()
    {
        Schema::table('users', function(Blueprint $table)
        {
            // If the primary id in your you user table is different than the Facebook id
            // Make sure it's an unsigned() bigInteger()
            $table->bigInteger('facebook_user_id')->unsigned()->index();
            // Normally you won't need to store the access token in the database
            $table->string('access_token')->nullable();
        });
    }

    public function down()
    {
        Schema::table('users', function(Blueprint $table)
        {
            $table->dropColumn(
                'facebook_user_id',
                'access_token'
            );
        });
    }
}

不要忘记运行迁移。

$ php artisan migrate

如果您计划使用Facebook用户ID作为主键,请确保您有一个名为id的列,该列是无符号大整数且已索引。如果您将Facebook ID存储在不同的字段中,请确保该字段存在于数据库中,并确保在您的模型中将它映射到自定义id名称。

如果您正在使用Eloquent ORM并将访问令牌存储在数据库中,请确保在您的User模型中隐藏access_token字段,以防止可能的外部暴露。

不要忘记将SyncableGraphNodeTrait添加到您的用户模型中,以便您可以将模型与来自Graph API的数据同步。

# User.php
use SammyK\LaravelFacebookSdk\SyncableGraphNodeTrait;

class User extends Eloquent implements UserInterface {
    use SyncableGraphNodeTrait;

    protected $hidden = ['access_token'];
}

Laravel登录用户

用户使用Facebook登录并从Graph API获取用户ID后,您可以通过将登录用户的User模型传递给Auth::login()方法,在Laravel中登录用户。

class FacebookController {
    public function getUserInfo(SammyK\LaravelFacebookSdk\LaravelFacebookSdk $fb) {
       try {
           $response = $fb->get('/me?fields=id,name,email');
       } catch (Facebook\Exceptions\FacebookSDKException $e) {
           dd($e->getMessage());
       }

       // Convert the response to a `Facebook/GraphNodes/GraphUser` collection
       $facebook_user = $response->getGraphUser();

       // Create the user if it does not exist or update the existing entry.
       // This will only work if you've added the SyncableGraphNodeTrait to your User model.
       $user = App\User::createOrUpdateGraphNode($facebook_user);

       // Log the user into Laravel
       Auth::login($user);
    }
}

错误处理

Facebook PHP SDK会抛出Facebook\Exceptions\FacebookSDKException异常。每当Graph返回错误响应时,SDK将抛出一个扩展自Facebook\Exceptions\FacebookSDKExceptionFacebook\Exceptions\FacebookResponseException。如果抛出Facebook\Exceptions\FacebookResponseException,您可以通过调用getPrevious()方法来获取与错误相关的特定异常。

try {
    // Stuffs here
} catch (Facebook\Exceptions\FacebookResponseException $e) {
    $graphError = $e->getPrevious();
    echo 'Graph API Error: ' . $e->getMessage();
    echo ', Graph error code: ' . $graphError->getCode();
    exit;
} catch (Facebook\Exceptions\FacebookSDKException $e) {
    echo 'SDK Error: ' . $e->getMessage();
    exit;
}

LaravelFacebookSdk不会抛出任何自定义异常。

在canvas应用中获取TokenMismatchException

如果您的应用在app canvas或Page tab的上下文中提供服务,当您尝试在Facebook上查看应用时,您可能会看到一个TokenMismatchException错误。这是因为Facebook将通过发送带有signed_request参数的POST请求来渲染您的应用,而Laravel 5为每个非读请求启用了CSRF保护,因此会触发错误。

尽管可以完全禁用此功能,但这绝对不推荐,因为CSRF保护是网站上的一个重要安全功能,并且应默认在每个路由上启用。

我遵循了一篇博客文章,该文章解释了如何在Laravel 5中禁用特定路由的CSRF保护

我编辑了我的app\Http\Middleware\VerifyCsrfToken.php文件,并给它添加了一个excludedRoutes()方法。然后,我创建了一个包含我的canvas应用或page tab端点的路由数组。我的完整文件如下所示

<?php namespace App\Http\Middleware;

use Closure;
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as BaseVerifier;
use Illuminate\Session\TokenMismatchException;

class VerifyCsrfToken extends BaseVerifier
{
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     *
     * @throws TokenMismatchException
     */
    public function handle($request, Closure $next)
    {
        if ($this->isReading($request) || $this->excludedRoutes($request) || $this->tokensMatch($request)) {
            return $this->addCookieToResponse($request, $next($request));
        }

        throw new TokenMismatchException;
    }

    /**
     * Ignore CSRF on these routes.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return bool
     */
    private function excludedRoutes($request)
    {
        $routes = [
          'my-app/canvas',
          'my-app/page-tab',
          // ... insert all your canvas endpoints here
        ];

        foreach($routes as $route){
            if ($request->is($route)) {
                return true;
            }
        }

        return false;
    }
}