You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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');
});

关键优化点

  1. 令牌缓存:服务类中添加了令牌缓存逻辑,避免频繁调用令牌接口,减少不必要的请求开销。
  2. 错误分层处理:区分系统接口异常和用户权限错误,给用户更清晰的反馈。
  3. Session安全:登录时调用session()->regenerate()防止会话固定攻击。

内容的提问来源于stack exchange,提问作者Marco Bozzola

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.15 09:41:29