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 实现的用户管理。
当用户登录尝试成功验证后,将调用 AbstractProvider 的 getAccessToken()
方法并从 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()
方法才能做到这一点。
我一直在尝试开发一个 API 客户端,它们通过实施 ThePHPLeague 的 OAuth2 server and client 相互通信。在 CLI 中使用 curl
命令,我能够生成令牌并使用它来访问受保护的资源。
用户身份验证依赖于带有 Slim 框架的 bespoke PHP solution,它接受存储在数据库 table 中的用户名和加密密码。同样的 table 用于 OAuth2 实现的用户管理。
当用户登录尝试成功验证后,将调用 AbstractProvider 的 getAccessToken()
方法并从 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()
方法才能做到这一点。