Laravel Breeze下User与Client共享认证服务实现方案
Laravel Breeze 双表(Users/Clients)认证实现方案
核心问题:你当前仅新增了clients认证提供者,但默认web guard仍绑定users表,$request->authenticate()默认走当前默认guard的校验逻辑,自然不会查询clients表,且你之前的跳转逻辑写在authenticate()调用之后,根本走不到客户端校验分支。
第一步:完善auth.php配置,新增客户端专属guard
你当前配置的guards数组缺少客户端对应的guard,修改config/auth.php如下,改完执行php artisan config:clear清除配置缓存:
return [ 'defaults' => [ 'guard' => 'web', 'passwords' => 'users', ], 'guards' => [ 'web' => [ 'driver' => 'session', 'provider' => 'users', ], // 新增客户端专属guard 'client' => [ 'driver' => 'session', 'provider' => 'clients', ], ], // providers部分你已配置正确,无需修改 'providers' => [ 'users' => [ 'driver' => 'eloquent', 'model' => App\Models\User::class, ], 'clients' => [ 'driver' => 'eloquent', 'model' => App\Models\Client::class, ], ], // passwords部分你已配置正确,无需修改 'passwords' => [ 'users' => [ 'provider' => 'users', 'table' => 'password_resets', 'expire' => 60, 'throttle' => 60, ], 'clients' => [ 'provider' => 'clients', 'table' => 'password_resets', 'expire' => 60, 'throttle' => 60, ], ], 'password_timeout' => 10800, ];
第二步:确保Client模型满足认证要求
Laravel认证逻辑要求模型必须实现Illuminate\Contracts\Auth\Authenticatable接口,直接复用框架自带的认证基类即可,打开app/Models/Client.php确认代码如下:
<?php namespace App\Models; use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Notifications\Notifiable; use Illuminate\Contracts\Auth\MustVerifyEmail; // 继承Authenticatable基类就自带所有认证所需方法 class Client extends Authenticatable implements MustVerifyEmail { use Notifiable; protected $fillable = [ 'name', 'email', 'password', ]; protected $hidden = [ 'password', 'remember_token', ]; protected $casts = [ 'email_verified_at' => 'datetime', ]; }
注意:clients表必须包含
id、password、remember_token、email_verified_at这些认证必需字段,缺的字段补写迁移文件添加即可。
第三步:拆分独立登录入口,分离认证逻辑
不要把管理员和客户端的登录逻辑塞在同一个控制器方法里,分开写逻辑能避免绝大多数认证冲突问题:
- 首先在
routes/web.php拆分路由组:
// 管理员登录路由,走默认web guard,复用Breeze自带控制器即可 Route::middleware('guest')->group(function () { Route::get('/login', [AuthenticatedSessionController::class, 'create'])->name('login'); Route::post('/login', [AuthenticatedSessionController::class, 'store']); }); // 客户端登录路由,走client guard,使用独立控制器 Route::middleware('guest:client')->prefix('portal')->name('portal.')->group(function () { Route::get('/login', [ClientAuthenticatedSessionController::class, 'create'])->name('login'); Route::post('/login', [ClientAuthenticatedSessionController::class, 'store'])->name('login.store'); });
- 新建客户端专属登录控制器
app/Http/Controllers/Auth/ClientAuthenticatedSessionController.php,逻辑和Breeze自带控制器一致,仅指定使用client guard:
<?php namespace App\Http\Controllers\Auth; use App\Http\Controllers\Controller; use App\Http\Requests\Auth\ClientLoginRequest; use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; use Illuminate\View\View; class ClientAuthenticatedSessionController extends Controller { public function create(): View { return view('portal.login'); // 对应客户端专属登录页模板 } public function store(ClientLoginRequest $request): RedirectResponse { $request->authenticate(); $request->session()->regenerate(); // 登录成功直接跳转到客户端对应portal页面 return redirect()->route('portal.show', auth('client')->id()); } public function destroy(Request $request): RedirectResponse { Auth::guard('client')->logout(); $request->session()->invalidate(); $request->session()->regenerateToken(); return redirect('/portal/login'); } }
- 新建客户端专属登录请求类
app/Http/Requests/Auth/ClientLoginRequest.php,核心是在认证方法中指定使用client guard校验:
<?php namespace App\Http\Requests\Auth; use Illuminate\Auth\Events\Lockout; use Illuminate\Foundation\Http\FormRequest; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\RateLimiter; use Illuminate\Support\Str; use Illuminate\Validation\ValidationException; class ClientLoginRequest extends FormRequest { public function authorize(): bool { return true; } public function rules(): array { return [ 'email' => ['required', 'string', 'email'], 'password' => ['required', 'string'], ]; } public function authenticate(): void { $this->ensureIsNotRateLimited(); // 指定client guard认证,就会自动查询clients表 if (! Auth::guard('client')->attempt($this->only('email', 'password'), $this->boolean('remember'))) { RateLimiter::hit($this->throttleKey()); throw ValidationException::withMessages([ 'email' => trans('auth.failed'), ]); } RateLimiter::clear($this->throttleKey()); } public function ensureIsNotRateLimited(): void { if (! RateLimiter::tooManyAttempts($this->throttleKey(), 5)) { return; } event(new Lockout($this)); $seconds = RateLimiter::availableIn($this->throttleKey()); throw ValidationException::withMessages([ 'email' => trans('auth.throttle', [ 'seconds' => $seconds, 'minutes' => ceil($seconds / 60), ]), ]); } public function throttleKey(): string { // 加client后缀,避免和管理员登录共享限流计数 return Str::transliterate(Str::lower($this->input('email')).'|'.$this->ip().'|client'); } }
第四步:修改中间件逻辑,区分不同guard的跳转规则
- 打开
app/Http/Middleware/RedirectIfAuthenticated.php,修改handle方法,让不同guard的已登录用户跳转到对应页面:
public function handle(Request $request, Closure $next, string ...$guards): Response { $guards = empty($guards) ? [null] : $guards; foreach ($guards as $guard) { if (Auth::guard($guard)->check()) { if ($guard == 'web') { return redirect(RouteServiceProvider::HOME); } if ($guard == 'client') { return redirect()->route('portal.show', Auth::guard($guard)->id()); } } } return $next($request); }
- 打开
app/Exceptions/Handler.php,修改未认证用户的跳转逻辑,避免客户端用户未登录时被跳转到管理员登录页:
protected function unauthenticated($request, AuthenticationException $exception) { if ($request->expectsJson()) { return response()->json(['message' => 'Unauthenticated.'], 401); } // 根据guard类型跳转到对应登录页 if (in_array('client', $exception->guards())) { return redirect()->guest(route('portal.login')); } return redirect()->guest(route('login')); }
额外注意事项
- 邮箱验证、密码重置逻辑要对应指定guard,比如客户端发密码重置邮件时要调用
Password::broker('clients'),不要用默认的users broker - 获取客户端登录用户信息统一用
auth('client')->user(),不要直接用Auth::user(),后者默认读取web guard的登录用户 - 路由鉴权时,客户端路由加
auth:client中间件,管理员路由加auth:web中间件,避免权限混淆
内容的提问来源于stack exchange,提问作者PeterPan
相关产品推荐
相关产品推荐

