OAuth - Error on Twitter (could not authenticate)

2019-07-19 01:01发布

问题:

I have written a class (so I could learn how OAuth works). It is working fine; I can retrieve the access token with the class. But when I try to post an update, it says that I'm not authenticated! What am I doing wrong here?

// By Kevin Jacobs
class OAuth {

    private $url = null;
    private $debug = false;
    private $method = 'POST';
    private $oauthData = array();
    private $data = array();
    private $token = array('key' => '', 'secret' => '');
    private $consumer = array('key' => '', 'secret' => '');

    /**
     * Encode a string in such a way you can use it for OAuth.
     * @param string $oauthData Data to encode
     * @return string $encData Encoded data
     */
    public static function encode($oauthData) {
        if (is_string($oauthData)) {
            return str_ireplace(
                array('+', '%7E'),
                array(' ', '~'),
                rawurlencode($oauthData)
            );
        } else {
            return '';
        }
    }

    /**
     * Generates a relative unique random string of a certain length.
     * @param int $length Length of the string
     * @return string $strRand A random string
     */
    public static function generateString($length = 40) {
        // Only strong cryptographic strings are allowed
        while (!isset($bStrong) || $bStrong === false) {
            $bytes = openssl_random_pseudo_bytes(floor($length / 2), $bStrong);
        }
        $strRand = bin2hex($bytes);
        return sha1($strRand);
    }

    /**
     * Generate a token pair (key and secret).
     * @return array $tokenPair
     */
    public static function generateTokenPair() {
        $tokenPair = array();
        $tokenPair['key'] = self::generateString();
        $tokenPair['secret'] = self::generateString();
        return $tokenPair;
    }

    /**
     * Set the callback URL.
     * @param string $callbackURL
     */
    public function setCallback($callback = null) {
        if ($callback === null) {
            $callback = 'http';
            if ($_SERVER['SERVER_PORT'] == 443) $callback .= 's';
            $callback .= '://' . $_SERVER['SERVER_NAME'];
            if ($_SERVER['SERVER_PORT'] != 80 && $_SERVER['SERVER_PORT'] != 443) {
                $callback .= ':' . $_SERVER['SERVER_PORT'];
            }
            $callback .= $_SERVER['REQUEST_URI'];
        }
        $this->oauthData['oauth_callback'] = $callback;
    }

    /**
     * Get the callback URL.
     * @return string $callbackURL
     */
    public function getCallback() {
        return $this->oauthData['oauth_callback'];
    }

    /**
     * Generate the nonce.
     * @return string $nonce
     */
    public function setNonce() {
        $this->oauthData['oauth_nonce'] = md5(self::generateString(20) . mktime());
        return $this->oauthData['oauth_nonce'];
    }

    /**
     * Set the timestamp.
     * @return int $timestamp
     */
    public function setTimestamp() {
        $this->oauthData['oauth_timestamp'] = mktime();
        return $this->oauthData['oauth_timestamp'];
    }

    /**
     * Set the OAuth version.
     * @param string Version
     */
    public function setVersion($version = '1.0') {
        $this->oauthData['oauth_version'] = $version;
    }

    /**
     * Set the HTTP method.
     * @param string Method
     */
    public function setMethod($method = 'POST') {
        $this->method = trim(strtoupper($method));
    }

    /**
     * Get the HTTP method.
     * @return string Method
     */
    public function getMethod() {
        return $this->method;
    }

    /**
     * Get the URL to call.
     * @return string URL
     */
    public function getURL() {
        return $this->url;
    }

    /**
     * Set the URL to call.
     * @param string URL
     */
    public function setURL($URL) {
        $this->url = $URL;
    }

    /**
     * Get the token key and secret
     * @return array $token Containing token key and secret
     */
    public function getToken() {
        return $this->token;
    }

    /**
     * Set the token
     * @param string $tokenKey Token key
     * @param string $tokenSecret Token secret
     */
    public function setToken($tokenKey, $tokenSecret = null) {
        $this->token['key'] = $tokenKey;
        $this->token['secret'] = $tokenSecret;
        $this->oauthData['oauth_token'] = $tokenKey;
        $this->oauthData['oauth_token_secret'] = $tokenSecret;
    }

    /**
     * Get the consumer
     * @return array $consumer Containing consumer key and secret
     */
    public function getConsumer() {
        return $this->consumer;
    }

    /**
     * Set the consumer
     * @param string $consumerKey Consumer key
     * @param string $consumerSecret Consumer secret
     */
    public function setConsumer($consumerKey, $consumerSecret) {
        $this->oauthData['oauth_consumer_key'] = $consumerKey;
        $this->consumer['key'] = $consumerKey;
        $this->consumer['secret'] = $consumerSecret;
    }

    /**
     * Generate the signature.
     * @return array Signature properties
     */
    public function setSignature() {
        // Set the signature method
        $this->oauthData['oauth_signature_method'] = 'HMAC-SHA1';
        // First, sort the OAuth data
        $oauthData = $this->oauthData;
        ksort($oauthData);
        // Now combine them in a string
        $query = http_build_query($oauthData);
        // Make it URL proof
        $query = rawurlencode($query);
        // Fetch the method and URL
        $method = $this->getMethod();
        $url = $this->getURL();
        // Make the URL URL proof
        $url = rawurlencode($url);
        // Now bind everything together
        $baseString = $method . '&' . $url . '&' . $query;
        // Retrieve the key
        $consumer = $this->getConsumer();
        $token = $this->getToken();
        $key = self::encode($consumer['secret']) . '&' . self::encode($token['secret']);
        // Encrypt the base string
        $signature = hash_hmac('SHA1', $baseString, $key, true);
        // And make it URL proof using base64_encode
        $signature = base64_encode($signature);
        $this->oauthData['oauth_signature'] = $signature;
    }

    public function setVerifier($verifier) {
        $this->oauthData['oauth_verifier'] = $verifier;
    }

    public function debugOn() {
        $this->debug = true;
    }

    public function debugOff() {
        $this->debug = false;
    }

    public function setData($data) {
        $this->data = $data;
    }

    public function call($url) {
        $method = $this->getMethod();
        $this->setURL($url);
        $this->setNonce();
        $this->setTimestamp();
        $this->setSignature();
        $oauthData = $this->oauthData;
        $data = $this->data;
        $data = array_merge($data, $oauthData);

        if ($method == 'GET') {
            $url = explode('#', $url);
            $url = reset($url);
            if (strpos($url, '?') !== false) {
                $binder = '&';
            } else {
                $binder = '?';
            }
            $url .= $binder . http_build_query($oauthData);
        }
        $ch = curl_init();
        if (!empty($headers)) {
            curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
        }
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
        if ($method == 'POST') {
            curl_setopt($ch, CURLOPT_POST, true);
            curl_setopt($ch, CURLOPT_POSTFIELDS, $oauthData);
        }
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_HEADER, false);
        curl_setopt($ch, CURLOPT_FAILONERROR, false);
        curl_setopt($ch, CURLOPT_MAXREDIRS, 10);
        curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
        curl_setopt($ch, CURLOPT_ENCODING, '');
        curl_setopt($ch, CURLOPT_HTTPHEADER, array('Expect:'));
        $result = curl_exec($ch);
        curl_close($ch);
        return $result;
    }

}

$x = new OAuth();
$x->debugOn();
$x->setVersion('1.0');
$x->setConsumer('consumerToken', 'consumerSecret');
$x->setToken('accessToken', 'accessSecret');
$x->setMethod('POST');
$x->setData(array('status' => 'Hello World!'));
echo $x->call('http://api.twitter.com/1/statuses/update.json', true);

The following code is working (retrieving the access token):

$x = new OAuth();
$x->debugOn();
$x->setVersion('1.0');
$x->setConsumer('consumerToken', 'consumerSecret');
$x->setToken('accessToken', 'accessSecret');
$x->setMethod('POST');
if (isset($_GET['oauth_verifier']) && isset($_GET['oauth_token'])) {
    // Request token -> Access token
    $verifier = $_GET['oauth_verifier'];
    $token = $_GET['oauth_token'];
    $x->setVerifier($verifier);
    $x->setToken($token);
    $x->setMethod('GET');
    $result = $x->call('https://api.twitter.com/oauth/access_token', true);
    parse_str($result);
    echo 'Access token: ' . $oauth_token . '<br />';
    echo 'Access token secret: ' . $oauth_token_secret;
} else {
    // Request token
    $x->setCallback();
    $x->setMethod('GET');
    $result = $x->call('https://api.twitter.com/oauth/request_token');
    parse_str($result);
    header('Location: http://api.twitter.com/oauth/authorize?oauth_token=' . $oauth_token);
}

回答1:

I'm kind of guessing here, but since you only speak of "retrieving the access token with the class", I suspect you aren't actually following through the entire Twitter OAuth authorization flow. The initial token you get back is just your starting point toward getting the real token you'll be able to use to post updates; you have to jump through a bunch of hoops.

If I'm mistaken and you have actually gone through those hoops, never mind. :)