没有全局范围的 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】:我不得不使用
改为->withoutGlobalScopes()
为了让它工作
【讨论】:
以上是关于没有全局范围的 Laravel 身份验证的主要内容,如果未能解决你的问题,请参考以下文章
jwt api 身份验证如何工作(使用 dingo-laravel)