用户注册登录流程及多字段登录实现
注册登录流程
我们接着上一篇教程继续往下走。如果系统中还没有任何用户的话,首先需要点击注册按钮进入注册页面:
填写注册信息,点击注册按钮,完成注册。如果设置了邮箱验证功能,需要验证邮箱后才能登录,否则会直接登录,并跳转到 home
路由:
如果在登录和注册过程中验证表单失败,则会在对应字段提示错误信息:
对应的验证规则在相应的控制器中都可以看到,如果你对这一块还不太熟悉,可以查看在 Laravel 控制器中进行表单请求字段验证这篇教程。
获取登录用户信息
用户登录成功后,就可以获取登录用户信息了,我们可以通过多种方式获取用户信息。
通过 Auth 门面
我们可以在控制器中通过 Auth
门面快速获取当前登录用户信息:
$user = Auth::user(); // 获取当前登录用户的完整信息
$userId = Auth::id(); // 获取当前登录用户 ID
上述返回的 $user
数据是一个 User
模型实例,可以通过它获取用户所有信息。此外,你还可以通过 Auth
门面提供的其他方法快速进行一些常见判断,比如判断用户是否已经登录,可以通过 Auth::check()
方法,如果已登录,该方法返回 true
,否则返回 false
。相对的,还有 Auth::guest()
方法判断用户是否未登录,逻辑与 Auth::check()
方法刚好相反。
Blade 指令
我们还可以在 Blade 视图上使用上述门面方法进行流程判断,此外,Blade 模板引擎还为我们提供了对应的快捷指令:
@auth
// 用户已登录...
@endauth
@guest
// 用户未登录...
@endguest
通过 Request 实例
除了 Auth
门面外,我们还可以在控制器方法中通过 Request
请求对象实例获取登录用户信息:
public function update(Request $request)
{
$user = $request->user(); # 获取当前登录用户实例
}
获取到的 $user
数据和通过 Auth::user()
返回结果完全一致。
学院君注:尽量不要在控制器和视图以外的地方使用
Auth
门面获取用户信息,在其他地方获取可以通过数据传递的方式,因为服务类或模型类的应用场景不一定是 Web 层,有可能出现获取不到 Session 而导致获取数据为空的情况。
登录失败次数限制
需要注意的是对于用户登录功能而言,框架底层会校验登录失败次数,超过指定阈值会报错:
默认阈值是 1 分钟内尝试 5 次,超过这个次数就会提示失败次数过多,过段时间再来尝试。对于未登录用户而言,这个限制维度是基于 IP 的。对应的实现细节位于 Illuminate\Foundation\Auth\ThrottlesLogins
中。
如果你想要修改这个阈值,可以在 LoginController
控制器中通过设置如下属性来实现:
// 单位时间内最大登录尝试次数
protected $maxAttempts = 3;
// 单位时间值
protected $decayMinutes = 30;
这次,我们设置半小时(30分钟)内只能尝试 3 次。再次尝试登录功能,检查设置是否生效:
第四次尝试登录,就会报错了。
支持用户名/邮箱登录
通过用户名登录
Laravel 支持在用户名和邮箱之间切换登录,默认是通过注册邮箱登录的,如果你想要调整为通过用户名登录,很简单,在 LoginController
控制器中定义一个 username()
方法重写 AuthenticatesUsers
Trait 中的同名方法即可,Laravel 底层通过该方法定义登录字段名,而不是写死的,我们在该方法中返回登录字段名:
public function username()
{
return 'name';
}
然后把前端视图对应的登录表单字段调整为 name
。这样,就可以基于用户名进行登录了。
通过用户名或邮箱登录
实际开发中,有时候我们的登录字段可能需要同时支持用户名/注册邮箱/手机号登录,即用户既可以在登录框中输入邮箱,也可以输入用户名,这个时候,Laravel 框架底层自带的 UserProvider 已经满足不了这个需求了,我们需要扩展默认的 EloquentUserProvider
来实现这个功能。
注:实现此功能有个前提,那就是用户名、手机号和邮箱字段一样,在数据库中是唯一的。
自定义 UserProvider
为此,我们需要创建一个继承自底层 EloquentUserProvider
的子类,并将其存放到 app/Extensions
目录下,重写父类用户记录获取方法 retrieveByCredentials
如下:
<?php
namespace App\Extensions;
use Illuminate\Support\Str;
use Illuminate\Auth\EloquentUserProvider as BaseUserProvider;
class EloquentUserProvider extends BaseUserProvider
{
/**
* Retrieve a user by the given credentials.
*
* @param array $credentials
* @return \Illuminate\Contracts\Auth\Authenticatable|null
*/
public function retrieveByCredentials(array $credentials)
{
if (empty($credentials) ||
(count($credentials) === 1 &&
array_key_exists('password', $credentials))) {
return;
}
// First we will add each credential element to the query as a where clause.
// Then we can execute the query and, if we found a user, return it in a
// Eloquent User "model" that will be utilized by the Guard instances.
$query = $this->createModel()->newQuery();
// 用于标识是否是第一个登录字段,如果包含多个登录字段,使用 OR 查询
$flag = false;
foreach ($credentials as $key => $value) {
if (Str::contains($key, 'password')) {
continue;
}
if ($flag) {
$query->orWhere($key, $value);
} else {
$query->where($key, $value);
$flag = true;
}
}
return $query->first();
}
}
核心是获取用户记录那段逻辑,我们支持用户登录凭证数组 $credentials
除密码字段外传入一个或多个登录字段,多个登录字段使用 OR 查询。
然后我们需要到 app/Providers/AuthServiceProvider.php
的 boot
方法中使用自定义的 UserProvider 覆盖系统自带的 EloquentUserProvider
:
// 通过自定义的 EloquentUserProvider 覆盖系统默认的
Auth::provider('eloquent', function ($app, $config) {
return new EloquentUserProvider($app->make('hash'), $config['model']);
});
这样,用户登录认证时使用的就是我们自定义的这个 EloquentUserProvider
了。
修改控制器传入字段
接下来,我们到控制器中修改传入字段逻辑以便在业务层支持传入不同字段。打开 app/Http/Controllers/Auth/LoginController.php
控制器文件,为控制器设置一个新的属性用于包含系统支持的登录字段:
// 支持的登录字段
protected $supportFields = ['name', 'email'];
然后重写 AuthenticatesUsers
中的 credentials
方法用于传入系统支持的所有登录字段,并将其值都设置为用户在登录表单输入框中输入的值:
// 将支持的登录字段都传递到 UserProvider 进行查询
public function credentials(Request $request)
{
$credentials = $request->only($this->username(), 'password');
foreach ($this->supportFields as $field) {
if (empty($credentials[$field])) {
$credentials[$field] = $credentials[$this->username()];
}
}
return $credentials;
}
修改登录表单字段
最后去掉登录视图 login.blade.php
对邮箱字段的验证,以支持不同的字段输入:
<div class="form-group row">
<label for="email" class="col-sm-4 col-form-label text-md-right">{{ __('邮箱') . '/' . __('用户名') }}</label>
<div class="col-md-6">
<input id="email" class="form-control{{ $errors->has('email') ? ' is-invalid' : '' }}" name="email" value="{{ old('email') }}" required autofocus>
@if ($errors->has('email'))
<span class="invalid-feedback" role="alert">
<strong>{{ $errors->first('email') }}</strong>
</span>
@endif
</div>
</div>
测试多字段登录
这样,多字段登录功能就完成了,我们在浏览器中测试下,切换邮箱/用户名,都可以实现正常登录了:
此外,如果你还有其他支持登录的字段,比如用户昵称、手机号,都可以实现,只需在控制器的 $supportFields
属性中添加对应的字段就可以了,非常方便。
19 Comments
use App\Extentions\EloquentUserProvider;
laravel5.6 不能用嘛 ? 出现这个错误 : Exception message: Class 'App\Exceptions\EloquentUserProvider' not found 这个类确实存在啊。
引入的命名空间不对吧
5.8版本中 // 通过自定义的 EloquentUserProvider 覆盖系统默认的 Auth::provider('eloquent', function ($app, $config) { return new EloquentUserProvider($app->make('hash'), $config['model']); }); 报错为:Class 'App\Providers\Auth' not found 求助!!!谢谢各位大佬指点一下啊
Auth 门面引入完整的命名空间!
注意 app/Extensions 路径是否正确
自定义 UserProvider中namespace App\Extentions;少了个p
没有啊
自定义 UserProvider // 用于标识是否是第一个登录字段,如果包含多个登录字段,使用 OR 查询 这句话是不是应该改为 ‘用于标识是否是一个登录字段,如果包含多个登录字段,使用 OR 查询’