很多应用是需要登陆后才能操作,Laravel提供了一个auth工具来实现用户的认证功能。并且有一个config/auth.php来配置auth工具。大概看一下auth工具的常用方法
Auth::check();// 判断当前用户是否未登录
Auth::guest();// 判断当前用户是否未登录,与 check() 相反
Auth::guard();// 自定义看守器 默认为 `web`
Auth::user();// 获取当前的认证用户,一个提供者的模型
Auth::id();// 获取当前的认证用户的 ID(未登录情况下会报错)
Auth::attempt(['email' => $email, 'password' => $password],true);// 通过给定的信息来尝试对用户进行认证(成功后会自动启动会话),第一个数组就是认证的参数,第二个参数true就是'记住我'功能
Auth::login(User::find(1), $remember = false);// 登录一个指定用户到应用上,一般是登陆的参数通过后,执行login方法,保存session等登陆成功的操作
Auth::logout();// 使用户退出登录(清除会话)
看一下auth.php的配置,其实Auth的实现原理就是通过一个guard来实现的
'defaults' => [
'guard' => 'web', //没有指定guard时,就用‘web’
'passwords' => 'users',
],
'guards' => [ //这就是guard数组,用哪一个guard需要指定
'web' => [
'driver' => 'session',
'provider' => 'users',
],
'api' => [
'driver' => 'token',
'provider' => 'users',
'hash' => false,
],
],
//每一个guard最少需要两个成员,driver驱动(比如登陆成功,通过什么方式保存登陆状态),provider提供者(就是用户表,保存用户名密码的那张表)
所以一个系统中,我们可以有多个认证体系,前后台认证,接口认证等等,只要配置不同的guard即可。不同认证体系,使用对应的guard就可以。
说了这么多,还是很懵逼,不怕,laravel给我们实现一个客户认证脚手架,通过命令就可以实现一套认证系统。我们把这套系统弄懂,就可以仿照它的体系生成我们自己的认证体系。
laravel脚手架实现认证体系
php artisan make:auth
这个命令会做哪些动作?
1.生成登陆,注册,退出等等的路由,在web.php新增Auth::routes();等价于下面
public function auth(array $options = [])
{
// Authentication Routes...
$this->get('login', 'Auth\LoginController@showLoginForm')->name('login');
$this->post('login', 'Auth\LoginController@login');
$this->post('logout', 'Auth\LoginController@logout')->name('logout');
// Registration Routes...
if ($options['register'] ?? true) {
$this->get('register', 'Auth\RegisterController@showRegistrationForm')->name('register');
$this->post('register', 'Auth\RegisterController@register');
}
// Password Reset Routes...
if ($options['reset'] ?? true) {
$this->resetPassword();
}
// Email Verification Routes...
if ($options['verify'] ?? false) {
$this->emailVerification();
}
}
其实就到调用Router::auth(),我们就可以看到登陆注册的路由啦,其他功能类似。
2.生成登陆注册等控制器App\Http\Controllers\Auth目录下面,上面的路由就是访问这里的控制器
3.生成登陆注册等页面resource/views/auth/目录下
4.生成provider表的migerate,创建用户表,这时候你可以修改这个用户表字段
public function up()
{
Schema::create('users', function (Blueprint $table) {
$table->bigIncrements('id');
$table->string('name');
$table->string('email')->unique();
$table->timestamp('email_verified_at')->nullable();
$table->string('password');
$table->rememberToken();
$table->timestamps();
});
}
php artisan migrate//生成user表
这样,访问上面的路由就可以生成一套认证体系了。认证系统其实就是三个步骤
1.登陆注册等逻辑接口,就是控制器
2.配置路由
3.页面
脚手架认证体系原理,我们自定义认证体系
先看注册的逻辑,我们自定义自己的注册类的时候,也可以参考着写,跳转路径,检查字段,用户表都可以根据实际情况修改。而接口,laravel已经写在RegistersUsers里面了。
class RegisterController extends Controller
{
use RegistersUsers; //这个是laravel写好的trait
protected $redirectTo = '/home';//注册成功跳转路径
public function __construct()
{
$this->middleware('guest');//添加一个guest中间件
}
protected function validator(array $data)
{
return Validator::make($data, [ //返回一个过滤器
'name' => ['required', 'string', 'max:255'],
'email' => ['required', 'string', 'email', 'max:255', 'unique:users'],
'password' => ['required', 'string', 'min:8', 'confirmed'],
]);
}
protected function create(array $data)
{
return User::create([ //注册最终会写入到provider中,通过create插入数据,返回模型
'name' => $data['name'],
'email' => $data['email'],
'password' => Hash::make($data['password']),
]);
}
protected function guard()//这个方法trait也有,但是如果我们用其他的guard,就要重写方法
{
return Auth::guard('api');//你要使用的guard
}
}
但是还是要看一下laravel是怎么通过auth和auth的配置来实现注册的
public function register(Request $request)
{
$this->validator($request->all())->validate();//判断参数
event(new Registered($user = $this->create($request->all())));//注意看create方法,这是插入到用户表中,返回那个模型。
$this->guard()->login($user);//获取对应的guard,执行login方法,其实就是告诉session这个用户登录成功状态。(注册成功,就不用重新登录),具体流程下面登录成功的操作再分析
return $this->registered($request, $user)
?: redirect($this->redirectPath());//注册成功跳转
}
其实注册步骤还是非常简单的,参数正确,就可以写入用户表,注册成功,然后跳转。
登录逻辑
class LoginController extends Controller
{
use AuthenticatesUsers;
protected $redirectTo = '/home';//登录成功跳转路径
public function __construct()
{
$this->middleware('guest')->except('logout');//添加guest中间件,除了logout方法
}
protected function guard()//这个方法trait也有,但是如果我们用其他的guard,就要重写方法
{
return Auth::guard('api');//你要使用的guard
}
}
登录接口laravel也帮我们实现了,直接use AuthenticatesUsers.我们看看
public function login(Request $request)
{
$this->validateLogin($request);//验证参数
if ($this->hasTooManyLoginAttempts($request)) {//登录失败次数,超过次数不能再登陆
$this->fireLockoutEvent($request);
return $this->sendLockoutResponse($request);
}
if ($this->attemptLogin($request)) { //比对数据库,看看登陆是否成功
return $this->sendLoginResponse($request);
}
$this->incrementLoginAttempts($request);//增加登陆失败次数,以$this->username())),$request->ip()为基准,就是说同一个username,同一个ip登陆失败次数是有限的。时间是1h.
return $this->sendFailedLoginResponse($request);
}
我们主要看他如何查用户表,如何判断登录是否成功
protected function attemptLogin(Request $request)
{
return $this->guard()->attempt(
$this->credentials($request), $request->filled('remember')
);
}
和注册一样,也是获取到对应的guard配置的Auth,执行attempt方法来验证,所以不同的guard就要重写guard方法,才能配置Auth.
Auth门面介绍
其实操作的类是Illuminate\Auth\AuthManager,而最终功能其实是对应的driver类实现的。
Auth::guard('web'),返回一个对象,就是那个driver对象
public function guard($name = null)
{
$name = $name ?: $this->getDefaultDriver();
return $this->guards[$name] ?? $this->guards[$name] = $this->resolve($name);
}
protected function resolve($name)
{
$config = $this->getConfig($name);
if (is_null($config)) {
throw new InvalidArgumentException("Auth guard [{$name}] is not defined.");
}
if (isset($this->customCreators[$config['driver']])) {
return $this->callCustomCreator($name, $config);
}
$driverMethod = 'create'.ucfirst($config['driver']).'Driver';
if (method_exists($this, $driverMethod)) {
return $this->{$driverMethod}($name, $config);
}
throw new InvalidArgumentException(
"Auth driver [{$config['driver']}] for guard [{$name}] is not defined."
);
}
需要说明的,laravel给我们配置了三种guard的driver,分别是session,token,request,都放在Illuminate\Auth目录下,先看一下SessionGuard
public function __construct($name,UserProvider $provider,Session $session,Request rquest = null)
{
$this->name = $name;
$this->session = $session;//操作session的工具
$this->request = $request;//request
$this->provider = $provider;//就是我们的provider模型
}
有了这三个工具,我们就可以实现auth的各种功能了。
比如登陆attempt()
public function attempt(array $credentials = [], $remember = false)
{
$this->fireAttemptEvent($credentials, $remember);
//用除了密码查询provider表,得到$user。
$this->lastAttempted = $user = $this->provider->retrieveByCredentials($credentials);
//对比密码是否正确
if ($this->hasValidCredentials($user, $credentials)) {//严重
$this->login($user, $remember);//成功就执行login方法
return true;
}
$this->fireFailedEvent($user, $credentials);
return false;
}
比如获取登陆的客户模型Auth::user();
public function user()
{
if ($this->loggedOut) {
return;
}
if (! is_null($this->user)) {
return $this->user;
}
$id = $this->session->get($this->getName());//通过session获取id
if (! is_null($id) && $this->user = $this->provider->retrieveById($id)) {
$this->fireAuthenticatedEvent($this->user);
}
if (is_null($this->user) && ! is_null($recaller = $this->recaller())) {
$this->user = $this->userFromRecaller($recaller);
if ($this->user) {
$this->updateSession($this->user->getAuthIdentifier());
$this->fireLoginEvent($this->user, true);
}
}
return $this->user;
}
在看看TokenGuard,就是登陆成功会给前端保存一个token,每次请求要带这个token
Auth::user();
public function user()
{
// If we've already retrieved the user for the current request we can just
// return it back immediately. We do not want to fetch the user data on
// every call to this method because that would be tremendously slow.
if (! is_null($this->user)) {
return $this->user;
}
$user = null;
$token = $this->getTokenForRequest();//从request得到token值
if (! empty($token)) {
$user = $this->provider->retrieveByCredentials([
$this->storageKey => $this->hash ? hash('sha256', $token) : $token,
]);
}
return $this->user = $user;
}
$this->storageKey 默认是"api_token",所以查询provider表的条件是['api_token'=>$token],我们的用户表需要一个api_token字段保存登陆的token值。
Auth中间件验证登陆
我们知道,要让接口通过登陆验证才能访问,需要在添加auth中间件,这个laravel已经配置好了
protected $routeMiddleware = [
'auth' => \App\Http\Middleware\Authenticate::class,//系统生成的前台登陆验证
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
'can' => \Illuminate\Auth\Middleware\Authorize::class,
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
'auth.admin' => \App\Http\Middleware\AdminAuthMiddleware::class,// 后台登陆验证
];
这个Authenticate中间件继承了Illuminate\Auth\Middleware\Authenticate类
看它的handle方法
public function handle($request, Closure $next, ...$guards)
{
$this->authenticate($request, $guards);
return $next($request);
}
protected function authenticate($request, array $guards)
{
if (empty($guards)) {
$guards = [null];
}
foreach ($guards as $guard) {
if ($this->auth->guard($guard)->check()) { //Auth::check方法验证
return $this->auth->shouldUse($guard);
}
}
throw new AuthenticationException(
'Unauthenticated.', $guards, $this->redirectTo($request)
);
}
自定义后台验证如何配置验证中间件
比如我们建了一套后台的登陆验证系统,使用的guards是admin,我们的后台路由就需要一个中间件来验证登陆了
php artisan make:middleware AdminAuth
把中间件写入到路由中间件数组中
protected $routeMiddleware = [
'auth' => \App\Http\Middleware\Authenticate::class,//系统生成的前台登陆验证
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
'can' => \Illuminate\Auth\Middleware\Authorize::class,
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
'auth.admin' => \App\Http\Middleware\AdminAuthMiddleware::class,// 后台登陆验证
];
中间件的过滤代码
public function handle($request, Closure $next, $guard = null)
{
//当 auth 中间件判定某个用户未认证,会返回一个 JSON 401 响应,或者,如果是 Ajax 请求的话,将用户重定向到 login 命名路由(也就是登录页面)。
if (Auth::guard($guard)->guest()) {
if ($request->ajax() || $request->wantsJson()) {
return response('Unauthorized.', 401);
} else {
return redirect()->guest('admin/login');
}
}
return $next($request);
}
这里中间件需要传一个参数$guard,不传就是web,所以说中间件的guard要和我们的登陆,注册等系列接口要保持一致。假如我们创建了一个admin的guard,路由就应该这样写
// 后台其他页面都要有登陆验证
Route::middleware('auth.admin:admin')->name('admin.')->group(function () {
//后台的其他路由
}
本作品采用《CC 协议》,转载必须注明作者和本文链接
用过哪些工具?为啥用这个工具(速度快,支持高并发...)?底层如何实现的?