如何使用Dancer2 :: Plugin :: Auth :: Extensible处理锁定/禁用的用户帐户?

时间:2017-10-14 16:35:29

标签: perl authentication dancer

我目前正在将CGI应用程序迁移到Dancer2。我以前使用过MySQL的“手工制作”认证机制和一个属性为emailpasswordstate的用户表。 state表示该帐户是active还是lockedlocked表示帐户已被停用(逻辑删除)。

我还有表rolesuser_roles来实现我的两个角色:admin和user。

一切都像魅力一样,但有一个例外:

使用我原来的“手工制作”机制,我能够锁定用户,即逻辑删除它们而不将其从数据库中删除。只有当电子邮件和hash_of(密码)匹配帐户未锁定时,登录才会成功。

如何使用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个用户?

2 个答案:

答案 0 :(得分:2)

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或时间戳。我认为使用禁用比使用活动更容易进行说明。

然后我对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。您将看到默认的登录表单。

login page

输入footest。这应该有效,你应该看到/users路线。 (或者不是,就像在我的情况下,重定向似乎被打破了......)。

foo is logged in

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

您将看到登录无效。

bar cannot log in

要进行反测试,请替换users_table中的config.yml并重新启动应用。

# config.yml
                users_table: 'users'

现在,用户foo将能够登录。

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

您的应用程序,尤其是身份验证插件,根本不需要了解活动已禁用字段的存在。他们不需要关心。东西会起作用。

答案 1 :(得分:1)

您可以继承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相关的任何交互中。