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 () {

        //后台的其他路由

    }



  • 2017-02-10 15:24:14

    linux学习之——vim简明教程

    学习 vim 并且其会成为你最后一个使用的文本编辑器。没有比这个更好的文本编辑器了,非常地难学,但是却不可思议地好用。 我建议下面这四个步骤: 存活 感觉良好 觉得更好,更强,更快 使用VIM的超能力

  • 2017-02-10 16:22:13

    git历史记录查询

    查看提交历史:git log 查看提交历史并显示版本间的差异:git log -p 查看指定历史:git log xxx(sha1值) -p 查看提交历史(指定时间):

  • 2017-02-13 17:50:05

    cURL error 60: SSL certificate problem: unable to get local issuer certificate

    Drupal 8 version uses Guzzle Http Client internally, but under the hood it may use cURL or PHP internals. If you installed PHP cURL on your PHP server it typically uses cURL and you may see an exception with error Peer certificate cannot be authenticated with known CA certificates or error code CURLE_SSL_CACERT (60).

  • 2017-02-16 08:09:01

    HTML中PRE和p的区别

    pre 元素可定义预格式化的文本。被包围在 pre 元素中的文本通常会保留空格和换行符。而文本也会呈现为等宽字体。 <pre> 标签的一个常见应用就是用来表示计算机的源代码。