1

我已经使用教程制作了一个网站,但我希望任何时候都只允许一个用户登录。

我想应该在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->authen->username它总是设置为调用运行模式undef时。mustLogin这意味着多个用户可以登录。

我已插入

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

问题发生的地方。

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

知道为什么$self->authen->username设置undefmustLogin运行吗?

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);
  }

}
4

1 回答 1

2

一次只有一个用户?这是一个非常奇怪的要求。我以前从来没有做过这样的事情。这是一种解决方法:您需要一个名为“can_login”之类的二进制状态变量。您可以将其存储在文件或数据库中。将其初始化为“true”,然后在用户成功登录后立即将其切换为“false”。这该怎么做?在 $self->authen->config() 中,为 POST_LOGIN_CALLBACK 定义一个值。这将需要指向代码引用以检查“can_login”值。如果用户已通过身份验证,并且“can_login”为真,则将“can_login”切换为假以防止其他登录。如果用户已通过身份验证,并且 'can_login' 为 false,则调用 $self->authen->logout 将其注销,并将其重定向到显示“

更新

关于如何处理过期会话,假设您在 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 变量。

于 2011-07-19T21:07:33.717 回答