Laravel 用户认证 Auth

很多应用是需要登陆后才能操作,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 协议》,转载必须注明作者和本文链接
走出舒适区
本帖由系统于 3年前 自动加精
《L05 电商实战》
从零开发一个电商项目,功能包括电商后台、商品 & SKU 管理、购物车、订单管理、支付宝支付、微信支付、订单退款流程、优惠券等
《G01 Go 实战入门》
从零开始带你一步步开发一个 Go 博客项目,让你在最短的时间内学会使用 Go 进行编码。项目结构很大程度上参考了 Laravel。
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!