本次專案的後臺使用 iview-admin 搭建,前後端是分離的,需要給後臺管理也寫一套介面(看了幾篇資料說可以把iview-admin 放到 laravel 專案裡跑,但沒有成功,卒),於是想用 passport 來做後臺介面的認證。誰知過程一波三折,折騰了兩天,算是摸清了坑也爬起來了。坑了一次不能再被坑第二次,也不希望後面的人接著被坑,於是就有了這篇文章,給大家介紹一下整體的流程和避坑操作。
1. 安裝
專案是 Laravel 5.5 的,直接使用 composer require laravel/passport
安裝可能會出現報錯。如果報錯的話建議修改 composer.json
檔案,然後 composer update
安裝。
"require": {
"laravel/passport": "~4.0"
}
2. 配置
執行遷移
框架會自動生成 passport 所需要的資料表
$ php artisan migrate
生成加密金鑰
$ php artisan passport:install
使用 HasApiTokens
在認證用的 model 中新增 HasApiTokens
Trait,並且 model 要繼承 Illuminate\Foundation\Auth\User
<?php
namespace App\Models;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Laravel\Passport\HasApiTokens;
class AdminUser extends Authenticatable
{
use HasApiTokens;
}
更改 guard 和 provider
在 config/auth.php
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
'admin' => [
'driver' => 'passport',
'provider' => 'admin_users',
],
],
'providers' => [
'users' => [
'driver' => 'eloquent',
'model' => App\Models\User::class,
],
'admin_users' => [
'driver' => 'eloquent',
'model' => App\Models\AdminUser::class,
],
],
3. 使用
到這一步,可以開始實現登入的邏輯了,我希望驗證的是 admin_users
表裡的管理使用者,但是根據其他教程的配置,發現一直驗證的都是 users
表。最後看了原始碼,發現這麼一個大坑。src/Bridge/UserRepository.php
public function getUserEntityByUserCredentials($username, $password, $grantType, ClientEntityInterface $clientEntity)
{
$provider = config('auth.guards.api.provider');
if (is_null($model = config('auth.providers.'.$provider.'.model'))) {
throw new RuntimeException('Unable to determine authentication model from configuration.');
}
if (method_exists($model, 'findForPassport')) {
$user = (new $model)->findForPassport($username);
} else {
$user = (new $model)->where('email', $username)->first();
}
·
·
·
}
這段原始碼可以發現兩個問題。第一個,passport 認證的 model 是 auth.guards.api.provider
裡定義的 model。
第二個,認證預設驗證的欄位是 email
,如果你的使用者名稱是其他的欄位,那麼就要自己在認證的model裡實現 findForPassport
方法。發現了坑,就想辦法解決掉,下面是具體的使用方法,可以供大家參考。
建立一個登入認證的 trait
<?php
/**
* Created by PhpStorm.
* User: jason
* Date: 2018/12/24
* Time: 0:14
*/
namespace App\Http\Controllers\Admin\Traits;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\RequestException;
trait ProxyHelpers
{
/**
* @return mixed
* @throws \GuzzleHttp\Exception\GuzzleException
*/
public function authenticate($guard = '')
{
$client = new Client();
try {
$url = request()->root() . '/admin/oauth/token';
$params = array_merge(config('passport.proxy'), [
'username' => request('username'),
'password' => request('password'),
'provider' => $guard
]);
$respond = $client->post($url, ['form_params' => $params]);
} catch (RequestException $exception) {
abort(401, $exception->getMessage());
}
if ($respond->getStatusCode() !== 401) {
return json_decode($respond->getBody()->getContents(), true);
}
abort(401, '賬號或密碼錯誤');
}
public function getRefreshToken()
{
$client = new Client();
try {
$url = request()->root() . 'admin/oauth/token';
$params = array_merge(config('passport.proxy'), [
'refresh_token' => request('refresh_token'),
]);
$respond = $client->post($url, ['form_params' => $params]);
} catch (RequestException $exception) {
abort(401, '請求失敗,伺服器錯誤');
}
if ($respond->getStatusCode() !== 401) {
return json_decode($respond->getBody()->getContents(), true);
}
abort(401, 'refresh_token 錯誤');
}
}
登入控制器
使用上面定義的 ProxyHelpers trait 來完成認證,響應部分可以參考 Laravel5.5+passport 放棄 dingo 開發 API 實戰,讓 API 開發更省心 的內容來實現
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Admin\Traits\ProxyHelpers;
use App\Http\Requests\Admin\LoginRequest;
use App\Models\AdminUser;
use Carbon\Carbon;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
class LoginController extends Controller
{
use AuthenticatesUsers, ProxyHelpers;
public function login(LoginRequest $request)
{
$user = AdminUser::query()->where('username', $request->username)->first();
if (!$user) {
return $this->failed('使用者不存在', 401);
}
if (!Hash::check($request->password, $user->password)) {
return $this->failed('密碼不正確');
}
$user->last_login_at = Carbon::now()->toDateTimeString();
$user->save();
$token = $this->authenticate(‘admin’); // 認證用的 guard
return $this->success(['token' => $token, 'user' => $user]);
}
public function logout()
{
if (Auth::guard('admin')->check()) {
Auth::guard('admin')->user()->token()->delete();
}
}
}
建立一個處理請求的中介軟體 PassportCustomProvider
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Support\Facades\Config;
class PassportCustomProvider
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle($request, Closure $next)
{
$params = $request->all();
if (array_key_exists('provider', $params)) {
Config::set('auth.guards.api.provider', $params['provider']); // 動態配置 auth.guards.api.provider 的 model
}
return $next($request);
}
}
註冊路由中介軟體
在 app\Http\Kernel.php
中註冊配置的路由
protected $routeMiddleware = [
'auth' => \Illuminate\Auth\Middleware\Authenticate::class,
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
'can' => \Illuminate\Auth\Middleware\Authorize::class,
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
'passport-administrators' => \App\Http\Middleware\PassportCustomProvider::class
];
配置 passport 路由
在 app/Providers/AuthServiceProvider.php
class AuthServiceProvider extends ServiceProvider
{
public function boot()
{
$this->registerPolicies();
// 我只需要前後端分離的 password 授權模式,所以只註冊獲取 Token 的路由
Passport::routes(function(RouteRegistrar $router) {
$router->forAccessTokens();
}, ['prefix' => 'admin/oauth', 'middleware' => 'passport-administrators']);
}
}
最後配置 LoginController 的路由,然後就發起請求吧!
參考資料
- passport API 認證 -- 多表登入
- Laravel Passport 踩坑日記
- Laravel 5.5 使用 Passport 實現 Auth 認證
- Laravel5.5+passport 放棄 dingo 開發 API 實戰,讓 API 開發更省心
本作品採用《CC 協議》,轉載必須註明作者和本文連結