返回 2. 修改
Laravel 11 Breeze: 手机号/邮箱登录实现教程
php
2025-03-04 22:38:54
Laravel 11 Breeze: 使用手机号或邮箱登录
遇到个事儿:想在 Laravel 11 里,用 Breeze 实现个登录功能,允许用户输手机号或者邮箱都能登录。 默认的 Breeze 只支持邮箱,这咋整?
问题根源
Breeze 默认的 LoginRequest.php
文件里,写死了用 email
字段做验证和登录:
// ...
public function rules(): array
{
return [
'email' => ['required', 'email','exists:users,email'],
'password' => ['required', 'string'],
];
}
// ...
public function authenticate(): void
{
$this->ensureIsNotRateLimited();
if (! Auth::attempt($this->only('email', 'password'), $this->boolean('remember'))) {
// ...
}
// ...
}
// ...
public function throttleKey(): string
{
return Str::transliterate(Str::lower($this->string('email')).'|'.$this->ip());
}
关键点有这么几个:
rules()
方法里,规定了必须是email
格式,且在users
表里得有。authenticate()
方法,直接用了$this->only('email', 'password')
去尝试登录。throttleKey()
方法, 也直接拿email
字段来做登录限制。
要实现手机号/邮箱登录,就得把这几个地方都改了。
解决方法
1. 修改数据表结构 (可选)
如果你的 users
表里,手机号和邮箱是分开的俩字段(比如 email
和 phone_number
),那这步可以跳过。
如果你想用一个字段(比如就叫 username
)来存手机号或者邮箱,那就得改下表结构:
-
创建迁移文件:
php artisan make:migration alter_users_table_add_username_column
-
修改迁移文件:
// database/migrations/xxxx_xx_xx_xxxxxx_alter_users_table_add_username_column.php use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; return new class extends Migration { public function up(): void { Schema::table('users', function (Blueprint $table) { $table->string('username')->unique()->after('name'); // after('name') 只是放个位置,你随意. $table->dropUnique('users_email_unique'); // 删除 email 的唯一索引, 因为 username 要变成唯一的了. $table->dropColumn('email'); //删掉 email 列 }); } public function down(): void { Schema::table('users', function (Blueprint $table) { $table->string('email')->unique()->after('name'); $table->dropUnique('users_username_unique'); $table->dropColumn('username'); }); } };
-
执行迁移:
php artisan migrate
- 温馨提示, 你最好在迁移文件执行前备份你的数据库.
2. 修改 LoginRequest.php
这是最关键的一步,要改三个地方:
<?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;
use Illuminate\Support\Facades\Validator; // 引入 Validator
class LoginRequest extends FormRequest
{
public function authorize(): bool
{
return true;
}
public function rules(): array
{
return [
'login' => ['required', 'string'], // 改成 login 字段
'password' => ['required', 'string'],
];
}
public function messages():array
{
return [
'login.required' => '请输入邮箱或手机号',
];
}
/**
* 自定义验证逻辑。
*/
protected function prepareForValidation()
{
$loginValue = $this->input('login');
$validator = Validator::make(['login' => $loginValue], [
'login' => 'email',
]);
//如果是邮箱
if (! $validator->fails()) {
$this->merge([
'email' => $loginValue,
'username' => $loginValue, //假设你有username,就这么用.
]);
}else
{
$this->merge([
'phone_number' => $loginValue, //手机号
]);
}
}
public function authenticate(): void
{
$this->ensureIsNotRateLimited();
$loginType = filter_var($this->input('login'), FILTER_VALIDATE_EMAIL) ? 'email' : 'phone_number';
//如果 users 表里 统一使用一个 username 字段
//$loginType = filter_var($this->input('login'), FILTER_VALIDATE_EMAIL) ? 'username' : 'username';
if (!Auth::attempt([$loginType => $this->input('login'), 'password' => $this->input('password')], $this->boolean('remember')))
{
RateLimiter::hit($this->throttleKey());
throw ValidationException::withMessages([
'login' => trans('auth.failed'), // 这里的 'login' 对应前端的 name="login"
]);
}
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([
'login' => trans('auth.throttle', [
'seconds' => $seconds,
'minutes' => ceil($seconds / 60),
]),
]);
}
public function throttleKey(): string
{
return Str::transliterate(Str::lower($this->input('login')).'|'.$this->ip());
}
}
主要变动说明:
rules()
方法
将验证规则改为验证login
字段,
新增 messages() 自定义验证提示消息.- 新增 prepareForValidation() 方法 :
自定义预验证逻辑,判断如果输入的内容符合邮箱格式, 则合并'email' => $loginValue
,反之, 则合并phone_number
authenticate()
方法 :
用filter_var
判断一下,看login
字段是邮箱还是手机号,然后构造对应的查询条件。throttleKey()
:
限流的key 也得改, 直接使用 login 字段, 防御暴力破解。
3. 修改登录表单 (login.blade.php)
把表单里的 email
字段改成 login
:
<!-- resources/views/auth/login.blade.php -->
<form method="POST" action="{{ route('login') }}">
@csrf
<!-- Email or Phone Number Address -->
<div>
<label for="login">Email or Phone Number</label>
<input id="login" type="text" name="login" value="{{ old('login') }}" required autofocus autocomplete="username" />
<x-input-error :messages="$errors->get('login')" class="mt-2" />
</div>
<!-- Password -->
<div class="mt-4">
<label for="password">Password</label>
<input id="password" type="password" name="password" required autocomplete="current-password" />
<x-input-error :messages="$errors->get('password')" class="mt-2" />
</div>
<!-- Remember Me -->
<div class="block mt-4">
<label for="remember_me">
<input id="remember_me" type="checkbox" name="remember">
<span>Remember me</span>
</label>
</div>
<div>
<a href="{{ route('password.request') }}">
Forgot your password?
</a>
<button type="submit">
Log in
</button>
</div>
</form>
进阶使用和安全建议
-
手机号格式验证 : 你可以在
prepareForValidation()
方法使用更严格的正则, 对手机号进行强校验。 -
Throttle 限流 : 虽然改了
throttleKey()
,但如果攻击者猜到你的某个用户名,还是可以针对性攻击。 可以考虑结合其他方式,如验证码。 -
数据库字段 : 设计数据库的时候就考虑到这点能省很多事,比如用一个
login_id
字段,存各种类型的唯一标识。 -
双因子认证(2FA) : 更安全的做法是,登录成功后,再加一层验证,比如短信验证码、邮箱验证码。
希望这详细步骤能解决你在用 Laravel 11 Breeze 时遇到的登录问题。