【问题标题】:How to allow only one user at a time in this CGI::Application?如何在此 CGI::Application 中一次只允许一个用户?
【发布时间】:2011-07-26 21:41:21
【问题描述】:

我用this教程做了一个网站,但我希望任何时候都只允许一个用户登录。

我想应该在Login.pm 中进行此更改,我已将其包含在内,但我不知道在哪里设置此限制。

更新

基于 scorpio17 的解决方案,我现在只有一个用户可以登录,如果用户记得点击注销。

现在的问题是如何在会话超时时更改 $can_login 状态。

这是更新的功能。

sub logout : Runmode {
    my $self = shift;
    if ($self->authen->username) {
    $self->authen->logout;
    $self->session->delete; # Delete current session
    }

    # get state of can_login file
    my $file = "lock-can_login.txt";
    open my $fh, '+<', $file or die "can't open $file in update mode: $!\n";
    flock($fh, LOCK_EX) or die "couldn't get lock: $!\n";

    # 1 means a new user can login
    my $can_login = <$fh>;
    chomp $can_login;

    # allow others to login now
    $can_login = !$can_login;

    # write
    seek $fh, 0, 0;
    print $fh "$can_login\n";
    truncate($fh, tell($fh));
    close $fh;

    return $self->redirect($self->query->url);
}

sub one_user {
    my $self = shift;

    # get state of can_login file
    my $file = "lock-can_login.txt";
    open my $fh, '+<', $file or die "can't open $file in update mode: $!\n";
    flock($fh, LOCK_EX) or die "couldn't get lock: $!\n";

    # 1 means a new user can login
    my $can_login = <$fh>;
    chomp $can_login;

    if ($self->authen->is_authenticated && $can_login) {
    # prevent others from logging in
    $can_login = !$can_login;
    } else {
    $self->authen->logout;
    #and redirect them to a page saying "there can be only one!"
    }

    # write
    seek $fh, 0, 0;
    print $fh "$can_login\n";
    truncate($fh, tell($fh));
    close $fh;
}

谁能解决这个问题?

package MyLib::Login;

use strict;

#use lib '/usr/lib/perl5/vendor_perl/5.8.8/';

use base 'CGI::Application';

# shorter URLs
# extract the desired run mode from the PATH_INFO environment variable.
use CGI::Application::Plugin::AutoRunmode;

# wrapper for DBI
#use CGI::Application::Plugin::DBH(qw/dbh_config dbh/);

# a wrapper around CGI::Session.
# maintain state from one page view to the next (provides persistent data).
use CGI::Application::Plugin::Session;

# logging in and out.
# Authentication allows to identify individual users.
use CGI::Application::Plugin::Authentication;

# external redirects in CGI::Application
use CGI::Application::Plugin::Redirect;

# read parameters from config file
use CGI::Application::Plugin::ConfigAuto(qw/cfg/);

# encrypt passphrases
use Digest::MD5 qw(md5_hex);

# authenticate against NIS/LDAP server
use Authen::Simple::LDAP;


sub setup {
    my $self = shift;

    $self->mode_param(
    path_info => 1,         # tell CGI::Application to parse the PATH_INFO environment variable
    param     => 'rm',
    );
}

# most of the initialization is done here
sub cgiapp_init {
    my $self = shift;

    # read config file and store name-value pairs in %CFG
    my %CFG = $self->cfg;

    # where to look for templete files
    $self->tmpl_path(['./templates']);

    # save session data in mysql
    $self->session_config(
    # store sessions in /tmp as files
    CGI_SESSION_OPTIONS => [ "driver:File", $self->query, {Directory=>'/tmp'} ],

    DEFAULT_EXPIRY => '+10m',   # default expiration time for sessions
    );

    # configure authentication parameters
    $self->authen->config(
    DRIVER => [ 'Authen::Simple::LDAP',
            host   => 'ldaps://nms.imm.dtu.dk/dc=ldap,dc=imm,dc=dtu,dc=dk',
            basedn => 'OU=people,DC=ldap,DC=imm,DC=dtu,DC=dk',
    ],

    STORE                => 'Session',          # save login state inside a session
                                                    # If a user is not logged in, but tries to access a
                                                    # protected page, the Authentication plugin will
                                                    # automatically redirect the user to the login page.
                                                    # Once the user enters a valid username and
                                                    # passsword, they get redirected back to the
                                                    # protected page they originally requested.
    LOGOUT_RUNMODE       => 'logout',           # method to use for logging out when session expires

# uncomment the next 3 lines to enable custom build login prompt
#   LOGIN_RUNMODE        => 'login',
#   POST_LOGIN_RUNMODE   => 'okay',             # run mode that gets called after a user successfully logs in
                                                    # figures out which run mode (page) the user really wanted to
                                                    # see, then redirects the browser to that page using http
                                                    # (not https).

#   RENDER_LOGIN         => \&my_login_form,    # generate a login form. Authentication plugin comes with a default 
    );

    # define runmodes (pages) that require successful login:
    # The Login.pm module doesn't define any content - all of the actual web pages are in Simple.pm.
    # 'mustlogin' page is a place-holder. It's a dummy page that forces you to login, but immediately redirects
    # you back to the default start page (usually the index page).
    $self->authen->protected_runmodes('mustlogin');
}


# define mustlogin runmode
sub mustlogin : Runmode {
    my $self = shift;
    my $url = $self->query->url;
    return $self->redirect($url);
}


# switch from https to http. It assumes that the target run mode is stored in a cgi parameter named
# 'destination', but if for some reason this is not the case, it will default back to the index page.
sub okay : Runmode {
    my $self = shift;

    my $url = $self->query->url;
#  my $user = $self->authen->username;
    my $dest = $self->query->param('destination') || 'index';

    if ($url =~ /^https/) {
    $url =~ s/^https/http/;
    }

    return $self->redirect("$url/$dest");
}

# displays the login form
# But first, it checks to make sure you're not already logged in, and second, it makes sure you're connecting with https. If you try to access the login page with http, it will automatically redirect you using https.
sub login : Runmode {
    my $self = shift;
    my $url = $self->query->url;

    my $user = $self->authen->username;
    # is user logged in?
    if ($user) {
    my $message = "User $user is already logged in!";
    my $template = $self->load_tmpl('default.html');
    $template->param(MESSAGE => $message);
    $template->param(MYURL => $url);
    return $template->output;
    } else {
    my $url = $self->query->self_url;
    unless ($url =~ /^https/) {
            $url =~ s/^http/https/;
        return $self->redirect($url);
    }
    return $self->my_login_form;
    }
}

# generate custom login. See templates/login_form.html
sub my_login_form {
    my $self = shift;
    my $template = $self->load_tmpl('login_form.html');

    (undef, my $info) = split(/\//, $ENV{'PATH_INFO'});
    my $url = $self->query->url;

    # 'destination' contains the URL of the page to go to once the user has successfully logged in

    # try to get a value for 'destination' from the CGI query object (in case it was passed as a hidden variable)
    my $destination = $self->query->param('destination');

    # If failed to get from CGI query object, try get destination from PATH_INFO environment variable
    # in case it's being passed as part of the URL
    unless ($destination) {
    if ($info) {
        $destination = $info;
    } else {
        # default to index page
        $destination = "index";
    }
    }

    my $error = $self->authen->login_attempts;

    # insert values into the template parameters
    $template->param(MYURL => $url);
    $template->param(ERROR => $error);
    $template->param(DESTINATION => $destination);

    # generate final html
    return $template->output;
}
# logout method
sub logout : Runmode {
    my $self = shift;
    if ($self->authen->username) {
    $self->authen->logout;
    $self->session->delete; # Delete current session
    }
    return $self->redirect($self->query->url);
}

# error runmode / page
sub myerror : ErrorRunmode {
    my $self = shift;
    my $error = shift;
    my $template = $self->load_tmpl("default.html");
    $template->param(NAME => 'ERROR');
    $template->param(MESSAGE => $error);
    $template->param(MYURL => $self->query->url);
    return $template->output;
}

# called if non-existant runmode/page is accessed. Gives a nicer error message, when typing a wrong url
sub AUTOLOAD : Runmode {
    my $self = shift;
    my $rm = shift;
    my $template = $self->load_tmpl("default.html");
    $template->param(NAME => 'AUTOLOAD');
    $template->param(MESSAGE => "<p>Error: could not find run mode \'$rm\'<br>\n");
    $template->param(MYURL => $self->query->url);
    return $template->output;
}

1;

更新 2

我现在知道$self-&gt;authen-&gt;username 在调用mustLogin 运行模式时总是设置为undef。这意味着多个用户可以登录。

我已经插入

  open F, ">/tmp/debug";
  print F Dumper $self->authen->username;
  close F;

问题发生在哪里。

$self-&gt;cfg('SESSIONS_DIR') 返回正确的路径。

知道为什么$self-&gt;authen-&gt;username 设置为undef 运行mustLogin 吗?

sub teardown {
  my $self = shift;

  $self->param('found_a_user', 0);

  CGI::Session->find(
      "driver:File;serializer:yaml",
      sub { my_subroutine($self, @_)},
      {Directory => $self->cfg('SESSIONS_DIR'), UMask => 0600,},
      );

  open F, ">/tmp/debug";
  print F Dumper $self->authen->username;
  close F;

  # get state of can_login file
  open my $fh, '+<', 'can_login.yaml';
  flock($fh, LOCK_EX) or die "couldn't get lock: $!\n";
  my $c = YAML::Syck::LoadFile($fh);

  if ( $self->param('found_a_user') ) {
      # found a logged in user with an unexpired session
      $c->{can_login} = 0;
  } else {
      # did NOT find any logged in users
      $c->{can_login} = 1;
  }

  # write
  my $yaml = YAML::Syck::Dump($c);
  $YAML::Syck::ImplicitUnicode = 1;
  seek $fh,0, SEEK_SET;   # seek back to the beginning of file
  print $fh $yaml . "---\n";
  close $fh;
}

sub my_subroutine {
  my $self = shift;
  my ($session) = @_;  # I don't actually need this for anything here

  if ($self->authen->username) {
    $self->param('found_a_user', 1);
  }

}

【问题讨论】:

  • 在其他用户登录之前是否需要用户退出?
  • @Alexandr Ciornii:这是现在的问题。如果会话超时,则没有人可以登录,因为我保持$can_login 状态的文件尚未更改为 TRUE。如果没有活动会话,您知道如何将 $can_login 设置为 TRUE 吗?
  • 将上次访问时间和用户名存储在数据库/文件中。当用户访问它时,检查它是否是同一用户。如果是其他用户并且已经过了一段时间,请允许他登录。

标签: perl cgi-application


【解决方案1】:

一次只有一个用户?这是一个非常奇怪的要求。我以前从来没有做过这样的事情。这是一种解决方法:您需要一个名为“can_login”之类的二进制状态变量。您可以将其存储在文件或数据库中。将其初始化为“true”,然后在用户成功登录后立即将其切换为“false”。这个怎么做?在 $self->authen->config() 中,为 POST_LOGIN_CALLBACK 定义一个值。这将需要指向代码引用以检查“can_login”值。如果用户已通过身份验证,并且“can_login”为真,则将“can_login”切换为假以防止其他登录。如果用户通过了身份验证,并且 'can_login' 为假,则调用 $self->authen->logout 将他们注销,并将他们重定向到一个页面,上面写着“只能有一个!”管他呢。另外 - 确保您在保存登录信息的会话上创建超时值,这样您就不会因为有人忘记注销而意外锁定所有人。如果将“can_login”值存储在文件中,则必须实现文件锁定以避免竞争条件。如果将其存储在数据库中,则必须弄清楚如何将正确的数据库句柄传递给回调代码 ref(可能将其粘贴到 setup() 方法中的全局变量中)。您的注销运行模式应该像往常一样将用户注销,并将“can_login”值切换回true。处理过期旧会话的代码也应该这样做。您甚至可以在单独的进程中执行此操作(例如每 5 分钟运行一次的 cron 作业) - 使旧会话过期,检查活动登录,如果不存在,请确保“can_login”为真。

更新

关于如何处理过期会话,假设您的 cgiapp_init() 方法中有这样的内容:

$self->session_config(
  CGI_SESSION_OPTIONS => [
    "driver:File;serializer:yaml",
    $self->query,
    {Directory => $self->cfg('SESSIONS_DIR'), UMask => 0600,},
  ],

  DEFAULT_EXPIRY => '+1h',
  COOKIE_PARAMS => {
    -path     => '/',
    -httponly => 1,        # help avoid XSS attacks
  },
);

那么通常你可能需要这样的拆解方法:

sub teardown {
  my $self = shift;

  # purge old sessions
  CGI::Session->find(
    "driver:File;serializer:yaml",
    sub {},
    {Directory => $self->cfg('SESSIONS_DIR'), UMask => 0600,},
  );
}

teardown() 方法在每个运行模式结束时被调用。在这种情况下,它所做的只是使旧会话过期(有关更多详细信息,请参阅 CGI::Session 的文档 - 查看“查找”方法下的部分)。一般形式是这样的:

find($dsn, \&code, \%dsn_args);

$dsn 和 \%dsn_args 应该与您的会话配置中的任何内容相匹配 - 例如,这就是我展示我的原因。这里的 coderef 不需要做任何事情,因为 find() 会自动为每个会话调用 load() ,这会自动删除任何已经过期的东西——因为这就是我想要它做的一切,不需要其他任何东西。但是您可以使用它来检查登录用户。你必须这样做:

sub teardown {
  my $self = shift;

  $self->param('found_a_user',0);

  CGI::Session->find(
    "driver:File;serializer:yaml",
    sub { my_subroutine( $self, @_ ) },
    {Directory => $self->cfg('SESSIONS_DIR'), UMask => 0600,},
  );

  if ( $self->param('found_a_user') ) {
    # found a logged in user with an unexpired session: set $can_login=0 here
  } else {
    # did NOT find any logged in users - set $can_login=1 here
  }

}

sub my_subroutine {
  my $self = shift;
  my ($session) = @_;  # I don't actually need this for anything here

  if ($self->authen->username) {
    $self->param('found_a_user',1);
  }

}

注意 my_subroutine 不是一个方法,所以我必须传递 $self 作为一个额外的参数。 我需要它来访问 authen->username,这只有在我们有一个未过期会话的登录用户时才成立。请注意,这将为每个会话调用,同时删除旧会话。如果 'found_a_user' 参数设置为 1,那么我们知道我们至少找到了一个活动用户,并且可以根据需要更新 $can_login 变量。

【讨论】:

  • 非常感谢!我会马上解决的。而不是记住$can_login的状态,是否可以在登录运行模式到if ($#users &gt; 1) {$self-&gt;authen-&gt;logout;}。?如果我可以询问登录用户的数量,这样的事情会起作用吗?
  • 据我所知,身份验证模块中没有任何内容可以计算当前登录的用户数量。如果需要,可以以类似的方式实现它,只需创建$user_count 变量而不是 $can_login 布尔值。更新登录/注销方法中的计数(并注意由于会话过期而导致的注销!)当然,如果您询问的原始实现按预期工作,则计数将永远不会是 0 或 1。如果您是希望有一种“计数”方法可以让这更容易,对不起!
  • 我更新了答案以包含更多关于即将到期的会话 - 我希望这会有所帮助!
  • 您可以使用元标记刷新来强制每 60 秒左右重新加载整页。如果您的 ajax 请求针对受保护的运行模式,您可以将这些运行模式设计为​​在用户未登录时不返回任何内容。这样注销可以有效地关闭更新而不会破坏页面。
  • 您可以使用 $self->redirect('runmode') - 它基本上会将指定的运行模式附加到当前 URL。如果您想在同一个实例脚本中使用不同的运行模式,这很好。如果您需要重定向到不同的实例脚本,请使用 $self->redirect($url)。
猜你喜欢
  • 2017-02-26
  • 2013-05-18
  • 2014-02-11
  • 2019-11-19
  • 1970-01-01
  • 2015-03-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多