Laravel Passport 通过访问令牌获取客户端 ID
Laravel Passport Get Client ID By Access Token
我正在编写一个小型短信网关,供几个项目使用,
我实施了 laravel 护照身份验证 (client credentials grant token)
然后我将 CheckClientCredentials
添加到 api 中间件组:
protected $middlewareGroups = [
'web' => [
...
],
'api' => [
'throttle:60,1',
'bindings',
\Laravel\Passport\Http\Middleware\CheckClientCredentials::class
],
];
逻辑工作正常,现在在我的控制器中我需要让客户端与有效令牌相关联。
routes.php
Route::post('/sms', function(Request $request) {
// save the sms along with the client id and send it
$client_id = ''; // get the client id somehow
sendSms($request->text, $request->to, $client_id);
});
出于明显的安全原因,我永远无法将客户端 ID 与消费者请求一起发送,例如$client_id = $request->client_id;
.
所以,没有答案......
我能够通过使用自己的 API 来解决问题,最后我想出了更简单的身份验证流程,客户端需要在每个请求中发送他们的 ID 和密码,然后我使用自己的 /oauth/token
发送凭据的路由,灵感来自 Esben Petersen blog post.
生成访问令牌后,我将其附加到正在处理的 Symfony\Request
实例的 headers。
我的最终输出是这样的:
<?php
namespace App\Http\Middleware;
use Request;
use Closure;
class AddAccessTokenHeader
{
/**
* Octipus\ApiConsumer
* @var ApiConsumer
*/
private $apiConsumer;
function __construct() {
$this->apiConsumer = app()->make('apiconsumer');
}
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle($request, Closure $next)
{
$response = $this->apiConsumer->post('/oauth/token', $request->input(), [
'content-type' => 'application/json'
]);
if (!$response->isSuccessful()) {
return response($response->getContent(), 401)
->header('content-type', 'application/json');
}
$response = json_decode($response->getContent(), true);
$request->headers->add([
'Authorization' => 'Bearer ' . $response['access_token'],
'X-Requested-With' => 'XMLHttpRequest'
]);
return $next($request);
}
}
上面的中间件我是结合Passport的CheckClientCredentials
使用的。
protected $middlewareGroups = [
'web' => [
...
],
'api' => [
'throttle:60,1',
'bindings',
\App\Http\Middleware\AddAccessTokenHeader::class,
\Laravel\Passport\Http\Middleware\CheckClientCredentials::class
],
];
通过这种方式,我能够确保 $request->input('client_id')
是可靠的并且不会被伪造。
有一个棘手的方法。
可以修改中间件CheckClientCredentials中handle的方法,添加这一行即可。
$request["oauth_client_id"] = $psr->getAttribute('oauth_client_id');
然后在controller的函数中可以得到client_id:
public function info(\Illuminate\Http\Request $request)
{
var_dump($request->oauth_client_id);
}
OAuth 令牌和客户端信息作为 受保护变量 存储在 Laravel\Passport\HasApiTokens 特征(您添加到用户模型中)中。
因此,只需将 getter 方法添加到您的 User 模型 即可公开 OAuth 信息:
public function get_oauth_client(){
return $this->accessToken->client;
}
这将为 return oauth_clients table
的 Eloquent 模型
我深入研究了 CheckClientCredentials class 并提取了从令牌中获取 client_id
所需的内容。 aud
声明是 client_id
的存储位置。
<?php
Route::middleware('client')->group(function() {
Route::get('/client-id', function (Request $request) {
$jwt = trim(preg_replace('/^(?:\s+)?Bearer\s/', '', $request->header('authorization')));
$token = (new \Lcobucci\JWT\Parser())->parse($jwt);
return ['client_id' => $token->getClaim('aud')];
});
});
很少有地方可以重构它以便轻松访问,但这取决于您的应用程序
public function handle($request, Closure $next, $scope)
{
if (!empty($scope)) {
$psr = (new DiactorosFactory)->createRequest($request);
$psr = $this->server->validateAuthenticatedRequest($psr);
$clientId = $psr->getAttribute('oauth_client_id');
$request['oauth_client_id'] = intval($clientId);
}
return $next($request);
}
把上面的内容放到你的中间件文件中,然后你就可以通过request()->oauth_client_id
访问client_id
我用它来访问经过身份验证的客户端应用程序...
$bearerToken = $request->bearerToken();
$tokenId = (new \Lcobucci\JWT\Parser())->parse($bearerToken)->getHeader('jti');
$client = \Laravel\Passport\Token::find($tokenId)->client;
$client_id = $client->id;
$client_secret = $client->secret;
然而答案已经很晚了,我在提取 JTI 时遇到了一些错误 header
在 Laravel 6.x 中,因为 JTI 不再在 header 中,而仅在 payload/claim 中。 (使用客户赠款)
local.ERROR: Requested header is not configured {"exception":"[object] (OutOfBoundsException(code: 0): Requested header is not configured at /..somewhere/vendor/lcobucci/jwt/src/Token.php:112)
此外,将它添加到中间件中对我来说不是一个选项。因为我在我的应用程序的几个地方需要它。
所以我扩展了原始的 Laravel Passport Client (oauth_clients) 模型。
并检查 header 以及有效载荷。允许传递请求,或使用
请求门面,如果没有请求被传递。
<?php
namespace App\Models;
use Illuminate\Support\Facades\Request as RequestFacade;
use Illuminate\Http\Request;
use Laravel\Passport\Client;
use Laravel\Passport\Token;
use Lcobucci\JWT\Parser;
class OAuthClient extends Client
{
public static function findByRequest(?Request $request = null) : ?OAuthClient
{
$bearerToken = $request !== null ? $request->bearerToken() : RequestFacade::bearerToken();
$parsedJwt = (new Parser())->parse($bearerToken);
if ($parsedJwt->hasHeader('jti')) {
$tokenId = $parsedJwt->getHeader('jti');
} elseif ($parsedJwt->hasClaim('jti')) {
$tokenId = $parsedJwt->getClaim('jti');
} else {
Log::error('Invalid JWT token, Unable to find JTI header');
return null;
}
$clientId = Token::find($tokenId)->client->id;
return (new static)->findOrFail($clientId);
}
}
现在您可以像这样在 laravel 应用中的任何地方使用它:
如果您有可用的 $request object,(例如来自控制器)
$client = OAuthClient::findByRequest($request);
或者即使请求以某种方式不可用,您也可以不使用它,就像这样:
$client = OAuthClient::findByRequest();
希望这对今天遇到这个问题的任何人都有用。
正如我所见,上面的答案 是旧的 并且最重要的是它不适用于 laravel 8 和 php 8,所以我找到了一种获取访问令牌(当前请求)的客户端 ID 的方法
答案基本上就是做一个中间件,然后把它添加到所有你想获取客户端id的路由中。
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Nyholm\Psr7\Factory\Psr17Factory;
use Laravel\Passport\TokenRepository;
use League\OAuth2\Server\ResourceServer;
use Illuminate\Auth\AuthenticationException;
use League\OAuth2\Server\Exception\OAuthServerException;
use Symfony\Bridge\PsrHttpMessage\Factory\PsrHttpFactory;
class SetPassportClient
{
/**
* The Resource Server instance.
*
* @var \League\OAuth2\Server\ResourceServer
*/
protected $server;
/**
* Token Repository.
*
* @var \Laravel\Passport\TokenRepository
*/
protected $repository;
/**
* Create a new middleware instance.
*
* @param \League\OAuth2\Server\ResourceServer $server
* @param \Laravel\Passport\TokenRepository $repository
* @return void
*/
public function __construct(ResourceServer $server, TokenRepository $repository)
{
$this->server = $server;
$this->repository = $repository;
}
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle(Request $request, Closure $next)
{
$psr = (new PsrHttpFactory(
new Psr17Factory,
new Psr17Factory,
new Psr17Factory,
new Psr17Factory
))->createRequest($request);
try {
$psr = $this->server->validateAuthenticatedRequest($psr);
} catch (OAuthServerException $e) {
throw new AuthenticationException;
}
$token = $this->repository->find($psr->getAttribute('oauth_access_token_id'));
if (!$token)
abort(401);
$request->merge(['passportClientId' => $token->client_id]);
return $next($request);
}
}
添加中间件到app\Http\Kernel.php
protected $routeMiddleware = [
.
.
'passport.client.set' => \App\Http\Middleware\SetPassportClient::class
];
最后在路由中添加中间件
Route::middleware(['client', 'passport.client.set'])->get('/test-client-id', function (Request $request){
dd($request->passportClientId); // this the client id
});
抱歉,回答很长,但我希望所有人都清楚。
所有代码的灵感来自 laravel CheckCredentials.php
在最新的实现中,您可以使用:
use Laravel\Passport\Token;
use Lcobucci\JWT\Configuration;
$bearerToken = request()->bearerToken();
$tokenId = Configuration::forUnsecuredSigner()->parser()->parse($bearerToken)->claims()->get('jti');
$client = Token::find($tokenId)->client;
此处建议:https://github.com/laravel/passport/issues/124#issuecomment-784731969
在一个方法中,您可以轻松获得:
$token = $request->user()->token();
$clientId = $token['client_id'];
我正在编写一个小型短信网关,供几个项目使用,
我实施了 laravel 护照身份验证 (client credentials grant token)
然后我将 CheckClientCredentials
添加到 api 中间件组:
protected $middlewareGroups = [
'web' => [
...
],
'api' => [
'throttle:60,1',
'bindings',
\Laravel\Passport\Http\Middleware\CheckClientCredentials::class
],
];
逻辑工作正常,现在在我的控制器中我需要让客户端与有效令牌相关联。
routes.php
Route::post('/sms', function(Request $request) {
// save the sms along with the client id and send it
$client_id = ''; // get the client id somehow
sendSms($request->text, $request->to, $client_id);
});
出于明显的安全原因,我永远无法将客户端 ID 与消费者请求一起发送,例如$client_id = $request->client_id;
.
所以,没有答案......
我能够通过使用自己的 API 来解决问题,最后我想出了更简单的身份验证流程,客户端需要在每个请求中发送他们的 ID 和密码,然后我使用自己的 /oauth/token
发送凭据的路由,灵感来自 Esben Petersen blog post.
生成访问令牌后,我将其附加到正在处理的 Symfony\Request
实例的 headers。
我的最终输出是这样的:
<?php
namespace App\Http\Middleware;
use Request;
use Closure;
class AddAccessTokenHeader
{
/**
* Octipus\ApiConsumer
* @var ApiConsumer
*/
private $apiConsumer;
function __construct() {
$this->apiConsumer = app()->make('apiconsumer');
}
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle($request, Closure $next)
{
$response = $this->apiConsumer->post('/oauth/token', $request->input(), [
'content-type' => 'application/json'
]);
if (!$response->isSuccessful()) {
return response($response->getContent(), 401)
->header('content-type', 'application/json');
}
$response = json_decode($response->getContent(), true);
$request->headers->add([
'Authorization' => 'Bearer ' . $response['access_token'],
'X-Requested-With' => 'XMLHttpRequest'
]);
return $next($request);
}
}
上面的中间件我是结合Passport的CheckClientCredentials
使用的。
protected $middlewareGroups = [
'web' => [
...
],
'api' => [
'throttle:60,1',
'bindings',
\App\Http\Middleware\AddAccessTokenHeader::class,
\Laravel\Passport\Http\Middleware\CheckClientCredentials::class
],
];
通过这种方式,我能够确保 $request->input('client_id')
是可靠的并且不会被伪造。
有一个棘手的方法。 可以修改中间件CheckClientCredentials中handle的方法,添加这一行即可。
$request["oauth_client_id"] = $psr->getAttribute('oauth_client_id');
然后在controller的函数中可以得到client_id:
public function info(\Illuminate\Http\Request $request)
{
var_dump($request->oauth_client_id);
}
OAuth 令牌和客户端信息作为 受保护变量 存储在 Laravel\Passport\HasApiTokens 特征(您添加到用户模型中)中。
因此,只需将 getter 方法添加到您的 User 模型 即可公开 OAuth 信息:
public function get_oauth_client(){
return $this->accessToken->client;
}
这将为 return oauth_clients table
的 Eloquent 模型我深入研究了 CheckClientCredentials class 并提取了从令牌中获取 client_id
所需的内容。 aud
声明是 client_id
的存储位置。
<?php
Route::middleware('client')->group(function() {
Route::get('/client-id', function (Request $request) {
$jwt = trim(preg_replace('/^(?:\s+)?Bearer\s/', '', $request->header('authorization')));
$token = (new \Lcobucci\JWT\Parser())->parse($jwt);
return ['client_id' => $token->getClaim('aud')];
});
});
很少有地方可以重构它以便轻松访问,但这取决于您的应用程序
public function handle($request, Closure $next, $scope)
{
if (!empty($scope)) {
$psr = (new DiactorosFactory)->createRequest($request);
$psr = $this->server->validateAuthenticatedRequest($psr);
$clientId = $psr->getAttribute('oauth_client_id');
$request['oauth_client_id'] = intval($clientId);
}
return $next($request);
}
把上面的内容放到你的中间件文件中,然后你就可以通过request()->oauth_client_id
我用它来访问经过身份验证的客户端应用程序...
$bearerToken = $request->bearerToken();
$tokenId = (new \Lcobucci\JWT\Parser())->parse($bearerToken)->getHeader('jti');
$client = \Laravel\Passport\Token::find($tokenId)->client;
$client_id = $client->id;
$client_secret = $client->secret;
然而答案已经很晚了,我在提取 JTI 时遇到了一些错误 header 在 Laravel 6.x 中,因为 JTI 不再在 header 中,而仅在 payload/claim 中。 (使用客户赠款)
local.ERROR: Requested header is not configured {"exception":"[object] (OutOfBoundsException(code: 0): Requested header is not configured at /..somewhere/vendor/lcobucci/jwt/src/Token.php:112)
此外,将它添加到中间件中对我来说不是一个选项。因为我在我的应用程序的几个地方需要它。
所以我扩展了原始的 Laravel Passport Client (oauth_clients) 模型。 并检查 header 以及有效载荷。允许传递请求,或使用 请求门面,如果没有请求被传递。
<?php
namespace App\Models;
use Illuminate\Support\Facades\Request as RequestFacade;
use Illuminate\Http\Request;
use Laravel\Passport\Client;
use Laravel\Passport\Token;
use Lcobucci\JWT\Parser;
class OAuthClient extends Client
{
public static function findByRequest(?Request $request = null) : ?OAuthClient
{
$bearerToken = $request !== null ? $request->bearerToken() : RequestFacade::bearerToken();
$parsedJwt = (new Parser())->parse($bearerToken);
if ($parsedJwt->hasHeader('jti')) {
$tokenId = $parsedJwt->getHeader('jti');
} elseif ($parsedJwt->hasClaim('jti')) {
$tokenId = $parsedJwt->getClaim('jti');
} else {
Log::error('Invalid JWT token, Unable to find JTI header');
return null;
}
$clientId = Token::find($tokenId)->client->id;
return (new static)->findOrFail($clientId);
}
}
现在您可以像这样在 laravel 应用中的任何地方使用它:
如果您有可用的 $request object,(例如来自控制器)
$client = OAuthClient::findByRequest($request);
或者即使请求以某种方式不可用,您也可以不使用它,就像这样:
$client = OAuthClient::findByRequest();
希望这对今天遇到这个问题的任何人都有用。
正如我所见,上面的答案 是旧的 并且最重要的是它不适用于 laravel 8 和 php 8,所以我找到了一种获取访问令牌(当前请求)的客户端 ID 的方法
答案基本上就是做一个中间件,然后把它添加到所有你想获取客户端id的路由中。
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Nyholm\Psr7\Factory\Psr17Factory;
use Laravel\Passport\TokenRepository;
use League\OAuth2\Server\ResourceServer;
use Illuminate\Auth\AuthenticationException;
use League\OAuth2\Server\Exception\OAuthServerException;
use Symfony\Bridge\PsrHttpMessage\Factory\PsrHttpFactory;
class SetPassportClient
{
/**
* The Resource Server instance.
*
* @var \League\OAuth2\Server\ResourceServer
*/
protected $server;
/**
* Token Repository.
*
* @var \Laravel\Passport\TokenRepository
*/
protected $repository;
/**
* Create a new middleware instance.
*
* @param \League\OAuth2\Server\ResourceServer $server
* @param \Laravel\Passport\TokenRepository $repository
* @return void
*/
public function __construct(ResourceServer $server, TokenRepository $repository)
{
$this->server = $server;
$this->repository = $repository;
}
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle(Request $request, Closure $next)
{
$psr = (new PsrHttpFactory(
new Psr17Factory,
new Psr17Factory,
new Psr17Factory,
new Psr17Factory
))->createRequest($request);
try {
$psr = $this->server->validateAuthenticatedRequest($psr);
} catch (OAuthServerException $e) {
throw new AuthenticationException;
}
$token = $this->repository->find($psr->getAttribute('oauth_access_token_id'));
if (!$token)
abort(401);
$request->merge(['passportClientId' => $token->client_id]);
return $next($request);
}
}
添加中间件到app\Http\Kernel.php
protected $routeMiddleware = [
.
.
'passport.client.set' => \App\Http\Middleware\SetPassportClient::class
];
最后在路由中添加中间件
Route::middleware(['client', 'passport.client.set'])->get('/test-client-id', function (Request $request){
dd($request->passportClientId); // this the client id
});
抱歉,回答很长,但我希望所有人都清楚。
所有代码的灵感来自 laravel CheckCredentials.php
在最新的实现中,您可以使用:
use Laravel\Passport\Token;
use Lcobucci\JWT\Configuration;
$bearerToken = request()->bearerToken();
$tokenId = Configuration::forUnsecuredSigner()->parser()->parse($bearerToken)->claims()->get('jti');
$client = Token::find($tokenId)->client;
此处建议:https://github.com/laravel/passport/issues/124#issuecomment-784731969
在一个方法中,您可以轻松获得:
$token = $request->user()->token();
$clientId = $token['client_id'];