看到Laravel-China 社區常有人問 Laravel Passport 用於密碼驗證方式來獲取 Token 的問題,恰好我最近一個 API 項目使用 Laravel Dingo Api+Passport,也是使用 Oauth2 的'grant_type' => 'password'密碼受權來作 Auth 驗證,對於如何作登陸登出,以及多帳號系統的認證等經常使用場景作一下簡單的使用小總結。php

基本配置

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

config/auth.phpjson

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

Providers/AuthServiceProvider.phpapi

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 自定義中間件返回markdown

<?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.phpapp

/** * 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方法,示例代碼以下:ide

App\Models\Userspost

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 請求只傳用戶名和密碼

對於密碼受權的方式須要提交的參數以下:this

$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發送請求帶上相關的配置就能夠了。spa

.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\User; 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 技術棧筆記