2017-10-14 92 views
0

我目前正在将CGI应用程序迁移到Dancer2。我以前使用了使用MySQL的“手工制作”身份验证机制,并使用了属性为email,passwordstate的用户表。 state表示账户是active还是lockedlocked表示帐户被禁用(逻辑删除)。如何使用Dancer2 :: Plugin :: Auth :: Extensible处理锁定/禁用的用户帐户?

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

一切工作就像一个魅力,但有一个例外:

用我的旧的“手工制作”的机制,我能够用户,即逻辑删除而不从数据库中删除它们。只有当电子邮件和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用户?

+0

我不明白为什么有人会低估你的问题。它写得很好,显示了一些研究(我们认为阅读源不应该被期望,或根本没有问题),它清楚地说明你想要做什么以及为什么,并且有一个代码示例是这里没有关系。 – simbabque

+0

我创建了一个票据将其移入文档:https://github.com/PerlDancer/Dancer2-Plugin-Auth-Extensible-Provider-Database/issues/5 – simbabque

回答

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的饼干和开放http://localhost:5000/users一次。这次输入bartest

你会看到登录不起作用。

bar cannot log in

要反测试,更换config.ymlusers_table并重新启动应用程序。

# config.yml 
       users_table: 'users' 

现在用户foo将能够登录。因为数据库处理所有的逻辑(并且很可能已经缓存了它),所以这种方法不仅易于实现,而且也应该是具有最高性能的方式。

您的应用程序,特别是身份验证插件,也不需要了解活跃禁用领域都存在。他们不需要关心。东西只会工作。

+0

您可能希望做一些奇特的事情来允许活动的用户名与重复的禁用用户名重复。也许是对多列的限制? 'CONSTRAINT unique_name UNIQUE(username,disabled)'可以工作,但不是很漂亮。 – Borodin

+0

@Borodin我不确定这是否是一个好主意。那么用户不再是唯一的。对于审计而言,这可能是相关的。 – simbabque

+0

哇!谢谢你的两个很好的答案。你甚至提交了D2的FAQ。我认为作为第一个镜头,我将在视图中使用该解决方案,因为这更容易实施。从长远来看,我可能会深入挖掘并编写自己的Provider,它完全集成了'state'列。我的应用程序也有一个可以锁定和解锁用户的管理页面,所以如果['update_user'](https://metacpan.org/pod/Dancer2::Plugin::Auth::Extensible# update_user)函数可以无缝工作(包括'state'列)。 – PerlDuck

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相关的任何交互中。

+1

是不是可以创建* view *用户表,它只是排除锁定的记录,并获得认证工作呢? 'DBIx :: Class :: ResultSource :: View'是'DBIx :: Class :: ResultSource'的一个子类,所以调用的代码不应该知道任何不同,但你对舞者的了解要多于我。 – Borodin

+1

@Borodin这主意听起来很不错。我没有想过。如果这件事情在引擎盖下使用DBIC。如果没有,常规观点也可以发挥作用。我喜欢。 – simbabque

+0

我会试试这个@Borodin。如果能解决问题,我们可以将其发布到文档中。 – simbabque