Laravel 6 – 从基本用户认证深入理解Laravel

Laravel 6 – 从基本用户认证深入理解Laravel

启用Laravel的基本用户认证模块后,就会自动生成几个用户注册/登录/密码找回的页面。我们就来从这几个页面深入理解Laravel的用法。

AUTH路由图

1. 找到路由

这几个路由都由/routes/web.php文件中新增的 Auth::routes(); 这一行注册的。

1.1 Facade

这个用法涉及到了Laravel的facade概念,facade是指用看起来像调用类的静态方法的语法(类名::静态方法)调用容器中对应实例的同名公有方法的一种用法。

我们可以在/config/app.php的aliases数组中找到这个Auth别名,该别名指向实际的facade类 'Auth' => Illuminate\Support\Facades\Auth::class,

所以在路由/routes/web.php中的 Auth::routes()实际上就是 Illuminate\Support\Facades\Auth::routes()

然后我们再去看这个…\Facades\Auth的代码。

其中getFacadeAccessor()方法是所有Facade派生类都必须重写的,并且都要返回一个字符串,该字符串指向容器中的一个绑定名。这样就可以通过这个facade来找到容器中绑定的对应实例了。

所以这里我们就要去容器中找名为”auth”的绑定。OK,那么如何找到容器中的绑定关系呢?这个其实挺麻烦的,因为/config/app.php文件中的providers数组只是定义了ServiceProvider类,所有的绑定都是在ServiceProvider类的register()方法中进行的。最简单粗暴的办法就是直接在代码编辑器中全局查找内容 singleton('auth 或者 bind('auth 。还有一种办法是写一个测试页面通过 dd(app()); 打印出整个容器。总之,我们可以在Illuminate\Auth\AuthServiceProvider.php的register()方法中找到这个”auth”其实绑定的是一个Illuminate\Auth\AuthManager类的共享实例(单例)。

然后由于Facade基类重写了php魔术方法 __callStatic() ,当系统找不到 Facade::method_name()这个静态方法时,就会自动调用 __callStatic() 找到在getFacadeAccessor()中关联的实例,并调用其同名公有方法(非静态)。这就是facade的实现原理。

不过实际上由于…\Facades\Auth类中已经定义了routes()静态方法,所以会直接调用它。不会再通过__callStatic()去AuthManager实例那找了(况且AuthManager也没定义routes()方法 ^_^)。

1.2 Router

所以真实的路由,我们可以在Illuminate\Routing\Router类的auth方法中找到。

从上面的代码看出我们可以通过在/routes/web.php中给 Auth::routes() 添加参数来关闭,开启某些路由。比如通过 Auth::routes(['register'=>false])来关闭注册页面的路由,通过 Auth::routes(['verify'=>true])来开启邮箱验证路由(这还要配合开启注册时候的邮箱验证来使用)。

2. 中间件

中间件提供了一种方便的机制过滤进入应用程序的 HTTP 请求。中间件是HTTP请求经过路由分发后的第一道槛。在“AUTH路由图”中我们可以看到所有的AUTH路由都关联了web中间件,还有部分关联guest,auth中间件。

2.1 定义中间件

中间件的定义文件默认都放在 app/Http/Middleware 目录下。

2.2 注册中间件

定义了中间件的类之后,还需通过 app/Http/Kernel.php 文件向系统注册中间件,告诉系统有哪些中间件,叫什么名字。

$middleware : 全局中间件数组,所有的HTTP请求都要经过这里的中间件。(不需要定义名字)

$routeMiddleware : 注册[中间件名 => 中间件类]的对应关系。(auth中间件、guest中间件就在这)

$middlewareGroups : 注册[中间件组名 => [中间件组] ]的对应关系。(web中间件组就在这)

如果想要为指定的路由分配中间件,那么就必须先注册中间件名或中间件组名。

2.3 关联中间件

定义并注册了中间件之后,就要把中间件分配到路由上了。

a. web中间件组是如何关联到/routes/web.php文件中的所有路由的?

App\Providers\RouteServiceProvider@mapWebRoutes() 方法中,加载/routes/web.php文件的同时就将web中间件组分配到所有的web路由组。

这几行代码看着简单,其实底层涉及很多概念,所以必须要好好说道说道了(ง •̀_•́)ง

首先是 Route::static_method() 这样的facade用法,它实际上调用的是容器中名为”router”的 \Illuminate\Routing\Router 单例绑定。(在 Illuminate\Routing\RoutingServiceProvider 中注册的)

然后,我们会发现在 \Illuminate\Routing\Router 类中根本找不到 middleware()namespace() 方法。而且它的 prefix() 方法是protected的,也不能通过 Route::prefix() 这种facade方式来调用。

实际上, \Illuminate\Routing\Router 实现了php的魔术方法 __call($method, $parameters) 。当系统找不到 $router->method() 时就会自动调用 $router->__call($method, $parameters)

所以 Route::middleware()Route::prefix()Route::namespace() 最终调用的都是 Illuminate\Routing\RouteRegistrar->attribute($method, $parameters) 方法。它负责将$method, $parameters转换成[属性 => 值]的形式写入自己的$attributes数组中,并返回RouteRegistrar实例本身。

由于方法返回实例本身,所以就能方便地使用php链式调用(Method Chaining)语法了: $instance->method()->another_method();

最后, Illuminate\Routing\RouteRegistrar->group() 就会调用 Illuminate\Routing\Router->group() 方法,加载路由文件(或是定义路由的闭包函数),为每个路由生成 Illuminate\Routing\Route 实例并关联上这组路由的属性(即middleware、prefix、namespace这些)。

所有的 Illuminate\Routing\Route 实例都会被放到 app('router') 这个共享实例的$routes属性中, Illuminate\Routing\Router->routes 属性是一个 Illuminate\Routing\RouteCollection 类型的实例。

b. 使用 Controller->middleware() 关联中间件

控制器中间件是指在控制器的构造函数中使用 middleware() 方法将中间件分配给该控制器。

/register路由的guest中间件就是在RegisterController控制器中定义的:

c. 使用 Route->middleware() 关联中间件

就是在路由文件中定义路由时候使用 ->middleware() 来直接关联中间件。

3. Cookie与Session

抛开那几个全局中间件不谈,我们来看一下web中间件组中最重要的两个中间件: EncryptCookies与StartSession。

3.1 Cookie

Laravel框架生成的cookie都是自动加密解密的。这个加解密就是由EncryptCookies中间件执行的。(可以在 App\Http\Middleware\EncryptCookies@except 属性中指定不加密的cookie)

来看一下EncryptCookies中间件的handle方法:

首先注意它这里的return并不是像中间件规范那样的 return $next($request); ,其实$next就是指向下一个中间件的handle()方法。所以常规的 return $next($request); 就是调用下一个中间件并返回,而在EncryptCookies这里是:

  1. 先调用 decrypt($request) 将请求中的cookies解密,
  2. 然后通过 $next($request) 调用后续的中间件继续处理请求,(next到最后一个中间件后,就会调用控制器方法,返回 Response )
  3. 最后调用 encrypt($response) 对上一步返回的$response中的cookies进行加密,并返回该$response。

EncryptCookies的加密器是在构造方法中由Laravel容器自动依赖注入的 Illuminate\Encryption\Encrypter 绑定实例(这个绑定是在 Illuminate\Encryption\EncryptionServiceProvider 中注册的)。

 

所以对于编写控制器而言,所有的cookie都是明文的,我们不需要去考虑cookie的加密解密,这些都由EncryptCookies中间件帮我们处理好了。在接收请求时自动解密,在返回响应时候自动加密。

3.2 Session

首先要提一下Laravel中Session的基本实现框架:

  • session manager

Illuminate\Session\SessionManager 类型,它负责创建session driver。根据不同的driver存储类型,调用对应的 createArrayDriver()createFileDriver()createDatabaseDriver() 等。默认的driver存储类型由配置文件中的config(‘session.driver’)指定。

一个manager可以创建多个driver(理论如此,实际上我们一般只用一种session driver)

  • session driver

Illuminate\Session\Store 或 Illuminate\Session\EncryptedStore 类型,这由config(‘session.encrypt’)配置指定。

它负责用户对session的直接操作,即 get() , save() 这些。而实际的底层读写操作由handler执行。

一个driver只能包含一个handler。

  • session driver handler

它是driver在底层存储方面的实现,driver的不同存储类型实际就是对应着不同的handler。比如 FileSessionHandlerDatabaseSessionHandler 等等。它们都要包含在存储层对session的read操作、write操作、destroy操作(删除指定session)以及gc操作(清除过期session)。

SessionManager在创建driver的时候,实际上是先根据存储类型创建对应的handler,然后再将handler传递给driver的构造方法。 Illuminate\Session\Store 类在构造实例时候需要至少传递两个实参: $name与$handler( EncryptedStore还要多一个$encrypter)。其中$name实参表示的是cookies中用来存储session_id的键名,由config(‘session.cookie’)配置指定。(默认名为’laravel_session’)。

—————————————————————————————–

现在再来看一下StartSession中间件的主要代码:

其中, tap($obj, $closure) 辅助函数的意思是执行$closure($obj),然后返回$obj。

所以 getSession($request) 方法的逻辑就是:

(1) 通过 $this->manager->driver() 获取一个 Illuminate\Session\Store 实例,即所谓的session driver。

由于该调用不带实参,所以获取的是默认driver(默认driver类型由config(‘session.driver’)指定)。

如果此时manager中还未生成该driver,则自动调用 createDriver() 方法生成一个Store实例。

对于新生成的Store实例,其id是随机的,其name就是cookies中存储session_id的键名(由config(‘session.cookie’)指定)

 (2) 在闭包函数中:

首先通过 $request->cookies->get($session->getName()) 从$request请求的cookies中获取session_id的值(别担心,EncryptCookies中间件已经自动解密过了)。

然后调用$session->setId()重新设置这个Store实例的id为cookies[seesion_id]。(如果cookies[session_id]不存在或异常,则保留原来的随机id)

(3) 最后返回这个$session实例。

然后 startSession($request) 方法的逻辑就是:

(1) 通过 $this->getSession($request) 新建一个$session实例。

(2) 通过闭包函数中的 $session->start() 启动这个$session实例。

所谓的启动,就是调用handler从底层存储中读取id为$session->id的数据出来,放到$session->attributes数组中。(对于file handler来说,id就是对应的文件名)

如果在存储中找不到该id的数据,或者数据已经过期(对于file handler来说,就是session文件的修改时间已经超过config(‘session.lifetime’)未更新),那么就返回空数组给$session->attributes。

另外,如果此时$session->attributes[‘_token’]不存在的话,则新建一个。这个$session->attributes[‘_token’]就是要用来做csrf验证的csrf_token。

(3) 返回这个$session实例。

3.3 重点

  1. 所有的cookie都是由 EncryptCookies 中间件自动加密解密的。收到$request时自动解密cookies,返回$response自动加密cookies。
  2. cookie与session的关联是:$request->cookie(‘session_id’) == $session->id
  3. 将$session->id写回$response->headers的cookie中,以及将$session->attributes保存到存储中。这两个操作都是在控制器方法返回$response,执行流程重新回到 StartSession 中间件后才执行的!

4. /register

现在来看一个/register页面是怎么工作的。/register包括两个路由,一个是接收GET请求展示注册页面,一个是接收POST请求处理注册信息。

相应于GET请求的 RegisterController->showRegistrationForm() 方法就是简单的返回auth.register视图,暂且不表。来看看关键的 RegisterController->register() 方法是如何处理用户POST过来的注册信息的。

4.1 validation

首先来看 $this->validator($request->all())->validate();$request->all()返回当前请求的所有输入信息。然后 $this->validator() 根据用户输入构造一个验证器实例。

这里的 Validator::make() 也是一个facade用法,实际上调用的是容器中名为”validator”的绑定实例的 make() 方法。然后我们再来看一下这个“validator”是如何绑定的。

首先这个 new Factory() 会自动加载到同命名空间的Illuminate\Validation\Factory类,然后创建一个单例绑定。

其中$app[‘translator’]是容器中绑定的Illuminate\Translation\Translator单例,负责输出信息的本地化(它会加载 $app['config']['app.locale'] 配置)。

还通过’validation.presence’绑定名关联了一个DatabasePresenceVerifier单例,这是一个用来检查数据库中唯一性的验证器(比如验证用户的email在数据库中已存在),这里就不再赘述,参考唯一性验证(6的文档还不如version 5)。

现在我们通过这个Validator facade得到的其实是一个工厂类,要真正得到一个可用的验证器,就得看看它的 make() 方法:

这样子,在RegisterController@register()方法第一行 $this->validator($request->all()) 就得到了一个验证器实例。然后调用该验证器实例的 validate() 方法,解析验证规则并对每个验证规则调用对应的验证方法 ValidatesAttributes@validateXXX()

当验证失败时候会自动抛出一个“验证异常” throw new ValidationException($this);

4.2 处理验证异常

我现在比较关心“验证异常”是如何返回页面与错误信息的。

Laravel中的所有错误与异常都会被/app/Exceptions/Handler.php处理(官方文档)(另外,关于php错误与异常的区别请自行搜索)。

这是因为Laravel核心(Illuminate\Foundation\Http\Kernel)在启动时就会通过$bootstrappers中的HandleExceptions@bootstrap()注册全局的错误处理函数与异常处理函数:

这样之后,发生的任何错误和异常最终都会被转到这个 handleException() ,而在 handleException() 中会调用 app(ExceptionHandler::class) 这个绑定实例的 render($request, Exception $e) 方法来处理异常并渲染成网页。这个绑定我们可以在\bootstrap\app.php文件中找到,它绑定是的App\Exception\Handler类的单例:

那么对于上述抛出的“验证异常”,它的异常处理就会走到 Illuminate\Foundation\Exceptions\Handler->render($request, ValidationException($validator)) ,最后再由 Handler->invalid($request, $exception) 返回一个重定向响应,重定向回原来的/register页面,并且将错误信息写入到session供页面渲染。

4.3 csrf保护

现在我们再来看一下POST /register时候的csrf保护。

实际上csrf保护是由 \App\Http\Middleware\VerifyCsrfToken 中间件提供的,它应该在/register路由进入控制器方法之前就被调用了。

VerifyCsrfToken属于’web’中间件组,它会处理/routes/web.php中定义的所有路由。不过对于GET/HEAD/OPTIONS这三种’read’类型的HTTP请求它不会进行csrf验证。

在调用 $this->tokensMatch($request) 方法验证$request中的csrf_token是否与$session[‘_token’]一致的时候,它首先需要通过 getTokenFromRequest($request) 方法从HTTP请求中获取csrf_token。 getTokenFromRequest($request) 会按顺序尝试从$request的三个地方获取csrf_token:

1、$request->input(‘_token’) 

这是通过表单POST提交时带的csrf_token。只要你在前端页面的表单中添加 @csrf Blade指令即可:

它最终会被渲染成:

2、$request->header(‘X-CSRF-TOKEN’) 

Larvel默认会将csrf_token放在HTML的<meta>标签中。如下所示:

所以当你在前端页面手动ajax发起请求时,就可以将该meta值放到X-CSRF-TOKEN请求头中。(当然你要非得用ajax.post[‘_token’]来做也是可以)

3、$request->header(‘X-XSRF-TOKEN’) 

Laravel默认将csrf_token存放在cookies[‘XSRF-TOKEN’]中,某些前端库(比如Axios)在发送请求时候会自动将cookies[‘XSRF-TOKEN’]值写入X-XSRF-TOKEN请求头中。

需要注意的是,默认的cookies[‘XSRF-TOKEN’]是加密的,所以 VerifyCsrfToken->getTokenFromRequest()方法中会强制对$request->header(‘X-XSRF-TOKEN’)进行解密,如果你手欠把cookies[‘XSRF-TOKEN’]放到加密排除数组中( EncryptCookies->except),那么对不起这会触发解密时异常DecryptException。

最后,如果csrf验证失败就会直接抛出异常。Laravel中所有异常与错误都会由 App\Exceptions\Handler 捕获,并调用 render($request, Exception $exception) 返回错误页面。

需要注意的是,抛出异常后,排队在VerifyCsrfToken之后的中间件以及控制器方法就不会被调用了,因为此时控制权已经交给了Handler。不过Handler最后还是要返回一个表示错误页面的$response,这个$response会被返回给VerifyCsrfToken之前的中间件,比如StartSession、EncryptCookies,最后才由$kernel调用$response->send()将错误页面发送给用户。

4.4 创建新用户

用户的POST /register请求经过一系列中间件(EncryptCookies、StartSession、VerifyCsrfToken等等)、再经过控制器方法 RegisterController->register($request) 中的表单验证之后,就来到了新建用户的步骤: $this->create($request->all()) 。它会调用 APP\User::create() 方法新建一个 User类实例,将它insert到数据库中并返回。

我们来看一下create()的实现,由于User类并没定义create()静态方法,那么会找到父类 Illuminate\Database\Eloquent\Model ,而Model类也没有定义create()静态方法,只是定义了 __callStatic()__call() 。

这里涉及到一个php高级语法: 后期静态绑定。在 __callStatic() 中的static在此时指向User类, new static 等同于 new User() 。所以到这就会去尝试找 User->create() 方法,然后就进到 __call() 方法中去了,去调用 $this->forwardCallTo($this->newQuery(), 'create', $parameters); 。根据 forwardCallTo() 方法以及 newQuery() 方法的定义,最后调用的实际是 Illuminate\Database\Eloquent\Builder->create($parameter) 。另外,参数前的三个点是php的可变数量参数列表用法。

4.5 触发用户注册事件

创建完新用户后, RegisterController->register() 方法会调用 event(new Registered($user); 触发一个“用户注册”事件。所有的“事件”=>“监听器”的关联都在 App\Providers\EventServiceProvider 中定义,一个事件可以关联多个监听器(当然一个监听器也可以监听多个事件)。

EventServiceProvider中定义了事件与监听器的关系,然后全局辅助函数 event() 负责将事件分发给监听器并调用监听器的 handle() 方法。

“事件—监听器”与“任务—队列”的区别

注意:事件(event)是立即分发给监听器(listener)进行处理的,要把它们跟任务(job)—队列(queue)的关系区别清楚。以上面的发送注册邮件为例,将它改造成任务队列形式的话:

  1. 首先是新建一个任务类 App\Jobs\SendEmail ,任务类其实有点像包含了 事件+监听器 的功能,因为它的构造方法跟事件类一样,接受要处理的信息。它的handle()方法跟监听器一样负责处理信息。
  2. 【控制器方法】在用户注册时,调用全局辅助函数 dispatch(new SendEmail($user)) ,将一个新的任务实例压入任务队列(队列可以是数据库、redis等,这在/config/queue.php配置文件中定义),等待被异步处理。
    有一个有意思的地方, dispatch() 方法返回一个 PendingDispatch 实例,该实例包含任务以及任务属性(放在哪个队列,延迟多久执行等)。但 dispatch() 后并没有再调用该实例了,那任务是怎么被压入任务队列呢?这其实是在该实例的析构方法中自动执行的=。=#
  3. 【控制器方法】然后控制器继续执行后续操作直到返回HTTP响应给用户。
  4. 【命令行】执行 php artisan queue:work 命令启动队列处理器(queue worker)。由这个queue worker从队列中取出任务并执行任务( 调用任务handle()方法)。

4.6 注册后自动登录

现在再来看一下用户注册成功后 $this->guard()->login($user); 这句做了什么。(这句话等同于 Auth::login($user) )

首先 $this->guard()通过 Auth::guard() 这个facade来调用 Illuminate\Auth\AuthManager->guard()。该方法返回一个 Illuminate\Auth\SessionGuard 实例(因为在/config/auth.php中定义的默认guard是’web’ guard,它的driver是session driver,它的user provider是 EloquentUserProvider )。

这里再讲两个php的魔术方法__get()与__set()。  在Laravel中我们可以用Model->id, Model->name直接访问Model->attributes[‘id’]与Model->attributes[‘name’],就是因为Model类实现了这两个魔术方法。

4.7 注册成功后重定向

return $this->registered($request, $user) ?: redirect($this->redirectPath()); 没啥好说的了,就是注册成功后的重定向。

可以通过修改 $this->registered() 执行额外操作并返回自定义的重定向地址,或者修改 $this->redirectTo 属性指定重定向地址。

5. /login

Laravel用户认证的核心是guard与user provider:

  • guard 定义如何对每个HTTP请求进行用户身份验证。(比如SessionGuard就会从sessions[‘login_xxx’]与cookies[‘remember_xxx’]中尝试读取用户信息,然后与user provider读取的用户信息进行对比验证)
  • user provider 定义guard如何从存储中读取真正的用户信息。
  • 每个guard必须指定一个user provider。
  • 可以定义多个guard,容器中所有guard都放在app(‘auth’)->guards[]数组中。(‘auth’绑定一个AuthManager单例)

与/register一样,用户登录同样包含两个路由:GET /login与POST /login。

5.1 guest中间件

这两个路由都经过guest中间件,我们可以在/app/Http/Kernel.php文件中找到该中间件的定义: 'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,

这个中间件还接收一个$guard实参表示用哪一个guard驱动来进行用户认证,同auth中间件一样。可以通过如下方式调用:

我们来看一下guard的check()方法。 Auth::guard(null) 返回一个 Illuminate\Auth\SessionGuard实例。

Auth::check()其实要判断四种状态:

  • 在当前请求的中间件链中,用户信息已存在于guard中,表示已登录。
  • 在当前请求的session中,存在sessions[‘login_xxx’]并且能从数据库中取出该用户信息,则登录用户并返回。
  • 在当前请求的cookie中,存在cookies[‘remember_xxx’]并且能从数据库取出该用户信息,则登录用户并返回。
  • 上述三种情况都不存在,则表示用户未登录。

5.2 LoginController->login()方法

5.3 auth中间件

用户登录成功后会自动重定向到/home页面,这是一个由’auth’中间件保护的路由,确保用户有权限访问该路由。

6. 用户登出

6.1 POST /logout路由

来看一下这个 $this->guard()->logout() 方法具体做了啥。这实际上引用的是SessionGuard实例

 

6.2 登出当前设备 SessionGuard->logoutCurrentDevice()

SessionGuard的logout()方法会刷新用户表的remember_token,这会导致登出所有设备,这点一直是被吐槽的。直到2019年8月的时候,才添加了一个 logoutCurrentDevice() 方法,只登出当前设备,不刷新remember_token。(但是因为这个函数是后面才添加的,官方文档6.x,7.x里甚至都还没提及。失望(´・_・`))

 

6.3 登出其他设备 Auth::logoutOtherDevices()

必须启用 AuthenticateSession中间件,它会在session中维护一个’password_hash’键值。

Auth::logoutOtherDevices() 实际调用的是 Illuminate\Auth\SessionGuard->logoutOtherDevices($password, $attribute = 'password')

但是要想让logoutOtherDevices生效,还必须在/app/Http/Kernel.php文件取消对web中间件组中的 \Illuminate\Session\Middleware\AuthenticateSession::class 中间件的注释,启动该中间件。

7. 记住登录

我们来总结一下记住登录是如何实现的:

1、用户在浏览器A勾选“记住登录”登录时

将登录用户的id写入 sessions['login_{guard名}_{guard类型名的哈希值}'] 后。检查用户模型的remember_token属性是否有值,没有的话就自动生成该值(60位随机数),有的话就用原来的。然后将该值写入 cookies['remember_{guard名}_{guard类型名的哈希值}'] ,这就是“记住登录”的cookie,它的值应该等于:{用户id}|{remember_token值}|{用户密码密文}

2、用户在浏览器B构造“记住登录”登录时候

同上面一样,不过这时候就直接用数据库中已生成的remember_token值就好了。并写回cookies。(所以同一个用户,不管他在哪里“Remember Me”登录,他的remember_token都是一样的。)

3、用户在浏览器A再次访问网站时

只要用户访问的路由有经过’auth’或’guest’中间件,它们都会自动调用 Illuminate\Auth\Middleware\Authenticate->authenticate()方法尝试从session或者cookie中自动登录用户(底层调用 SessionGuard->user() ):

如果session还没过期,就尝试从sessions[‘login_xxx’]获取用户id。如果数据库中存在该id的用户,就自动登录该用户。

如果用户已经很久没访问网站,session已过期。那么尝试读取cookies[‘remember_xxx’],并用其中的id与remember_token去与数据库中该用户对比,如果一致则自动登录该用户。

如果开启了’web’组的 AuthenticateSession 中间件,那么还会尝试读取sessions[‘password_hash’]或cookies[‘remember_xxx’]中的password_hash,将之与用户的密码密文比较,如果不一致则强制登出用户。这样只要用户一更新密码密文,其他设备的已登录状态就会失效。

4、用户在浏览器A退出登录

guard->logout()负责删除sessions[‘login_xxx’],删除cookies[‘remember_xxx’],并刷新数据库中的remember_token。

这就导致一个缺陷。只要浏览器B的session失效后,由于它的cookies[‘remember_xxx’]还是旧的,那么它就无法再依靠cookie自动登录了,也就是说它的“记住登录”失效了!

SessionGuard->logoutCurrentDevice() 提供了只登出当前设备,不刷新remember_token的功能)

5、用户在浏览器A修改密码

Laravel默认是没有开启‘web’组中 AuthenticateSession中间件的。而如第3点中说讲,SessionGuard->user()自动登录用户时只需要用到session中的用户id或cookies[‘remember_xxx’]中的id与remember_token,不涉及到用户密码。这样就无法在用户修改密码后自动踢出其他设备的登录状态。

所以要想让用户在浏览器A修改密码后自动登出其他设备,就必须启用 AuthenticateSession中间件。

 

 

发表评论

电子邮件地址不会被公开。 必填项已用*标注