【问题标题】:How to handle locked/disabled user accounts with Dancer2::Plugin::Auth::Extensible?如何使用 Dancer2::Plugin::Auth::Extensible 处理锁定/禁用的用户帐户?
【发布时间】:2017-10-15 19:55:59
【问题描述】:

我目前正在将 CGI 应用程序迁移到 Dancer2。我之前使用了一个“手工制作”的身份验证机制,它使用 MySQL 和一个具有属性 emailpasswordstate 的用户表。 state 表示帐户是active 还是lockedlocked 表示账户被禁用(逻辑删除)。

我还有表 rolesuser_roles 来实现我的两个角色:管理员和用户。

一切都像魅力一样,除了一个例外:

通过我旧的“手工”机制,我能够锁定用户,即在不将它们从数据库中删除的情况下从逻辑上删除它们。只有当 email 和 hash_of(password) 匹配并且帐户没有被锁定时,登录才会成功。

如何使用 Dancer2::Plugin::Auth::ExtensibleDancer2::Plugin::Auth::Extensible::Provider::Database 实现它?

我希望钩子after_authenticate_user可以返回truefalse覆盖authenticate_user的结果,但事实并非如此。至少,它没有记录在案。

我想到的一件事是拥有一个额外的角色active,然后——对于每条路线——require_role active 而不仅仅是require_login

所以我的问题是:我怎样才能让Dancer2::Plugin::Auth::Extensible 只考虑active 用户?

【问题讨论】:

  • 我不明白为什么有人会否决您的问题。它写得很好,展示了一些研究(我们可以争辩说不应该期望阅读源代码,或者根本没有必要询问),它清楚地说明了你想要做什么以及为什么,并且在这里没有代码示例。
  • 我创建了一张票以将其移至文档中:github.com/PerlDancer/…

标签: perl authentication dancer


【解决方案1】:

Borodin suggested 创建一个视图并将其用作用户表。我做了一些测试,可以说这确实是实现这一目标的最简单方法。

警告:由于视图的性质,这使得应用程序无法修改或添加用户!

考虑以下Dancer2 应用程序。我从dancer2 创建脚本开始。

$ dancer2 gen -a Foo
$ cd Foo

我创建了以下简单的 sqlite 数据库。

$ echo "
CREATE TABLE users (
    id       INTEGER     PRIMARY KEY AUTOINCREMENT,
    username VARCHAR(32) NOT NULL UNIQUE,
    password VARCHAR(40) NOT NULL,
    disabled TIMESTAMP   NULL
);

CREATE VIEW active_users (id, username, password) AS
    SELECT id, username, password FROM users WHERE disabled IS NULL;

INSERT INTO users ( username, password, disabled )
VALUES  ( 'foo', 'test', null),
        ( 'bar', 'test', '2017-10-01 10:10:10');
" | sqlite3 foo.sqlite

只有一个users 表,其中包含插件建议的默认列,加上一个列disabled,可以是NULL 或时间戳。我认为使用 disabled 比使用 active 更容易说明。

然后我对lib/Foo.pm 进行了以下更改。所有这些基本上来自Dancer2::Plugin::Auth::ExtensibleDancer2::Plugin::Auth::Extensible::Provider::Database的文档。

package Foo;
use Dancer2;
use Dancer2::Plugin::Database;
use Dancer2::Plugin::Auth::Extensible;

our $VERSION = '0.1';

get '/' => sub {
    template 'index' => { 'title' => 'Foo' };
};

get '/users' => require_login sub {
    my $user = logged_in_user;
    return "Hi there, $user->{username}";
};

true;

接下来,插件需要进入配置。编辑 config.yml 并用这个替换它。

appname: "Foo"
layout: "main"
charset: "UTF-8"
template: "simple"
engines:
  session:
    Simple:
      cookie_name: testapp.session

# this part is interesting
plugins:
    Auth::Extensible:
        realms:
            users:
                provider: 'Database'

############### here we set the view
                users_table: 'active_users'
    Database:
        driver: 'SQLite'
        database: 'foo.sqlite'
        on_connect_do: ['PRAGMA foreign_keys = ON']
        dbi_params:
            PrintError: 0
            RaiseError: 1

现在我们都准备好尝试了。

$ plackup bin/app.psgi
HTTP::Server::PSGI: Accepting connections at http://0:5000/

在浏览器中访问http://localhost:5000/users。您将看到默认的登录表单。

输入footest。这应该可以工作,您应该会看到/users 路由。 (或者不是,在我的情况下,重定向似乎被破坏了......)。

现在去http://localhost:5000/logout 删除foo 的cookie 并再次打开http://localhost:5000/users。这次输入bartest

您会看到登录不起作用。

要进行反测试,请将users_table 替换为config.yml 并重新启动应用程序。

# config.yml
                users_table: 'users'

现在用户 foo 可以登录了。

这种方法不仅易于实现,而且应该是迄今为止性能最高的方法,因为数据库处理所有逻辑(并且很可能已经缓存了它)。

您的应用程序,尤其是身份验证插件,根本不需要知道 activedisabled 字段的存在。他们不需要关心。东西就行了。

【讨论】:

  • 您可能想要做一些花哨的事情来允许活动用户名与禁用用户名重复。也许是对多列的约束? CONSTRAINT unique_name UNIQUE (username, disabled) 可能有用,但不是很漂亮。
  • @Borodin 我不确定是否允许这样做是个好主意。那么用户就不再是唯一的了。对于审计,这可能是相关的。
  • 哇!谢谢你的两个很好的答案。您甚至为 D2 的常见问题提交了一张票。我认为作为第一枪,我将使用带有视图的解决方案,因为这更容易实现。从长远来看,我可能会更深入地挖掘并编写我自己的提供程序,它完全集成了state 列。我的应用程序还有一个管理页面,可以锁定和解锁用户,所以如果update_user 功能能够无缝工作(包括state 列)会很酷。
  • 是的,有时有效,有时无效。我已经多年没有使用它并且不记得确切的先决条件。在这种情况下,也许 MySQL 比 SQLite 更宽容。
  • @PerlDuck 是的。我也打算举报。今天晚些时候。
【解决方案2】:

您可以继承Dancer2::Plugin::Auth::Extensible::Provider::Database 并包装get_user_details 方法以检查用户是否处于活动状态。

考虑我使用in my other answer 的同一个应用程序。添加以下类。

package Provider::Database::ActiveOnly;

use Moo;
extends 'Dancer2::Plugin::Auth::Extensible::Provider::Database';

around 'get_user_details' => sub {
    my $orig = shift;
    my $self = shift;

    # do nothing if we there was no user
    my $user = $self->$orig(@_) or return;

    # do nothing if the user is disabled
    return if $user->{disabled};

    return $user;
};

1;

代码很简单。查找到用户后,我们就有了用户数据,因此我们可以检查disabled 列。如果其中有任何内容,则该用户已被禁用,我们将中止。

您还需要对config.yml 进行以下更改。

# config.yml
plugins:
    Auth::Extensible:
        realms:
            users:
                provider: 'Provider::Database::ActiveOnly'
                users_table: 'users'

现在应用程序的行为应该与其他答案完全相同。


要了解为什么会这样,我们需要查看源代码。身份验证发生在authenticate_user。最初我认为应该替换它,但这个解决方案更聪明,因为我们只需要获取一次用户数据。

authenticate_user 方法 fetches the user datathe get_user_details method,所以我们可以在那里挂钩。我们的around wrapper会透明地注入对用户活跃度的检查,剩下的代码甚至都不知道有什么区别。

非活动用户不会出现在任何与 Plugin::Auth::Extensible 相关的交互中。

【讨论】:

  • 难道不能在用户表上创建一个 view 来简单地排除锁定的记录,并让身份验证解决这个问题吗? DBIx::Class::ResultSource::ViewDBIx::Class::ResultSource 的子类,所以调用代码应该没有什么不同,但你对 Dancer 的了解远比我多。
  • @Borodin 这听起来是个好主意。我没有考虑。如果那个东西在引擎盖下使用 DBIC。如果没有,常规视图也可以。我喜欢它。
  • 我要试试这个@Borodin。如果成功了,我们可以将其 PR 到文档中。
  • 我一直在研究 CPAN PRC,但它是如何工作的?我已注册接收消息,但历史记录显示 1 月 / 2 月只有 10 条消息,此后没有任何消息。您如何选择要处理的内容,以及您如何知道其他人尚未在处理它?
  • @Borodin 我已经用视图的想法写了一个答案。真的很整洁。
猜你喜欢
  • 2010-10-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-02-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多