Laravel 用户认证 Auth(精华)

2020-02-19 23:08:56

参考地址 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 () {

        //后台的其他路由

    }



  • 2018-03-05 11:30:04

    iOS wkwebkit 播放HTML5 视频 全屏问题解决

    使用html5 的video标签播放视频的时候,限制视频的尺寸,在android上是没有问题的,但是在ios上发现,视频没有开始播放的时候还是好的,但是一旦播放开是,就会全屏,非常奇怪。

  • 2018-03-07 14:35:32

    centos7下yum安装ffmpeg

    安装EPEL Release,因为安装需要使用其他的repo源,所以需要EPEL支持 yum install -y epel-release

  • 2018-03-08 09:44:12

    前端性能监控:window.performance

    Web Performance API允许网页访问某些函数来测量网页和Web应用程序的性能,包括 Navigation Timing API和高分辨率时间数据。

  • 2018-03-08 09:44:15

    前端性能监控:window.performance

    Web Performance API允许网页访问某些函数来测量网页和Web应用程序的性能,包括 Navigation Timing API和高分辨率时间数据。

  • 2018-03-08 09:47:14

    ES6,Array.fill()函数的用法

    ES6为Array增加了fill()函数,使用制定的元素填充数组,其实就是用默认内容初始化数组。

  • 2018-03-08 09:53:39

    document.readyState

    一个document 的 Document.readyState 属性描述了文档的加载状态。

  • 2018-03-09 02:09:23

    ArrayBuffer:类型化数组

    ArrayBuffer对象、TypedArray对象、DataView对象是JavaScript操作二进制数据的一个接口。这些对象早就存在,属于独立的规格,ES6将它们纳入了ECMAScript规格,并且增加了新的方法。

  • 2018-03-09 11:45:11

    SQL SELECT DISTINCT 语句

    如需从 Company" 列中仅选取唯一不同的值,我们需要使用 SELECT DISTINCT 语句: