Laravel 认证系统的内部结构
Auth Facade 与 AuthManager
Auth Facade 是 Illuminate\Auth\AuthManager 的代理。AuthManager 采用驱动模式管理多个 Guard,根据 config/auth.php 的配置生成并缓存合适的 Guard 实例。
// Auth::guard('web') 的内部动作(AuthManager::guard() 的简化版)
public function guard($name = null)
{
$name = $name ?: $this->getDefaultDriver();
return $this->guards[$name] ?? ($this->guards[$name] = $this->resolve($name));
}
resolve() 从 config/auth.php 的 guards 数组中读取 driver 键,并调用相应的工厂闭包。内置的 session 与 token 驱动也是以同样的机制注册的。
Guard 接口与 StatefulGuard 接口的区别
Laravel 的认证 Guard 至少要实现 Illuminate\Contracts\Auth\Guard。如果需要维持 Session,则要实现 StatefulGuard。
Guard 接口(Illuminate\Contracts\Auth\Guard)
Guard 接口(Illuminate\Contracts\Auth\Guard)
interface Guard
{
// 是否存在已认证用户
public function check();
// 是否为访客(未认证)
public function guest();
// 返回当前已认证的用户(未认证则为 null)
public function user();
// 返回当前已认证用户的 ID
public function id();
// 校验凭证是否有效(不执行登录)
public function validate(array $credentials = []);
// 是否已设置用户(未登录时也可通过 setUser 注入)
public function hasUser();
// 手动设置已认证用户
public function setUser(Authenticatable $user);
}
StatefulGuard 接口(Illuminate\Contracts\Auth\StatefulGuard)
StatefulGuard 接口(Illuminate\Contracts\Auth\StatefulGuard)
StatefulGuard 继承 Guard,并追加了使用 Session 或 Cookie 保持登录状态所需的方法。interface StatefulGuard extends Guard
{
// 校验凭证并登录(可带 remember 标志)
public function attempt(array $credentials = [], $remember = false);
// 不写入 Session,仅对本次请求进行认证
public function once(array $credentials = []);
// 直接以 User 实例登录
public function login(Authenticatable $user, $remember = false);
// 通过 ID 登录
public function loginUsingId($id, $remember = false);
// 通过 ID 只对本次请求进行认证
public function onceUsingId($id);
// 判断是否是通过「记住登录」Cookie 登录的
public function viaRemember();
// 登出
public function logout();
}
API 认证、自定义 Token 认证等无需 Session 的 Guard 只需实现
Guard 即可。像管理员登录这种需要 Session 的场景则需要实现 StatefulGuard。自定义 Guard 的实现
GuardHelpers trait
由于 Guard 接口中的 check()、guest()、id()、hasUser() 几乎都是相同的实现,Laravel 提供了 Illuminate\Auth\GuardHelpers trait。使用该 trait 可以把必须实现的方法收敛为 user() 与 validate() 两个。
// GuardHelpers 提供的默认实现(节选)
trait GuardHelpers
{
protected $user;
public function check(): bool
{
return ! is_null($this->user());
}
public function guest(): bool
{
return ! $this->check();
}
public function id(): mixed
{
return $this->user()?->getAuthIdentifier();
}
public function hasUser(): bool
{
return ! is_null($this->user);
}
public function setUser(Authenticatable $user): static
{
$this->user = $user;
return $this;
}
}
API Token 认证 Guard 实现示例
参照TokenGuard 的设计,实现一个简单的 API Token 认证 Guard。它从请求头或查询参数中获取 Token,通过 UserProvider 解析用户。
创建 Guard 类
在
app/Auth 目录下创建 Guard 类。<?php
namespace App\Auth;
use Illuminate\Auth\GuardHelpers;
use Illuminate\Contracts\Auth\Guard;
use Illuminate\Contracts\Auth\UserProvider;
use Illuminate\Http\Request;
class ApiTokenGuard implements Guard
{
use GuardHelpers;
protected Request $request;
public function __construct(UserProvider $provider, Request $request)
{
$this->provider = $provider;
$this->request = $request;
}
/**
* 返回当前已认证用户
*/
public function user(): ?\Illuminate\Contracts\Auth\Authenticatable
{
// 若已缓存用户,则直接返回
if (! is_null($this->user)) {
return $this->user;
}
$token = $this->getTokenForRequest();
if (empty($token)) {
return null;
}
// 通过 UserProvider 获取用户
$this->user = $this->provider->retrieveByCredentials([
'api_token' => $token,
]);
return $this->user;
}
/**
* 仅校验凭证(不登录)
*/
public function validate(array $credentials = []): bool
{
if (empty($credentials['api_token'])) {
return false;
}
return (bool) $this->provider->retrieveByCredentials($credentials);
}
/**
* 从请求中获取 Token
*
* 优先级:Bearer 头 → 查询参数 → 请求体
*/
protected function getTokenForRequest(): ?string
{
$token = $this->request->bearerToken();
if (empty($token)) {
$token = $this->request->query('api_token');
}
if (empty($token)) {
$token = $this->request->input('api_token');
}
return $token ?: null;
}
/**
* 替换请求实例
*/
public function setRequest(Request $request): static
{
$this->request = $request;
return $this;
}
}
在 ServiceProvider 中注册 Guard
在
AppServiceProvider 的 boot() 方法中通过 Auth::extend() 注册 Guard。<?php
namespace App\Providers;
use App\Auth\ApiTokenGuard;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
public function boot(): void
{
Auth::extend('api-token', function (Application $app, string $name, array $config) {
// 用 Auth::createUserProvider() 解析 config 中的 provider
$provider = Auth::createUserProvider($config['provider'] ?? 'users');
return new ApiTokenGuard($provider, $app->make('request'));
});
}
}
Auth::createUserProvider() 读取 config/auth.php 的 providers 配置,返回对应的 UserProvider 实例。只要没有自定义 Provider,就可以按此方式使用标准的 EloquentUserProvider。在 config/auth.php 中配置 Guard
在
config/auth.php 中添加新的 Guard。'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
// 添加的自定义 Guard
'api' => [
'driver' => 'api-token', // 与 Auth::extend() 的第 1 个参数保持一致
'provider' => 'users',
],
],
将 Guard 应用到路由
在 在 Controller 或代码中使用特定 Guard 时调用
auth 中间件后指定 Guard 名称。// routes/api.php
use Illuminate\Support\Facades\Route;
Route::middleware('auth:api')->group(function () {
Route::get('/user', function () {
return auth()->user();
});
Route::get('/posts', [\App\Http\Controllers\PostController::class, 'index']);
});
Auth::guard('api') 或 auth('api')。$user = Auth::guard('api')->user();
用闭包定义简易 Guard
使用Auth::viaRequest() 可以不创建类,仅用闭包定义简单的 Guard。适合原型验证或极为简单的认证。
use Illuminate\Http\Request;
use App\Models\User;
// AppServiceProvider::boot() 中
Auth::viaRequest('custom-token', function (Request $request): ?User {
$token = $request->bearerToken();
if (empty($token)) {
return null;
}
return User::where('api_token', $token)->first();
});
config/auth.php 中的配置:
'guards' => [
'api' => [
'driver' => 'custom-token',
],
],
使用
Auth::viaRequest() 定义的 Guard 不使用 UserProvider,因此 retrieveById() 等 Provider 方法不可用。生产环境建议使用基于 Auth::extend() 的类式 Guard。自定义 UserProvider 的实现
若要从数据库以外的来源(外部 API、LDAP 等)获取用户信息,请实现 Illuminate\Contracts\Auth\UserProvider 接口。
<?php
namespace App\Auth;
use App\Models\User;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Contracts\Auth\UserProvider;
class ApiUserProvider implements UserProvider
{
public function __construct(
protected string $apiBaseUrl,
protected string $model = User::class,
) {}
/**
* 通过 ID 获取用户
*/
public function retrieveById(mixed $identifier): ?Authenticatable
{
return ($this->model)::find($identifier);
}
/**
* 通过「记住登录」Token 获取用户
* 不使用 Session 的 Guard 直接返回 null 即可
*/
public function retrieveByToken(mixed $identifier, string $token): ?Authenticatable
{
return null;
}
/**
* 更新「记住登录」Token
* 不使用 Session 的 Guard 可不做处理
*/
public function updateRememberToken(Authenticatable $user, string $token): void {}
/**
* 通过凭证获取用户
* 由 Guard 的 validate() 与 user() 调用
*/
public function retrieveByCredentials(array $credentials): ?Authenticatable
{
if (empty($credentials['api_token'])) {
return null;
}
// 通过外部 API 校验 Token 并获取用户信息的示例
$response = \Illuminate\Support\Facades\Http::withToken($credentials['api_token'])
->get("{$this->apiBaseUrl}/auth/me");
if (! $response->successful()) {
return null;
}
$data = $response->json();
// 关联本地 DB 用户,或动态生成模型
return User::firstOrCreate(
['external_id' => $data['id']],
['name' => $data['name'], 'email' => $data['email']],
);
}
/**
* 校验取得的用户的凭证
* Token 认证下,只要 retrieveByCredentials 成功即可返回 true
*/
public function validateCredentials(Authenticatable $user, array $credentials): bool
{
return true;
}
/**
* 检查是否需要重新哈希密码
* Token 认证下始终返回即可,无需操作
*/
public function rehashPasswordIfRequired(Authenticatable $user, array $credentials, bool $force = false): void {}
}
注册自定义 UserProvider
// AppServiceProvider::boot()
Auth::provider('api-user', function (Application $app, array $config) {
return new \App\Auth\ApiUserProvider(
config('services.auth_api.base_url'),
$config['model'] ?? \App\Models\User::class,
);
});
config/auth.php 的 providers 段追加:
'providers' => [
'users' => [
'driver' => 'eloquent',
'model' => App\Models\User::class,
],
// 自定义 Provider
'api-users' => [
'driver' => 'api-user',
'model' => App\Models\User::class,
],
],
'guards' => [
'api' => [
'driver' => 'api-token',
'provider' => 'api-users', // 指定自定义 Provider
],
],
实战用例
多重认证(管理员与普通用户使用不同 Guard)
创建 Admin 模型
准备用于管理员的 Eloquent 模型。通过继承
Authenticatable 与 Auth 系统集成。php artisan make:model Admin -m
<?php
namespace App\Models;
use Illuminate\Foundation\Auth\User as Authenticatable;
class Admin extends Authenticatable
{
protected $fillable = ['name', 'email', 'password'];
protected $hidden = ['password', 'remember_token'];
}
配置 config/auth.php
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
'admin' => [
'driver' => 'session',
'provider' => 'admins', // 管理员 Provider
],
],
'providers' => [
'users' => [
'driver' => 'eloquent',
'model' => App\Models\User::class,
],
'admins' => [
'driver' => 'eloquent',
'model' => App\Models\Admin::class, // 管理员模型
],
],
配置路由与中间件
// routes/web.php
// 面向普通用户的路由(默认 web Guard)
Route::middleware('auth')->group(function () {
Route::get('/dashboard', [DashboardController::class, 'index']);
});
// 面向管理员的路由(admin Guard)
Route::prefix('admin')->middleware('auth:admin')->group(function () {
Route::get('/dashboard', [AdminDashboardController::class, 'index']);
});
指定 Guard 编写登录处理
<?php
namespace App\Http\Controllers\Admin;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
class LoginController extends Controller
{
public function store(Request $request)
{
$credentials = $request->validate([
'email' => 'required|email',
'password' => 'required',
]);
// 显式指定 admin Guard 进行 attempt
if (Auth::guard('admin')->attempt($credentials)) {
$request->session()->regenerate();
return redirect()->intended('/admin/dashboard');
}
return back()->withErrors(['email' => '登录信息不正确。']);
}
public function destroy(Request $request)
{
Auth::guard('admin')->logout();
$request->session()->invalidate();
$request->session()->regenerateToken();
return redirect('/admin/login');
}
}
基于 JWT Token 的外部 API 认证
使用外部 JWT 认证服务时的自定义 Guard 实现示例。<?php
namespace App\Auth;
use App\Models\User;
use Illuminate\Auth\GuardHelpers;
use Illuminate\Contracts\Auth\Guard;
use Illuminate\Contracts\Auth\UserProvider;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Http;
class JwtGuard implements Guard
{
use GuardHelpers;
protected Request $request;
protected ?array $payload = null;
public function __construct(UserProvider $provider, Request $request)
{
$this->provider = $provider;
$this->request = $request;
}
public function user(): ?\Illuminate\Contracts\Auth\Authenticatable
{
if (! is_null($this->user)) {
return $this->user;
}
$token = $this->request->bearerToken();
if (empty($token)) {
return null;
}
// 通过外部服务校验 JWT
$payload = $this->verifyToken($token);
if (is_null($payload)) {
return null;
}
$this->payload = $payload;
$this->user = $this->provider->retrieveById($payload['sub']);
return $this->user;
}
public function validate(array $credentials = []): bool
{
if (empty($credentials['token'])) {
return false;
}
return ! is_null($this->verifyToken($credentials['token']));
}
/**
* 校验 JWT 并获取 payload
*/
protected function verifyToken(string $token): ?array
{
$response = Http::withToken($token)
->get(config('services.auth.verify_url'));
if (! $response->successful()) {
return null;
}
return $response->json();
}
/**
* 返回已校验的 JWT payload
*/
public function payload(): ?array
{
return $this->payload;
}
}
AppServiceProvider 中注册:
Auth::extend('jwt', function (Application $app, string $name, array $config) {
return new \App\Auth\JwtGuard(
Auth::createUserProvider($config['provider'] ?? 'users'),
$app->make('request'),
);
});
可以像
Auth::guard('jwt')->payload() 这样访问自定义 Guard 特有的方法。Auth::guard() 返回的正是 Guard 实例本身,因此接口中未定义的方法也可以调用。测试
在自定义 Guard 的单元测试中,通过 MockUserProvider 来验证 Guard 的行为。
<?php
namespace Tests\Unit\Auth;
use App\Auth\ApiTokenGuard;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Contracts\Auth\UserProvider;
use Illuminate\Http\Request;
use PHPUnit\Framework\MockObject\MockObject;
use Tests\TestCase;
class ApiTokenGuardTest extends TestCase
{
private UserProvider&MockObject $provider;
private ApiTokenGuard $guard;
protected function setUp(): void
{
parent::setUp();
$this->provider = $this->createMock(UserProvider::class);
}
public function test_user_returns_null_when_no_token(): void
{
$request = Request::create('/');
$guard = new ApiTokenGuard($this->provider, $request);
$this->assertNull($guard->user());
}
public function test_user_returns_user_with_valid_bearer_token(): void
{
$mockUser = $this->createMock(Authenticatable::class);
$this->provider
->expects($this->once())
->method('retrieveByCredentials')
->with(['api_token' => 'valid-token'])
->willReturn($mockUser);
$request = Request::create('/');
$request->headers->set('Authorization', 'Bearer valid-token');
$guard = new ApiTokenGuard($this->provider, $request);
$this->assertSame($mockUser, $guard->user());
}
public function test_check_returns_false_when_no_token(): void
{
$request = Request::create('/');
$guard = new ApiTokenGuard($this->provider, $request);
$this->assertFalse($guard->check());
}
public function test_validate_returns_false_without_api_token_credential(): void
{
$request = Request::create('/');
$guard = new ApiTokenGuard($this->provider, $request);
$this->assertFalse($guard->validate([]));
}
}
ActingAs 的功能测试中,可以为指定 Guard 设定用户。
// 用特定 Guard 认证用户后进行测试
$this->actingAs($user, 'api')
->getJson('/api/user')
->assertOk();
相关页面
认证(入门)
了解 Starter Kit 及标准认证流程。
服务容器
理解 Guard 注册所依赖的服务容器机制。