簡體   English   中英

Laravel Passport 通過訪問令牌獲取客戶端 ID

[英]Laravel Passport Get Client ID By Access Token

我正在寫一個小型短信網關,供幾個項目使用,

我實施了 laravel 護照身份驗證( 客戶端憑據授予令牌

然后我將CheckClientCredentials添加到 api 中間件組:

protected $middlewareGroups = [
    'web' => [
       ...
    ],

    'api' => [
        'throttle:60,1',
        'bindings',
        \Laravel\Passport\Http\Middleware\CheckClientCredentials::class
    ],
];

邏輯工作正常,現在在我的 controller 中,我需要讓客戶端與有效令牌相關聯。

路線.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; .

我用它來訪問經過身份驗證的客戶端應用程序...

$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;

來源

然而,答案已經很晚了,我在 Laravel 6.x 中提取 JTI 標頭時遇到了一些錯誤,因為 JTI 不再位於標頭中,而僅位於有效載荷/聲明中。 (使用客戶贈款)

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) 模型。 並檢查標頭和有效載荷。 如果沒有請求被傳遞,則允許傳遞請求,或使用請求門面。

<?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 對象,(例如來自控制器)

$client = OAuthClient::findByRequest($request);

或者即使請求以某種方式不可用,您也可以不使用它,如下所示:

$client = OAuthClient::findByRequest();

希望這對今天面臨這個問題的任何人都有用。

有一個棘手的方法。 可以修改中間件CheckClientCredentials中handle的方法,添加這一行即可。

        $request["oauth_client_id"] = $psr->getAttribute('oauth_client_id');

然后你可以在控制器的函數中獲取client_id:

public function info(\Illuminate\Http\Request $request)
{
    var_dump($request->oauth_client_id);
}

OAuth 令牌和客戶端信息作為受保護的變量存儲在 Laravel\\Passport\\HasApiTokens 特征(您添加到用戶模型中)中。

因此,只需在您的User 模型中添加一個 getter 方法來公開 OAuth 信息:

public function get_oauth_client(){
  return $this->accessToken->client;
}

這將為 oauth_clients 表返回一個 Eloquent 模型

所以,沒有答案...

我能夠通過使用我自己的 API 來解決這個問題,最后我想出了更簡單的身份驗證流程,客戶端需要在每個請求中發送他們的 id 和秘密,然后我使用我自己的/oauth/token路由和發送的憑據,靈感來自Esben Petersen 博客文章

生成訪問令牌后,我將其附加到正在處理的Symfony\\Request實例的標頭中。

我的最終輸出是這樣的:

<?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 類並提取了從令牌中獲取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')];
        });
    });

很少有地方可以重構它以便輕松訪問,但這取決於您的應用程序

正如我所看到的,上面的答案是舊的,最重要的是它不適用於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

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

在一種方法中,您可以輕松獲得:

$token = $request->user()->token();

$clientId = $token['client_id'];

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM