Laravel Passport API 認證使用小結

Ryan發表於2017-08-04

看到Laravel-China社群常有人問Laravel Passport用於密碼驗證方式來獲取Token的問題,剛好我最近一個API專案使用Laravel Dingo Api+Passport,也是使用Oauth2 的'grant_type' => 'password'密碼授權來做Auth驗證,對於如何做登入登出,以及多賬號系統的認證等常用場景做一下簡單的使用小總結。

基本配置

基本安裝配置主要參照官方文件,具體不詳細說,列出關鍵程式碼段

config/auth.php

'guards' => [
        'api' => [
            'driver' => 'passport',
            'provider' => 'users',
        ],
    ],

 'providers' => [
        'users' => [
            'driver' => 'eloquent',
            'model' => \App\Models\User::class
        ],
    ],

Providers/AuthServiceProvider.php

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

        //預設令牌發放的有效期是永久
        //Passport::tokensExpireIn(Carbon::now()->addDays(2));
        //Passport::refreshTokensExpireIn(Carbon::now()->addDays(4));
        Passport::routes(function (RouteRegistrar $router) {
            //對於密碼授權的方式只要這幾個路由就可以了
            config(['auth.guards.api.provider' => 'users']);
            $router->forAccessTokens();
        });
    }

Middleware/AuthenticateApi.php 自定義中介軟體返回

<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Auth\Middleware\Authenticate;
use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException;

class AuthenticateApi extends Authenticate
{
    protected function authenticate(array $guards)
    {

        if ($this->auth->guard('api')->check()) {
            return $this->auth->shouldUse('api');
        }

        throw new UnauthorizedHttpException('', 'Unauthenticated');
    }
}

App/Http/Kernel.php


    /**
     * The application's route middleware.
     *
     * These middleware may be assigned to groups or used individually.
     *
     * @var array
     */
    protected $routeMiddleware = [
        'api-auth' => AuthenticateApi::class,
        ......
    ];
}

賬號驗證欄位不止郵箱

對於賬號驗證不止是資料表中的emial欄位,還可能是使用者名稱或者手機號欄位只需要在User模型中新增findForPassport方法,示例程式碼如下:

App\Models\Users

class User extends Authenticatable implements Transformable
{
    use TransformableTrait, HasApiTokens, SoftDeletes;
    public function findForPassport($login)
    {
        return $this->orWhere('email', $login)->orWhere('phone', $login)->first();
    }
}

客戶端獲取access_token請求只傳使用者名稱和密碼

對於密碼授權的方式需要提交的引數如下:

$response = $http->post('http://your-app.com/oauth/token', [
    'form_params' => [
        'grant_type' => 'password',
        'client_id' => 'client-id',
        'client_secret' => 'client-secret',
        'username' => 'taylor@laravel.com',
        'password' => 'my-password',
        'scope' => '',
    ],
]);

但是客戶端請求的時候不想把grant_type,client_id,client_secret,scope放到請求引數中或者暴露給客戶端,只像JWT一樣只傳送usernamepassword 怎麼辦?很簡單我們只要將不需要請求的放到配置檔案中,然後客戶端請求使用者名稱密碼以後我們再向oauth/token傳送請求帶上相關的配置就可以了。

.env.php

OAUTH_GRANT_TYPE=password
OAUTH_CLIENT_ID=1
OAUTH_CLIENT_SECRET=EvE4UPGc25TjXwv9Lmk432lpp7Uzb8G4fNJsyJ83
OAUTH_SCOPE=*

config/passport.php 當然該配置你可以配置多個client

return [
    'grant_type' => env('OAUTH_GRANT_TYPE'),
    'client_id' => env('OAUTH_CLIENT_ID'),
    'client_secret' => env('OAUTH_CLIENT_SECRET'),
    'scope' => env('OAUTH_SCOPE', '*'),
];

LoginController.php的示例程式碼如下,因為用了Dingo Api配置了api字首,所以請求/api/oauth/token

    /**
     * 獲取登入TOKEN
     * @param LoginRequest $request
     * @return \Illuminate\Http\JsonResponse
     */
    public function token(LoginRequest $request)
    {
        $username = $request->get('username');
        $user = User::orWhere('email', $username)->orWhere('phone', $username)->first();

        if ($user && ($user->status == 0)) {
            throw  new UnauthorizedHttpException('', '賬號已被禁用');
        }

        $client = new Client();
        try {
            $request = $client->request('POST', request()->root() . '/api/oauth/token', [
                'form_params' => config('passport') + $request->only(array_keys($request->rules()))
            ]);

        } catch (RequestException $e) {
            throw  new UnauthorizedHttpException('', '賬號驗證失敗');
        }

        if ($request->getStatusCode() == 401) {
            throw  new UnauthorizedHttpException('', '賬號驗證失敗');
        }
        return response()->json($request->getBody()->getContents());
    }

退出登入並清除Token

對於客戶端退出後並清除記錄在oauth_access_tokens表中的記錄,示例程式碼如下:

  /**
     * 退出登入
     */
    public function logout()
    {
        if (\Auth::guard('api')->check()) {
            \Auth::guard('api')->user()->token()->delete();
        }

        return response()->json(['message' => '登出成功', 'status_code' => 200, 'data' => null]);
    }

根據使用者ID認證使用者

app('auth')->guard('api')->setUser(User::find($userId));

多使用者表(多Auth)認證

比如針對客戶表和管理員表分別做Auth認證的情況,也列出關鍵程式碼段:

 'guards' => [
        'api' => [
            'driver' => 'passport',
            'provider' => 'users',
        ],

        'admin_api' => [
            'driver' => 'passport',
            'provider' => 'admin_users',
        ],
    ],

    'providers' => [
        'users' => [
            'driver' => 'eloquent',
            'model' => \App\Models\User::class
        ],

        'admin_users' => [
            'driver' => 'eloquent',
            'model' => \App\Models\AdminUser::class
        ],
    ],

新建一個PasspordAdminServiceProvider來實現我們自己的PasswordGrant,別忘了新增到config/app.phpproviders配置段中

AppProviders/PasspordAdminServiceProvider

<?php

namespace App\Providers;

use App\Foundation\Repository\AdminUserPassportRepository;
use League\OAuth2\Server\Grant\PasswordGrant;
use Laravel\Passport\PassportServiceProvider as BasePassportServiceProvider;
use Laravel\Passport\Passport;

class PasspordAdminServiceProvider extends BasePassportServiceProvider
{
    /**
     * Create and configure a Password grant instance.
     *
     * @return PasswordGrant
     */
    protected function makePasswordGrant()
    {
        $grant = new PasswordGrant(
            //主要是這裡,我們呼叫我們自己UserRepository
            $this->app->make(AdminUserPassportRepository::class),
            $this->app->make(\Laravel\Passport\Bridge\RefreshTokenRepository::class)
        );

        $grant->setRefreshTokenTTL(Passport::refreshTokensExpireIn());

        return $grant;
    }

}

新建AdminUserPassportRepository,Password的驗證主要透過getUserEntityByUserCredentials,它讀取配置的guards對應的provider來做認證,我們重寫該方法,透過傳遞一個引數來告訴它我們要用哪個guard來做客戶端認證

<?php

namespace App\Foundation\Repository;

use App;
use Illuminate\Http\Request;
use League\OAuth2\Server\Entities\ClientEntityInterface;
use Laravel\Passport\Bridge\UserRepository;
use Laravel\Passport\Bridge\User;
use RuntimeException;

class AdminUserPassportRepository extends UserRepository
{

    public function getUserEntityByUserCredentials($username, $password, $grantType, ClientEntityInterface $clientEntity)
    {
        $guard = App::make(Request::class)->get('guard') ?: 'api';//其實關鍵的就在這裡,就是透過傳遞一個guard引數來告訴它我們是使用api還是admin_api provider來做認證
        $provider = config("auth.guards.{$guard}.provider");
        if (is_null($model = config("auth.providers.{$provider}.model"))) {
            throw new RuntimeException('Unable to determine user model from configuration.');
        }

        if (method_exists($model, 'findForPassport')) {
            $user = (new $model)->findForPassport($username);
        } else {
            $user = (new $model)->where('email', $username)->first();
        }

        if (!$user) {
            return;
        } elseif (method_exists($user, 'validateForPassportPasswordGrant')) {
            if (!$user->validateForPassportPasswordGrant($password)) {
                return;
            }
        } elseif (!$this->hasher->check($password, $user->password)) {
            return;
        }

        return new User($user->getAuthIdentifier());
    }
}

登入和單使用者系統一樣,只是在請求oauth/token的時候帶上guard引數,示例程式碼如下:

Admin/Controllers/Auth/LoginController.php

<?php

namespace Admin\Controllers\Auth;

use Admin\Requests\Auth\LoginRequest;
use App\Http\Controllers\Controller;
use App\Models\AdminUser;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\RequestException;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
use Illuminate\Http\Request;
use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException;

class LoginController extends Controller
{
    /*
    |--------------------------------------------------------------------------
    | Login Controller
    |--------------------------------------------------------------------------
    |
    | This controller handles authenticating users for the application and
    | redirecting them to your home screen. The controller uses a trait
    | to conveniently provide its functionality to your applications.
    |
    */

    use AuthenticatesUsers;

    /**
     * Create a new controller instance.
     *
     * @return void
     */
    public function __construct()
    {
        $this->middleware('guest')->except('logout');
    }

    /**
     * 獲取登入TOKEN
     * @param LoginRequest $request
     * @return \Illuminate\Http\JsonResponse
     */
    public function token(LoginRequest $request)
    {
        $username = $request->get('username');
        $user = User::orWhere('email', $username)->orWhere('phone', $username)->first();

        if ($user && ($user->status == 0)) {
            throw  new UnauthorizedHttpException('', '賬號已被禁用');
        }

        $client = new Client();
        try {
            $request = $client->request('POST', request()->root() . '/api/oauth/token', [
                'form_params' => config('passport') + $request->only(array_keys($request->rules())) + ['guard' => 'admin_api']
            ]);
        } catch (RequestException $e) {
            throw  new UnauthorizedHttpException('', '賬號驗證失敗');
        }

        if ($request->getStatusCode() == 401) {
            throw  new UnauthorizedHttpException('', '賬號驗證失敗');
        }
        return response()->json($request->getBody()->getContents());
    }

    /**
     * 退出登入
     */
    public function logout()
    {
        if (\Auth::guard('admin_api')->check()) {
            \Auth::guard('admin_api')->user()->token()->delete();
        }

        return response()->json(['message' => '登出成功', 'status_code' => 200, 'data' => null]);
    }
}

轉載請註明: 轉載自Ryan是菜鳥 | LNMP技術棧筆記

如果覺得本篇文章對您十分有益,何不 打賞一下

謝謝打賞

本文連結地址: Laravel Passport API 認證使用小結

本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章