ThePHPLeague OAuth2 Client `getAccessToken()` 抛出 "An OAuth server error was encountered that did not contain a JSON body" 错误

ThePHPLeague OAuth2 Client `getAccessToken()` throws "An OAuth server error was encountered that did not contain a JSON body" error

我一直在尝试开发一个 API 客户端,它们通过实施 ThePHPLeague 的 OAuth2 server and client 相互通信。在 CLI 中使用 curl 命令,我能够生成令牌并使用它来访问受保护的资源。

用户身份验证依赖于带有 Slim 框架的 bespoke PHP solution,它接受存储在数据库 table 中的用户名和加密密码。同样的 table 用于 OAuth2 实现的用户管理。

当用户登录尝试成功验证后,将调用 AbstractProvidergetAccessToken() 方法并从 API 请求访问令牌。问题就出在这里。

我已经使用 GenericProvider class 测试了功能。我还扩展了提供程序以创建我自己的 class。使用这两个提供商,我在尝试登录时看到以下错误:

Slim Application Error
Type: UnexpectedValueException
Code: 0
Message: An OAuth server error was encountered that did not contain a JSON body
File: /var/www/sloth2-client-php/vendor/league/oauth2-client/src/Provider/AbstractProvider.php
Line: 693

#0 /.../vendor/league/oauth2-client/src/Provider/AbstractProvider.php(626): 
League\OAuth2\Client\Provider\AbstractProvider->parseResponse(Object(GuzzleHttp\Psr7\Response))
#1 /.../src/SlothProvider.php(113): League\OAuth2\Client\Provider\AbstractProvider->getParsedResponse(Object(GuzzleHttp\Psr7\Request))
#2 /.../src/Controller/AuthenticationController.php(69): App\SlothProvider->getAccessToken(Object(League\OAuth2\Client\Grant\ClientCredentials))
#3 /.../vendor/slim/slim/Slim/Handlers/Strategies/RequestResponse.php(42): App\Controller\AuthenticationController->authenticate(Object(Slim\Psr7\Request), Object(Slim\Psr7\Response), Array)
#4 /.../vendor/slim/slim/Slim/Routing/Route.php(372): Slim\Handlers\Strategies\RequestResponse->__invoke(Array, Object(Slim\Psr7\Request), Object(Slim\Psr7\Response), Array)
#5 /.../vendor/slim/slim/Slim/MiddlewareDispatcher.php(73): Slim\Routing\Route->handle(Object(Slim\Psr7\Request))
#6 /.../vendor/slim/slim/Slim/MiddlewareDispatcher.php(73): Slim\MiddlewareDispatcher->handle(Object(Slim\Psr7\Request))
#7 /.../vendor/slim/slim/Slim/Routing/Route.php(333): Slim\MiddlewareDispatcher->handle(Object(Slim\Psr7\Request))
#8 /.../vendor/slim/slim/Slim/Routing/RouteRunner.php(65): Slim\Routing\Route->run(Object(Slim\Psr7\Request))
#9 /.../vendor/slim/slim/Slim/Middleware/RoutingMiddleware.php(58): Slim\Routing\RouteRunner->handle(Object(Slim\Psr7\Request))
#10 /.../vendor/slim/slim/Slim/MiddlewareDispatcher.php(132): Slim\Middleware\RoutingMiddleware->process(Object(Slim\Psr7\Request), Object(Slim\Routing\RouteRunner))
#11 /.../vendor/slim/slim/Slim/Middleware/ErrorMiddleware.php(89): class@anonymous->handle(Object(Slim\Psr7\Request))
#12 /.../vendor/slim/slim/Slim/MiddlewareDispatcher.php(132): Slim\Middleware\ErrorMiddleware->process(Object(Slim\Psr7\Request), Object(class@anonymous))
#13 /.../vendor/slim/slim/Slim/MiddlewareDispatcher.php(73): class@anonymous->handle(Object(Slim\Psr7\Request))
#14 /.../vendor/slim/slim/Slim/App.php(206): Slim\MiddlewareDispatcher->handle(Object(Slim\Psr7\Request))
#15 /.../vendor/slim/slim/Slim/App.php(190): Slim\App->handle(Object(Slim\Psr7\Request))
#16 /.../public/index.php(8): Slim\App->run()
#17 {main}

堆栈跟踪中提到的SlothProvider class如下:

<?php

namespace App;

use League\OAuth2\Client\Provider\AbstractProvider;
use League\OAuth2\Client\Token\AccessToken;
use League\OAuth2\Client\Tool\BearerAuthorizationTrait;
use Psr\Http\Message\ResponseInterface;
use UnexpectedValueException;

class SlothProvider extends AbstractProvider
{
  use BearerAuthorizationTrait;

  public function __construct()
  {
    $this->clientId = getenv('OAUTH2_CLIENT_ID');
    $this->clientSecret = getenv('OAUTH2_CLIENT_SECRET');
    $this->redirectUri = getenv('OAUTH2_REDIRECT_URI');
  }

  /**
   * Get authorization url to begin OAuth flow
   *
   * @return string
   */
  public function getBaseAuthorizationUrl()
  {
    return getenv('OAUTH2_AUTHORIZATION_URL');
  }

  /**
   * Get access token url to retrieve token
   *
   * @param  array $params
   *
   * @return string
   */
  public function getBaseAccessTokenUrl(array $params)
  {
    return getenv('OAUTH2_ACCESS_TOKEN_URL');
  }

  /**
   * Get provider url to fetch user details
   *
   * @param  AccessToken $token
   *
   * @return string
   */
  public function getResourceOwnerDetailsUrl(AccessToken $token)
  {
    // You don't have one. You might consider throwing an exception here so
    // that, when this is called, you get an error and can code your
    // application to ensure that nothing calls this.
    //
    // Note that $this->getResourceOwner() is the most likely culprit for
    // calling this. Just don't call getResourceOwner() in your code.
  }

  /**
   * Get the default scopes used by this provider.
   *
   * This should not be a complete list of all scopes, but the minimum
   * required for the provider user interface!
   *
   * @return array
   */
  protected function getDefaultScopes()
  {
    return ['basic'];
  }

  /**
   * Check a provider response for errors.
   *
   * @throws IdentityProviderException
   * @param  ResponseInterface $response
   * @param  array $data Parsed response data
   * @return void
   */
  protected function checkResponse(ResponseInterface $response, $data)
  {
    // Write code here that checks the response for errors and throws
    // an exception if you find any.
  }

  /**
   * Generate a user object from a successful user details request.
   *
   * @param array $response
   * @param AccessToken $token
   * @return \League\OAuth2\Client\Provider\ResourceOwnerInterface
   */
  protected function createResourceOwner(array $response, AccessToken $token)
  {
    // Leave empty. You can't use this, since you don't have a clear
    // resource owner details URL. You might consider throwing an
    // exception from here, as well. See note on
    // getResourceOwnerDetailsUrl() above.
  }

  /**
   * Requests an access token using a specified grant and option set.
   *
   * @param  mixed $grant
   * @param  array $options
   * @throws IdentityProviderException
   * @return AccessTokenInterface
   */
  public function getAccessToken($grant, array $options = [])
  {
    $grant = $this->verifyGrant($grant);
    $params = [
      'client_id'     => $this->clientId,
      'client_secret' => $this->clientSecret,
      'redirect_uri'  => $this->redirectUri,
    ];
    $params   = $grant->prepareRequestParameters($params, $options);
    $request  = $this->getAccessTokenRequest($params);
    $response = $this->getParsedResponse($request);
    if (false === is_array($response)) {
      throw new UnexpectedValueException(
        'Invalid response received from Authorization Server. Expected JSON.'
      );
    }
    $prepared = $this->prepareAccessTokenResponse($response);
    $token    = $this->createAccessToken($prepared, $grant);
    return $token;
  }
}

我想知道这个错误是什么意思以及如何解决。

服务器收到 500 状态代码的响应,其中正文无法被 json_decode() 解码。来自 json_last_error_msg() 的实际解码消息可以在 UnexpectedValueException 的前一个异常的 `getMessage().

中找到

要找出它是什么,请尝试从 $response = $this->getParsedResponse($request); 捕获异常,然后抛出之前的异常。例如

    try {
        $response = $this->getParsedResponse($request);
    } catch (UnexpectedValueException $e) {
        if ($e->getPrevious()) {
            // json_decode() error message is in $e->getPrevious()->getMessage().
            // An easy way to see it is to throw the previous exception:
            throw $e->getPrevious();
        }
    }

希望错误消息能为您提供有关问题所在的线索。否则,您需要查看 sent/received 的 Request 和 Response 对象。您需要检查 League\OAuth2\Client\Provider\AbstractProvider 中的 getParsedResponse() 方法才能做到这一点。