Laravel中集成基于外部API的自定义认证方案咨询
Laravel 自定义认证实现方案(基于外部API权限校验)
核心思路
不需要复杂的OAuth2集成(本次场景是校验用户权限而非对接第三方登录),通过自定义Guard + API服务类即可实现,同时复用Laravel自带的认证流程逻辑,减少冗余代码。
步骤1:封装外部API服务类
先把获取令牌、校验权限的逻辑封装成服务类,方便在认证流程中调用:
// app/Services/ExternalAuthService.php namespace App\Services; use Illuminate\Support\Facades\Http; use Illuminate\Support\Facades\Cache; class ExternalAuthService { protected $token; public function __construct() { $this->fetchAccessToken(); } // 获取并缓存API访问令牌 protected function fetchAccessToken() { // 优先从缓存取令牌,避免频繁调用令牌接口 $token = Cache::get('external_api_access_token'); if ($token) { $this->token = $token; return; } $response = Http::post('https://xxx.xxx.xxx.xx/oauth/v2/accesstoken', [ 'grant_type' => 'client_credentials', 'client_id' => env('EXTERNAL_API_CLIENT_ID'), 'client_secret' => env('EXTERNAL_API_CLIENT_SECRET'), ]); if (!$response->successful()) { throw new \RuntimeException('获取外部API令牌失败'); } $tokenData = $response->json(); $this->token = $tokenData['access_token']; // 根据令牌实际过期时间设置缓存时长 Cache::put('external_api_access_token', $this->token, now()->addSeconds($tokenData['expires_in'] - 60)); } // 校验用户服务权限 public function verifyPermission(string $source, string $licensePlate): bool { $response = Http::withToken($this->token)->get('https://xxx.xxx.xxx.xx/subscribers/v1/', [ 'source' => $source, 'licenseplate' => $licensePlate, ]); // 根据API实际返回结构调整判断逻辑,示例假设返回{"has_access": true} return $response->successful() && $response->json('has_access') === true; } }
在.env文件中添加API配置项:
EXTERNAL_API_CLIENT_ID=你的客户端ID EXTERNAL_API_CLIENT_SECRET=你的客户端密钥
步骤2:创建极简认证用户模型
Laravel认证体系依赖Authenticatable接口,我们创建一个不需要关联数据库的极简模型,用来承载用户认证信息:
// app/Models/ExternalUser.php namespace App\Models; use Illuminate\Contracts\Auth\Authenticatable; use Illuminate\Auth\Authenticatable as AuthenticatableTrait; class ExternalUser implements Authenticatable { use AuthenticatableTrait; protected $fillable = ['source', 'licenseplate']; public function __construct(array $attributes = []) { parent::__construct($attributes); // 设置认证标识字段(可根据需求调整) $this->setAuthIdentifierName('source'); } }
如果后续需要持久化用户信息到本地数据库,只需让该模型继承Illuminate\Database\Eloquent\Model,并添加对应表结构即可。
步骤3:实现自定义认证Guard
创建自定义Guard来处理基于外部API的认证逻辑:
// app/Auth/ExternalAuthGuard.php namespace App\Auth; use App\Services\ExternalAuthService; use App\Models\ExternalUser; use Illuminate\Auth\GuardHelpers; use Illuminate\Contracts\Auth\Guard; use Illuminate\Http\Request; class ExternalAuthGuard implements Guard { use GuardHelpers; protected $request; protected $authService; public function __construct(Request $request, ExternalAuthService $authService) { $this->request = $request; $this->authService = $authService; } // 尝试认证用户 public function attempt(array $credentials = []): bool { $source = $credentials['source'] ?? ''; $licensePlate = $credentials['licenseplate'] ?? ''; if ($this->authService->verifyPermission($source, $licensePlate)) { $this->user = new ExternalUser([ 'source' => $source, 'licenseplate' => $licensePlate, ]); $this->login($this->user); return true; } return false; } // 获取当前登录用户 public function user() { if (!is_null($this->user)) { return $this->user; } // 从Session恢复用户信息 $userData = $this->request->session()->get('external_auth_user'); if ($userData) { $this->user = new ExternalUser($userData); } return $this->user; } // 登录用户(写入Session) public function login($user) { $this->request->session()->put('external_auth_user', $user->only(['source', 'licenseplate'])); $this->setUser($user); } // 退出登录 public function logout() { $this->request->session()->forget('external_auth_user'); $this->user = null; } // 校验凭证格式合法性 public function validate(array $credentials = []): bool { return !empty($credentials['source']) && !empty($credentials['licenseplate']); } }
步骤4:注册Guard和服务提供者
创建服务提供者来绑定自定义Guard和API服务:
// app/Providers/ExternalAuthServiceProvider.php namespace App\Providers; use App\Auth\ExternalAuthGuard; use App\Services\ExternalAuthService; use Illuminate\Support\Facades\Auth; use Illuminate\Support\ServiceProvider; class ExternalAuthServiceProvider extends ServiceProvider { public function register() { // 单例绑定API服务类 $this->app->singleton(ExternalAuthService::class, function ($app) { return new ExternalAuthService(); }); } public function boot() { // 注册自定义Guard驱动 Auth::extend('external', function ($app, $name, array $config) { return new ExternalAuthGuard($app['request'], $app->make(ExternalAuthService::class)); }); } }
在config/app.php的providers数组中添加该服务提供者:
App\Providers\ExternalAuthServiceProvider::class,
修改config/auth.php配置,将默认Guard切换为我们的自定义Guard:
'defaults' => [ 'guard' => 'external', 'passwords' => 'users', // 不需要密码重置可忽略 ], 'guards' => [ 'external' => [ 'driver' => 'external', ], // 保留默认web Guard(如需其他认证场景) 'web' => [ 'driver' => 'session', 'provider' => 'users', ], ],
步骤5:实现登录控制器与视图
生成登录控制器:
php artisan make:controller Auth/LoginController
修改控制器逻辑:
// app/Http/Controllers/Auth/LoginController.php namespace App\Http\Controllers\Auth; use App\Http\Controllers\Controller; use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; class LoginController extends Controller { public function showLoginForm() { return view('auth.login'); } public function login(Request $request) { $request->validate([ 'source' => 'required|string', 'licenseplate' => 'required|string', ]); try { if (Auth::attempt($request->only('source', 'licenseplate'))) { $request->session()->regenerate(); return redirect()->intended('/dashboard'); } } catch (\RuntimeException $e) { return back()->withErrors(['system' => '系统接口异常,请稍后重试']); } return back()->withErrors([ 'licenseplate' => 'Source或车牌号无效,或无服务权限', ]); } public function logout(Request $request) { Auth::logout(); $request->session()->invalidate(); $request->session()->regenerateToken(); return redirect('/'); } }
创建登录视图resources/views/auth/login.blade.php:
<form method="POST" action="{{ route('login') }}"> @csrf <div class="mb-3"> <label class="form-label">Source</label> <input type="text" class="form-control" name="source" value="{{ old('source') }}" required> @error('source') <div class="text-danger">{{ $message }}</div> @enderror </div> <div class="mb-3"> <label class="form-label">车牌号</label> <input type="text" class="form-control" name="licenseplate" value="{{ old('licenseplate') }}" required> @error('licenseplate') <div class="text-danger">{{ $message }}</div> @enderror </div> @error('system') <div class="text-danger mb-3">{{ $message }}</div> @enderror <button type="submit" class="btn btn-primary">登录</button> </form>
添加路由到routes/web.php:
// 登录路由 Route::get('/login', [App\Http\Controllers\Auth\LoginController::class, 'showLoginForm'])->name('login'); Route::post('/login', [App\Http\Controllers\Auth\LoginController::class, 'login']); Route::post('/logout', [App\Http\Controllers\Auth\LoginController::class, 'logout'])->name('logout'); // 专属区域路由(需认证) Route::middleware('auth')->group(function () { Route::get('/dashboard', function () { return view('dashboard'); })->name('dashboard'); });
关键优化点
- 令牌缓存:服务类中添加了令牌缓存逻辑,避免频繁调用令牌接口,减少不必要的请求开销。
- 错误分层处理:区分系统接口异常和用户权限错误,给用户更清晰的反馈。
- Session安全:登录时调用
session()->regenerate()防止会话固定攻击。
内容的提问来源于stack exchange,提问作者Marco Bozzola
相关产品推荐
相关产品推荐

