Laravel Passport

composer require laravel/passport
php artisan migrate // 創建表來存儲客戶端和 access_token
php artisan passport:install // 生成加密 access_token 的 key、密碼授權客戶端、個人訪問客戶端
Laravel\Passport\HasApiTokens Trait 添加到 App\User 模型中 // 提供一些輔助函數檢查已認證用戶的令牌和使用範圍
在 AuthServiceProvider 的 boot 方法中調用 Passport::routes 函數 // 訪問令牌並撤銷訪問令牌路由,客戶端和個人訪問令牌相關路由
config/auth.php 中 api 的 driver 選項改爲 passport

自定義 passport migration

php artisan vendor:publish --tag=passport-migrations

生成加密 access_token 的 key

php artisan passport:keys

AuthServiceProvider 中指定 passport key 加載路徑

Passport::loadKeysFrom('/secret-keys/oauth');

PASSPORT_PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----
<private key here>
-----END RSA PRIVATE KEY-----"

PASSPORT_PUBLIC_KEY="-----BEGIN PUBLIC KEY-----
<public key here>
-----END PUBLIC KEY-----"

配置

過期時間

Passport::tokensExpireIn(now()->addDays(15)); // access_token
Passport::refreshTokensExpireIn(now()->addDays(30));// refresh_token
Passport::personalAccessTokensExpireIn(now()->addMonths(6)); // personal access_token

重寫模型

use App\Models\Passport\AuthCode;
use App\Models\Passport\Client;
use App\Models\Passport\PersonalAccessClient;
use App\Models\Passport\Token;

/**
 * Register any authentication / authorization services.
 *
 * @return void
 */
public function boot()
{
    $this->registerPolicies();

    Passport::routes();

    Passport::useTokenModel(Token::class);
    Passport::useClientModel(Client::class);
    Passport::useAuthCodeModel(AuthCode::class);
    Passport::usePersonalAccessClientModel(PersonalAccessClient::class);
}

管理客戶端

命令行創建客戶端

php artisan passport:client
// 設置回調地址白名單的格式:http://example.com/callback,http://examplefoo.com/callback (逗號隔開)

api 管理客戶端

axios.get('/oauth/clients')
    .then(response => {
        console.log(response.data);
    });
const data = {
    name: 'Client Name',
    redirect: 'http://example.com/callback'
};

axios.post('/oauth/clients', data)
    .then(response => {
        console.log(response.data);
    })
    .catch (response => {
        // List errors on response...
    });
const data = {
    name: 'New Client Name',
    redirect: 'http://example.com/callback'
};

axios.put('/oauth/clients/' + clientId, data)
    .then(response => {
        console.log(response.data);
    })
    .catch (response => {
        // List errors on response...
    });
axios.delete('/oauth/clients/' + clientId)
    .then(response => {
        //
    });

授權碼模式

請求 token

Route::get('/redirect', function (Request $request) {
    $request->session()->put('state', $state = Str::random(40));

    $query = http_build_query([
        'client_id' => 'client-id',
        'redirect_uri' => 'http://example.com/callback',
        'response_type' => 'code',
        'scope' => '',
        'state' => $state,
    ]);

    return redirect('http://your-app.com/oauth/authorize?'.$query);
});

自定義用戶授權頁面

php artisan vendor:publish --tag=passport-views

跳過用戶授權頁面

<?php

namespace App\Models\Passport;

use Laravel\Passport\Client as BaseClient;

class Client extends BaseClient
{
    public function skipsAuthorization()
    {
        return $this->firstParty();
    }
}

獲取 access_token

Route::get('/callback', function (Request $request) {
    $state = $request->session()->pull('state');

    throw_unless(
        strlen($state) > 0 && $state === $request->state,
        InvalidArgumentException::class
    );

    $http = new GuzzleHttp\Client;

    $response = $http->post('http://your-app.com/oauth/token', [
        'form_params' => [
            'grant_type' => 'authorization_code',
            'client_id' => 'client-id',
            'client_secret' => 'client-secret',
            'redirect_uri' => 'http://example.com/callback',
            'code' => $request->code,
        ],
    ]);

    return json_decode((string) $response->getBody(), true);
});

刷新令牌

$http = new GuzzleHttp\Client;

$response = $http->post('http://your-app.com/oauth/token', [
    'form_params' => [
        'grant_type' => 'refresh_token',
        'refresh_token' => 'the-refresh-token',
        'client_id' => 'client-id',
        'client_secret' => 'client-secret',
        'scope' => '',
    ],
]);

return json_decode((string) $response->getBody(), true);

密碼模式

php artisan passport:client --password
$http = new GuzzleHttp\Client;

$response = $http->post('http://your-app.com/oauth/token', [
    'form_params' => [
        'grant_type' => 'password',
        'client_id' => 'client-id',
        'client_secret' => 'client-secret',
        'username' => '[email protected]',
        'password' => 'my-password',
        'scope' => '', // '*'是所有範圍,應該只在密碼模式和客戶端模式時候使用
    ],
]);

return json_decode((string) $response->getBody(), true);

自定義密碼驗證和 username 字段

public function validateForPassportPasswordGrant($password)
{
    return Hash::check($password, $this->password);
}

public function findForPassport($username)
{
    return $this->where('username', $username)->first();
}

隱式模式

Passport::enableImplicitGrant();
Route::get('/redirect', function (Request $request) {
    $request->session()->put('state', $state = Str::random(40));

    $query = http_build_query([
        'client_id' => 'client-id',
        'redirect_uri' => 'http://example.com/callback',
        'response_type' => 'token',
        'scope' => '',
        'state' => $state,
    ]);

    return redirect('http://your-app.com/oauth/authorize?'.$query);
});

客戶端模式

php artisan passport:client --client
use Laravel\Passport\Http\Middleware\CheckClientCredentials;

protected $routeMiddleware = [
    'client' => CheckClientCredentials::class,
];
Route::get('/orders', function (Request $request) {
    ...
})->middleware('client');
Route::get('/orders', function (Request $request) {
    ...
})->middleware('client:check-status,your-scope');
$guzzle = new GuzzleHttp\Client;

$response = $guzzle->post('http://your-app.com/oauth/token', [
    'form_params' => [
        'grant_type' => 'client_credentials',
        'client_id' => 'client-id',
        'client_secret' => 'client-secret',
        'scope' => 'your-scope',
    ],
]);

return json_decode((string) $response->getBody(), true)['access_token'];

使用 access_token

$response = $client->request('GET', '/api/user', [
    'headers' => [
        'Accept' => 'application/json',
        'Authorization' => 'Bearer '.$accessToken,
    ],
]);

玩轉 scope

# AuthServiceProvider
use Laravel\Passport\Passport;

Passport::tokensCan([
    'place-orders' => 'Place orders',
    'check-status' => 'Check order status',
]);

Passport::setDefaultScope([
    'check-status',
    'place-orders',
]);
Route::get('/redirect', function () {
    $query = http_build_query([
        'client_id' => 'client-id',
        'redirect_uri' => 'http://example.com/callback',
        'response_type' => 'code',
        'scope' => 'place-orders check-status', // 傳遞 scope 格式
    ]);

    return redirect('http://your-app.com/oauth/authorize?'.$query);
});

檢驗 scope

# app/Http/Kernel.php 中 $routeMiddleware
'scopes' => \Laravel\Passport\Http\Middleware\CheckScopes::class,
'scope' => \Laravel\Passport\Http\Middleware\CheckForAnyScope::class,
Route::get('/orders', function () {
    // Access token has both "check-status" and "place-orders" scopes...
})->middleware('scopes:check-status,place-orders');
Route::get('/orders', function () {
    // Access token has either "check-status" or "place-orders" scope...
})->middleware('scope:check-status,place-orders');

就算含有訪問令牌驗證的請求已經通過應用程序的驗證,你仍然可以使用當前授權 User 實例上的 tokenCan 方法來驗證令牌是否擁有指定的作用域

use Illuminate\Http\Request;

Route::get('/orders', function (Request $request) {
    if ($request->user()->tokenCan('place-orders')) {
        //
    }
});

scopeIds 方法將返回所有已定義 ID / 名稱的數組:

Laravel\Passport\Passport::scopeIds();

scopes 方法將返回一個包含所有已定義作用域數組的 Laravel\Passport\Scope 實例:

Laravel\Passport\Passport::scopes();

scopesFor 方法將返回與給定 ID / 名稱匹配的 Laravel\Passport\Scope 實例數組:

Laravel\Passport\Passport::scopesFor(['place-orders', 'check-status']);

你可以使用 hasScope 方法確定是否已定義給定作用域:

Laravel\Passport\Passport::hasScope('place-orders');

事件

protected $listen = [
    'Laravel\Passport\Events\AccessTokenCreated' => [
        'App\Listeners\RevokeOldTokens',
    ],

    'Laravel\Passport\Events\RefreshTokenCreated' => [
        'App\Listeners\PruneOldTokens',
    ],
];

javascript 中使用 api

'web' => [
    // Other middleware...
    \Laravel\Passport\Http\Middleware\CreateFreshApiToken::class,
],
// 注意:你應該確保在您的中間件堆棧中 CreateFreshApiToken 中間件之前列出了 EncryptCookies 中間件。
axios.get('/api/user')
    .then(response => {
        console.log(response.data);
    });

自定義 Cookie 名稱

public function boot()
{
    $this->registerPolicies();

    Passport::routes();

    Passport::cookie('custom_name');
}

測試

actingAs 方法可以指定當前已認證用戶及其作用域 。

use App\User;
use Laravel\Passport\Passport;

public function testServerCreation()
{
    Passport::actingAs(
        factory(User::class)->create(),
        ['create-servers']
    );

    $response = $this->post('/api/create-server');

    $response->assertStatus(201);
}

actingAsClient 方法可以指定當前已認證客戶端及其作用域 。

use Laravel\Passport\Client;
use Laravel\Passport\Passport;

public function testGetOrders()
{
    Passport::actingAsClient(
        factory(Client::class)->create(),
        ['check-status']
    );

    $response = $this->get('/api/orders');

    $response->assertStatus(200);
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章