【问题标题】:Laravel authentication without global scope没有全局范围的 Laravel 身份验证
【发布时间】:2016-11-11 00:01:57
【问题描述】:

在我的 Laravel 应用程序中,用户可以禁用(而不是删除)他们的帐户以从网站上消失。但是,如果他们再次尝试登录,他们的帐户应该会自动激活,并且应该会成功登录。

这是通过用户表中的“活动”列和用户模型中的全局范围来完成的:

protected static function boot() {
    parent::boot();

    static::addGlobalScope('active', function(Builder $builder) {
        $builder->where('active', 1);
    });
}

现在的问题是那些不活跃的帐户无法再次登录,因为 AuthController 没有找到它们(超出范围)。

我需要达到的目标:

  1. 使 AuthController 忽略全局范围“活动”。
  2. 如果用户名和密码正确,则将“活动”列值更改为“1”。

我现在的想法是找到使用 withoutGlobalScope 的用户,手动验证密码,将“活动”列更改为 1,然后进行常规登录。

在我的 AuthController 中的 postLogin 方法中:

$user = User::withoutGlobalScope('active')
            ->where('username', $request->username)
            ->first();

if($user != null) {
    if (Hash::check($request->username, $user->password))
    {
        // Set active column to 1
    }
}

return $this->login($request);

那么问题是如何让 AuthController 在不改变 Laravel 主代码的情况下忽略全局作用域,从而保持更新?

谢谢。

【问题讨论】:

标签: laravel authentication


【解决方案1】:

创建一个扩展 EloquentUserProvider 的类 GlobalUserProvider,如下所示

class GlobalUserProvider extends EloquentUserProvider {

    public function createModel() {
         $model = parent::createModel();
         return $model->withoutGlobalScope('active');
    }

}

AuthServiceProvider注册您的新用户提供商:

Auth::provider('globalUserProvider', function ($app, array $config) {
     return new GlobalUserProvider($this->app->make('hash'), $config['model']);
});

最后,您应该在auth.php 配置文件中将您的用户提供程序驱动程序更改为 globalUserProvider。

 'providers' => [
    'users' => [
        'driver' => 'globalUserProvider',
        'model' => App\Models\User::class
    ]
 ]

【讨论】:

  • 这不起作用。 $model->withoutGlobalScope('active'); 失败,因为 parent::createModel(); 返回一个用户对象。不过,withoutGlobalScope 必须在查询中调用。
  • 非常适合我 - 当全局范围应用于用户模型时帮助避免 Symfony\Component\Debug\Exception\FatalThrowableError: Maximum function nesting level of '256' reached, aborting! 错误,该模型正在检查用户是否已通过身份验证(从而再次启动用户)。
  • 对,你必须重写这个函数而不是 newModelQuery()
【解决方案2】:
protected static function boot() 
{
    parent::boot();
    if (\Auth::check()) {
        static::addGlobalScope('active', function(Builder $builder) {
            $builder->where('active', 1);
        });
    }
}

请尝试此登录问题,您可以使用withoutGlobalScopes()登录后激活。

【讨论】:

  • auth 中间件会自动注销用户,所以这是一个不好的解决方案
【解决方案3】:

@Sasan 的答案在 Laravel 5.3 中运行良好,但在 5.4 中不起作用 - createModel() 期待 Model 但得到 Builder 对象,因此当 EloquentUserProvider 调用 $model->getAuthIdentifierName() 时会引发异常:

BadMethodCallException: Call to undefined method Illuminate\Database\Query\Builder::getAuthIdentifierName() in /var/www/vendor/laravel/framework/src/Illuminate/Database/Query/Builder.php:2445

相反,遵循相同的方法,但覆盖更多函数,以便从 createModel() 返回正确的对象。

getQuery() 返回没有全局范围的构建器,其他两个函数使用它。

class GlobalUserProvider extends EloquentUserProvider
{
    /**
     * Get query builder for the model
     * 
     * @return \Illuminate\Database\Eloquent\Builder
     */
    private function getQuery()
    {
        $model = $this->createModel();

        return $model->withoutGlobalScope('active');
    }

    /**
     * Retrieve a user by their unique identifier.
     *
     * @param  mixed  $identifier
     * @return \Illuminate\Contracts\Auth\Authenticatable|null
     */
    public function retrieveById($identifier)
    {
        $model = $this->createModel();

        return $this->getQuery()
            ->where($model->getAuthIdentifierName(), $identifier)
            ->first();
    }

    /**
     * Retrieve a user by their unique identifier and "remember me" token.
     *
     * @param  mixed  $identifier
     * @param  string  $token
     * @return \Illuminate\Contracts\Auth\Authenticatable|null
     */
    public function retrieveByToken($identifier, $token)
    {
        $model = $this->createModel();

        return $this->getQuery()
            ->where($model->getAuthIdentifierName(), $identifier)
            ->where($model->getRememberTokenName(), $token)
            ->first();
    }
}

【讨论】:

    【解决方案4】:

    Sasan Farrokh 有一个正确的答案。唯一不重写 createModel 而是重写 newModelQuery 并且这将起作用

    protected function newModelQuery($model = null)
    {
        $modelQuery = parent::newModelQuery();
        return $modelQuery->withoutGlobalScope('active');
    }
    

    【讨论】:

      【解决方案5】:

      使用您在 OP 中使用的代码扩展 AuthController。应该可以的。

      public function postLogin(Request $request)
      {
          $user = User::withoutGlobalScope('active')
                      ->where('username', $request->username)
                      ->first();
      
          if($user != null){
              if (Hash::check($request->password, $user->password)){
                  $user->active = 1;
                  $user->save();
              }
          }
      
          return $this->login($request);
      }
      

      【讨论】:

      • 这对更高版本的 Laravel 有效吗?我尝试在文档中查找“postLogin”,但找不到任何内容。此外,似乎 AuthController 现在是 LoginController 和 RegisterController。
      • 这里的问题出在auth 中间件中,它不会看到任何身份验证用户,因此您将被注销
      • @TheGeeky 我这里没有使用 auth 中间件
      • 在任何其他路由中,auth 中间件将使用您创建的范围并将您视为访客用户
      • 那不是真的 - 我设置 $user->active = 1 以便他们下次发出请求时认为他们是活跃用户并使用 auth 中间件
      【解决方案6】:

      我通过创建新包解决了这个问题。

      mpyw/scoped-auth: Apply specific scope for user authentication.

      运行composer require mpyw/scoped-auth 并像这样修改您的用户模型:

      <?php
      
      namespace App;
      
      use Illuminate\Auth\Authenticatable;
      use Illuminate\Contracts\Auth\Authenticatable as UserContract;
      use Illuminate\Database\Eloquent\Builder;
      use Illuminate\Database\Eloquent\Model;
      use Mpyw\ScopedAuth\AuthScopable;
      
      class User extends Model implements UserContract, AuthScopable
      {
          use Authenticatable;
      
          public function scopeForAuthentication(Builder $query): Builder
          {
              return $query->withoutGlobalScope('active');
          }
      }
      

      您还可以轻松地选择Illuminate\Auth\Events\Login 来激活侦听器上的用户。

      <?php
      
      namespace App\Providers;
      
      use Illuminate\Support\Facades\Event;
      use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
      
      class EventServiceProvider extends ServiceProvider
      {
          /**
           * The event listener mappings for the application.
           *
           * @var array
           */
          protected $listen = [
              \Illuminate\Auth\Events\Login::class => [
                  \App\Listeners\ActivateUser::class,
              ],
          ];
      
          /**
           * Register any events for your application.
           *
           * @return void
           */
          public function boot()
          {
              parent::boot();
      
              //
          }
      }
      

       

      <?php
      
      namespace App\Listeners;
      
      use Illuminate\Auth\Events\Login;
      
      class ActivateUser
      {
          /**
           * Handle the event.
           *
           * @param  Illuminate\Auth\Events\Login $event
           * @return void
           */
          public function handle(Login $event)
          {
              $event->user->fill('active', 1)->save();
          }
      }
      

       

      【讨论】:

        【解决方案7】:

        我不得不使用 改为-&gt;withoutGlobalScopes()

        为了让它工作

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2014-12-20
          • 1970-01-01
          • 2016-04-14
          • 1970-01-01
          • 2022-11-11
          • 2017-09-14
          • 2011-04-07
          相关资源
          最近更新 更多