没有全局范围的 Laravel 身份验证

Posted

技术标签:

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

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

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

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

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

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

我需要达到的目标:

    使 AuthController 忽略全局范围“活动”。 如果用户名和密码正确,则将“活动”列值更改为“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 主代码的情况下忽略全局作用域,从而保持更新?

谢谢。

【问题讨论】:

我遇到了类似的问题。我使用了这个解决方案:***.com/questions/34696134/laravel-global-scope-auth 创建两个单独的用户模型。第一个没有全局范围,仅用于身份验证。第二个扩展第一个,包括全局范围,并用于其他所有内容。 如果我错了,请纠正我,但您没有在这里使用withoutGlobalScope回答您自己的问题吗? 请参考链接:***.com/a/59297932/6196907 【参考方案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()

为了让它工作

【讨论】:

以上是关于没有全局范围的 Laravel 身份验证的主要内容,如果未能解决你的问题,请参考以下文章

Laravel Passport 没有身份验证

没有数据库的 Laravel 中的 JWT 身份验证

jwt api 身份验证如何工作(使用 dingo-laravel)

没有身份验证的 Laravel 5.3 RESTFul API

Laravel 5.5 - 升级身份验证后没有正确重定向

没有用户模型的 Laravel JWT 身份验证