Facebook SDK返回一个错误:跨站点请求伪造验证失败.“state"URL的参数和会话不匹配


Facebook SDK returned an error: Cross-site request forgery validation failed. The "state" param from the URL and session do not match

我试图获得Facebook用户id使用php sdk像这样

$fb = new Facebook'Facebook([
    'app_id' => '11111111111',
    'app_secret' => '1111222211111112222',
    'default_graph_version' => 'v2.4',
]);
$helper = $fb->getRedirectLoginHelper();

$permissions = ['public_profile','email']; // Optional permissions
$loginUrl = $helper->getLoginUrl('http://MyWebSite', $permissions);
echo '<a href="' . $loginUrl . '">Log in with Facebook!</a>';

    try {
        $accessToken = $helper->getAccessToken();
        var_dump($accessToken);
    } catch (Facebook'Exceptions'FacebookResponseException $e) {
        // When Graph returns an error
        echo 'Graph returned an error: ' . $e->getMessage();
        exit;
    } catch (Facebook'Exceptions'FacebookSDKException $e) {
        // When validation fails or other local issues
        echo 'Facebook SDK returned an error: ' . $e->getMessage();
        exit;
    }
    if (!isset($accessToken)) {
        if ($helper->getError()) {
            header('HTTP/1.0 401 Unauthorized');
            echo "Error: " . $helper->getError() . "'n";
            echo "Error Code: " . $helper->getErrorCode() . "'n";
            echo "Error Reason: " . $helper->getErrorReason() . "'n";
            echo "Error Description: " . $helper->getErrorDescription() . "'n";
        } else {
            header('HTTP/1.0 400 Bad Request');
            echo 'Bad request';
        }
        exit;
    }
// Logged in
    echo '<h3>Access Token</h3>';
    var_dump($accessToken->getValue());
// The OAuth 2.0 client handler helps us manage access tokens
    $oAuth2Client = $fb->getOAuth2Client();
// Get the access token metadata from /debug_token
    $tokenMetadata = $oAuth2Client->debugToken($accessToken);
    echo '<h3>Metadata</h3>';
    var_dump($tokenMetadata);
// Validation (these will throw FacebookSDKException's when they fail)
    $tokenMetadata->validateAppId($config['11111111111']);
// If you know the user ID this access token belongs to, you can validate it here
//$tokenMetadata->validateUserId('123');
    $tokenMetadata->validateExpiration();
    if (!$accessToken->isLongLived()) {
        // Exchanges a short-lived access token for a long-lived one
        try {
            $accessToken = $oAuth2Client->getLongLivedAccessToken($accessToken);
        } catch (Facebook'Exceptions'FacebookSDKException $e) {
            echo "<p>Error getting long-lived access token: " . $helper->getMessage() . "</p>'n'n";
            exit;
        }
        echo '<h3>Long-lived</h3>';
        var_dump($accessToken->getValue());
    }
    $_SESSION['fb_access_token'] = (string)$accessToken;

但是它给了我这个错误:

Facebook SDK returned an error: 
Cross-site request forgery validation failed. 
The "state" param from the URL and session do not match.

请任何帮助,我是新的php和Facebook sdk的感谢任何帮助提前。

我发现只要在生成登录url之前启用PHP会话,并且在Facebook最终重定向到的脚本的顶部,它就可以正常工作而无需设置cookie(根据ale500的答案)。这是使用5.1版本的sdk。

在两个脚本的顶部,我添加了…

if(!session_id()) {
    session_start();
}

下面是一个适用于我的基本完整示例:

auth.php

if (!session_id()) {
    session_start();
}
$oFB = new Facebook'Facebook([
    'app_id'     => FACEBOOK_APP_ID,
    'app_secret' => FACEBOOK_APP_SECRET
]);
$oHelper = self::$oFB->getRedirectLoginHelper();
$sURL = $oHelper->getLoginUrl(FACEBOOK_AUTH_CALLBACK, FACEBOOK_PERMISSIONS);
// Redirect or show link to user.

auth_callback.php

if (!session_id()) {
    session_start();
}
$oFB = new Facebook'Facebook([
    'app_id'     => FACEBOOK_APP_ID,
    'app_secret' => FACEBOOK_APP_SECRET
]);
$oHelper = self::$oFB->getRedirectLoginHelper();
$oAccessToken = $oHelper->getAccessToken();
if ($oAccessToken !== null) {
    $oResponse = self::$oFB->get('/me?fields=id,name,email', $oAccessToken);
    print_r($oResponse->getGraphUser());
}

为什么?

作为一个额外的注意事项,这在文档中有解释。请看这一页的警告。

警告:FacebookRedirectLoginHelper使用会话来存储CSRF值。在调用getLoginUrl()方法之前,需要确保启用了会话。这通常在大多数web框架中自动完成,但如果你不使用web框架,你可以添加session_start();放到login。php &login-callback.php脚本。您可以覆盖默认的会话处理-参见下面的扩展点。

我添加这个说明,因为它是重要的要记住,如果你碰巧正在运行自己的会话管理或如果你正在运行多个web服务器并行。在这些情况下,依赖php的默认会话方法并不总是有效。

将此代码插入$helper = $fb->getRedirectLoginHelper();

  $_SESSION['FBRLH_state']=$_GET['state'];

已经提到了很多很棒的答案,这里是对我有帮助的一个,

我发现问题是跨站点请求伪造验证失败。所需参数"状态"缺失在FB代码,这是解决方案

这行之后

$helper = $fb->getRedirectLoginHelper();

添加以下代码,

if (isset($_GET['state'])) {
    $helper->getPersistentDataHandler()->set('state', $_GET['state']);
}

我在Symfony2中使用Facebook SDK时得到了这个错误,编写了一个Twig扩展来显示模板中API的数据。

我的解决方案是将'persistent_data_handler'=>'session'添加到Facebook对象配置中,这导致状态数据存储在会话密钥中而不是内存中:

$fb = new Facebook'Facebook([
    'app_id' => 'APP_ID',
    'app_secret' => 'APP_SECRET',
    'default_graph_version' => 'v2.4',
    'persistent_data_handler'=>'session'
]);

默认情况下,它使用内置内存处理程序,这对我来说不能正常工作。也许是因为有些函数是从Twig扩展中调用的,因为内存处理程序在正常控制器/服务中专门使用SDK时确实可以工作。

显然,状态是在您调用getLoginUrl()时设置的,并且在您调用getAccessToken()时检索。如果保存的状态返回null(因为您的数据处理程序没有达到应有的持久性),则CSRF验证检查失败。

如果你需要以一种特殊的方式处理会话,或者你想在其他地方存储状态,你也可以用'persistent_data_handler' => new MyPersistentDataHandler()编写你自己的处理程序,使用FacebookSessionPersistentDataHandler作为一个例子。

当Facebook库无法将它从Facebook接收到的状态参数与它在会话中默认设置的状态参数匹配时,就会发生这种情况。如果你正在使用一个框架,如Laravel, Yii2,或Kohana实现自己的会话存储,标准的Facebook会话实现可能无法工作。

要解决这个问题,你需要使用框架的会话库创建自己的PersistentDataInterface实现,并将其传递给Facebook'Facebook构造函数。

下面是一个来自Facebook的Laravel持久化处理程序的例子:

use Facebook'PersistentData'PersistentDataInterface;
class MyLaravelPersistentDataHandler implements PersistentDataInterface
{
  /**
   * @var string Prefix to use for session variables.
   */
  protected $sessionPrefix = 'FBRLH_';
  /**
   * @inheritdoc
   */
  public function get($key)
  {
    return 'Session::get($this->sessionPrefix . $key);
  }
  /**
   * @inheritdoc
   */
  public function set($key, $value)
  {
    'Session::put($this->sessionPrefix . $key, $value);
  }
}

构造函数参数示例:

$fb = new Facebook'Facebook([
  // . . .
  'persistent_data_handler' => new MyLaravelPersistentDataHandler(),
  // . . .
 ]);

更多信息在这里:https://developers.facebook.com/docs/php/PersistentDataInterface/5.0.0

最后,查看FB代码,我发现问题

跨站点请求伪造验证失败。缺少必需参数" state "

和类似的是由PHP变量$_SESSION['FBRLH_state']引起的,当FB调用登录回调文件时,由于一些"奇怪"的原因。

为了解决这个问题,我将这个变量"FBRLH_state"存储在函数$helper->getLoginUrl(...)调用之后。这是非常重要的,因为只有在调用这个函数之后,当变量$_SESSION['FBRLH_state']被填充时,它在这个函数中。

下面是我在login.php中的代码示例:
$uri=$helper->getLoginUrl($uri, $permissions);
foreach ($_SESSION as $k=>$v) {                    
    if(strpos($k, "FBRLH_")!==FALSE) {
        if(!setcookie($k, $v)) {
            //what??
        } else {
            $_COOKIE[$k]=$v;
        }
    }
}
var_dump($_COOKIE);

和在login-callback.php中调用所有FB代码之前:

foreach ($_COOKIE as $k=>$v) {
    if(strpos($k, "FBRLH_")!==FALSE) {
        $_SESSION[$k]=$v;
    }
}

最后,但并非最不重要的是,还记得包括PHP会话的代码,所以..

if(!session_id()) {
    session_start();
}
...
...
...
...
<?php session_write_close() ?>

我希望这个回复可以帮助你节省8-10个小时的工作:)再见,亚历克斯。

对于我来说,问题是我没有在脚本之前运行会话。

所以,我在实例化Facebook类之前添加了session_start();

Facebook对象有一个名为persistentDataHandler的实例变量,通常是FacebookSessionPersistentDataHandler的一个实例,它有一个set和一个get方法来访问本地PHP会话。

生成回调url时使用:

$loginUrl = $helper->getLoginUrl($callback_url, $permissions);

方法FacebookRedirectLoginHelper->getLoginUrl()将创建一个32个字符的随机字符串,将其添加到$loginUrl中,并使用persistentDataHandler的set方法将其保存到下面的代码中。

$_SESSION['FBRLH_' . 'state']

以后当调用$helper->getAccessToken();时,url中的状态参数将与存储在同一会话中的状态参数进行比较,以防止CSRF。如果不匹配,则抛出异常。

FacebookSDKException:跨站请求伪造验证失败。缺少必需参数"state"。

说了这么多,您需要确保在此过程中正确设置了本机PHP会话特性。您可以通过添加

进行检查。
die($_SESSION['FBRLH_' . 'state']);

$loginUrl = $helper->getLoginUrl($callback_url, $permissions);

之前
$accessToken = $helper->getAccessToken();

查看那个32个字符的字符串是否在那里。

希望有帮助!

对于Symfony,由于会话管理的方式,它不起作用。

为了解决这个问题,你可以创建一个新的处理程序来处理symfony的会话。

FacebookDataHandlerSymfony.php:

<?php
use Facebook'PersistentData'PersistentDataInterface;
use Symfony'Component'HttpFoundation'Session'Session;
class FacebookDataHandlerSymfony implements PersistentDataInterface
{
    private $session;
    public function __construct()
    {
        $this->session = new Session();
    }
    public function get($key)
    {
        return $this->session->get('FBRLH_' . $key);
    }
    public function set($key, $value)
    {
        $this->session->set('FBRLH_' . $key, $value);
    }
}

当你创建FB对象时,你只需要指定新的类:

$this->fb = new Facebook([
            'app_id' => '1234',
            'app_secret' => '1324',
            'default_graph_version' => 'v2.8',
            'persistent_data_handler' => new FacebookDataHandlerSymfony()
        ]);

我在laravel 5.4上遇到了同样的问题,我通过将

session_start();

在脚本的顶部

下面是示例laravel控制器命名空间,以向您展示它是如何工作的。

<?php
namespace App'Http'Controllers;
session_start();
use Facebook'Facebook as Facebook;
?>

这个问题发生是因为它还没有开始,所以通过在脚本的顶部添加会话开始,我们只是开始会话。

希望它可以帮助某人…

如果您的初始主机名与经过身份验证的目标主机名不同,则会收到此错误。

$loginUrl = $helper->getLoginUrl('http://MyWebSite', $permissions);

使用此语句,如果您网站上的访问者使用http://www.mywebsite.com/,则会引发跨站点错误。

您必须确保源主机名和目标主机名完全相同,包括最终的www前缀。

固定版本:

$loginUrl = $helper->getLoginUrl('http://'.$_SERVER['SERVER_NAME'], $permissions);

这可能有点晚了,但我希望它能帮助别人,因为这个问题仍然存在。

我有这个问题一段时间了,我已经搜索了周围,看到了很多不同的解决方案,其中许多禁用CSRF检查。所以,在我读了这么多之后,这就是对我有用的。

对于我的理解,你得到这个错误,当你的重定向URL不匹配你已经设置在你的应用程序设置,所以我的问题很容易修复,但我也看到人们有问题,没有他们的会话正常启动,所以我将涵盖这两个问题。

步骤1:确保会话在需要时已经启动。

例如:fb-config.php

session_start();
include_once 'path/to/Facebook/autoload.php';
$fb = new 'Facebook'Facebook([
    'app_id' => 'your_app_id',
    'app_secret' => 'your_secret_app_id',
    'default_graph_version' => 'v2.10'
]);
$helper = $fb->getRedirectLoginHelper();

如果你的facebook回调代码在配置文件之外的另一个文件上,那么也在该文件上启动会话。

例如:fb-callback.php

session_start();
include_once 'path/to/fb-config.php';
try {
    $accessToken = $helper->getAccessToken();
} catch ('Facebook'Exceptions'FacebookResponseException $e) {
    echo "Response Exception: " . $e->getMessage();
    exit();
} catch ('Facebook'Exceptions'FacebookSDKException $e) {
    echo "SDK Exception: " . $e->getMessage();
    exit();
}
/** THE REST OF YOUR CALLBACK CODE **/

现在,我的实际问题解决了。

步骤3:在你的应用设置中设置你的重定向URL

在您的Facebook登录应用程序设置中,转到Valid OAuth redirect uri ,您应该在其中添加指向fb-callback.php文件的url。

http://example.com/fb-callback.php
AND ALSO
http://www.example.com/fb-callback.php

然后设置你的重定向url如下。

$redirectURL = "http://".$_SERVER['SERVER_NAME']."/fb-callback.php";
$permissions = ['email'];
$fLoginURL = $helper->getLoginUrl($redirectURL, $permissions);

为什么有和没有www,为什么使用SERVER_NAME?

因为你的有效的OAuth重定向URI需要匹配你的代码中的重定向url,如果在你的应用程序设置中,你只设置你的OAuth重定向为http://example.com/fb-callback.php,并设置你的$redirectURL为http://example.com/fb-bacllback.php,使其匹配,但用户输入你的网站为http://www.example.com,那么用户将得到Facebook SDK错误:跨站请求伪造验证失败。持久性数据中缺少必需的参数"state",因为用户所在的URL与您设置的不完全匹配。为什么?我完全不知道。

我的方法使得它,如果用户输入您的网站为http://example.com或http://www.example.com,它将始终匹配你在你的应用程序设置设置。为什么?因为$_SERVER['SERVER_NAME']将返回带或不带www的域名,这取决于用户在浏览器中输入url的方式。

这是我的发现,这是唯一对我有用的东西,没有删除CSRF检查,到目前为止,没有问题。

我希望这对你有帮助。

你可以这样做用新的状态设置会话

<?php
if(isset($_GET['state'])) {
      if($_SESSION['FBRLH_' . 'state']) {
          $_SESSION['FBRLH_' . 'state'] = $_GET['state'];
      }
}
?>

在我的情况下,我检查了错误,发现错误,导致我执行代码解决方案:

date_default_timezone_set('Europe/Istanbul');

前脚本。效果很好。对于您的位置检查:timezones.europe.php

对我来说很容易修复。我改变了:

$loginUrl = $helper->getLoginUrl('http://www.MYWEBSITE.ca/index_callback.php', $permissions);

:

$loginUrl = $helper->getLoginUrl('http://MYWEBSITE.ca/index_callback.php', $permissions);

删除'www'解决了这个问题。

如果源主机名与目标主机名不同,则触发错误[如Souch所述]。当访问者在URL地址栏中输入"http://website.com"与"http://www.website.com"不同时。我们将它们重定向到正确的URL,方法是在session_start()之前的最上面位置添加以下代码。

  if($_SERVER['HTTP_HOST']!=='www.website.com'){
  header('location: http://www.website.com');
  }

Yii2适合我的解决方案:

use Facebook'PersistentData'PersistentDataInterface;
use Yii;
class PersistentDataHandler implements PersistentDataInterface
{
    /**
     * @var string Prefix to use for session variables.
     */
    protected $sessionPrefix = 'FBRLH_';
    public function get($key)
    {
        return Yii::$app->session->get($this->sessionPrefix . $key);
    }
    public function set($key, $value)
    {
        Yii::$app->session->set($this->sessionPrefix . $key, $value);
    }
}

我有同样的错误,因为我忘记在发件人地址中添加"www."。在Client-OAuth设置中,必须有正确的名称。

这是许多人在FB Api中面临的一个常见问题。这只是一个SESSION问题。要解决这个问题,可以添加一些代码,如。

在回调脚本通常fb-callback.php添加"session_start();"就在你包含facebook自动加载文件之前。然后在"$helper =$ fb->getRedirectLoginHelper();"行之后输入"$_SESSION['FBRLH_state']=$_GET['state'];"。

示例:

<?php 
session_start();
include 'vendor/autoload.php';
include 'config.php'; /*Facebook Config*/
$helper = $fb->getRedirectLoginHelper();
$_SESSION['FBRLH_state']=$_GET['state'];
try {
  $accessToken = $helper->getAccessToken();
} ?>

可能会帮助某人,谁正在使用Javascript Helper在前端进行用户身份验证,并且在PHP中试图从重定向登录Helper提取access_token。所以使用

getJavaScriptHelper();
不是

getRedirectLoginHelper();

间歇性问题解决方案

我是a)重定向到Facebook登录链接,b)从login.php重定向到main.php。用户将转到main.php和其他一些页面,然后在浏览器中单击返回。

最终,他们会点击login.php,并发布一堆信用,但Facebook在一次成功后删除了$_SESSION['FBRLH_state'],所以即使它有正确的$_GET['state'],它也会出错。

解决方案是a)内部跟踪用户是否登录并避免在login.php中重复Facebook逻辑,或者b)跟踪所有最近有效的状态参数,这些参数是由Facebook设置的特定用户(可能在会话中),如果$_GET['state']在该数组中,然后这样做:

$_SESSION['FBRLH_state'] = $_GET['state'];在这种情况下,您可以安全地执行此操作,而不会破坏CSRF保护。

For me设置会话状态有效

完整的代码(在重定向url php)

$accessToken = '';
$helper = $fb->getRedirectLoginHelper();
if(isset($_GET['state'])){
    $_SESSION['FBRLH_state']=$_GET['state'];
}

try {
    $accessToken = $helper->getAccessToken();
} catch ( Facebook'Exceptions'FacebookResponseException $e ) {
    // When Graph returns an error
    echo 'Graph returned an error: ' . $e->getMessage();
}

我正在玩Symfony,并有这个问题一次尝试是,下一个没有。我通过将facebookRedirectLoginHelper对象存储到会话中,并稍后从会话中检索它来解决这个问题,而不是再次向Facebook对象请求它。

文档(撰写本文时Symfony 4.3)声明如下:

在使用session类之前,请确保PHP会话尚未启动。如果您有一个启动会话的遗留会话系统,请参阅遗留会话。

Symfony会话被设计用来替换几个本地PHP函数。应用程序应避免使用session_start()、session_regenerate_id()、session_id()、session_name()和session_destroy(),而应使用下一节中的api。

虽然建议显式地启动会话,但会话实际上是根据需要启动的,也就是说,如果有任何会话请求来读取/写入会话数据。

所以我认为从会话中检索对象本身就会启动php会话

如果你正在使用php框架,请记住这一点。

对我来说,问题是不同的;(我太傻了)我有一个弹出窗口与Facebook登录和再次Facebook登录按钮在登录/注册页面。

    每当我从其他页面点击弹出的Facebook按钮时,它工作得很好。
  • 但是,当我点击Facebook按钮弹出从登录/注册页面,它不会工作。

原因是我重新实例化了Facebook和getRedirectLoginHelper对象。我不得不在登录/注册页面中注释掉这些语句,因为它已经可用了。

$_SESSION['FBRLH_state']=$_GET['state'];

不是正确的方法。尽管它有效。

如果一直看到错误,请清理浏览器缓存。当我使用enbrev解决方案修复了这个问题后,我仍然会遇到这个错误。过了一会儿,我明白了我需要清理缓存,在我重新启动浏览器后,它工作了!