2011-07-19 85 views
1

我已經使用this教程來創建一個網站,但我希望只有一個用戶可以隨時登錄。如何在此CGI :: Application中一次只允許一個用戶?

我想這個改變應該在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總是被設置爲undefmustLogin runmode被調用。這意味着多個用戶可以登錄。

我已插入

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

} 
+0

用戶在其他用戶能夠登錄之前是否需要註銷? –

+0

@Alexandr Ciornii:這是現在的問題。如果會話超時,則沒有人可以登錄,因爲保留'$ can_login'狀態的文件尚未更改爲TRUE。如果沒有活動會話,你有沒有想法如何設置'$ can_login'爲TRUE? –

+0

在DB /文件中存儲上次訪問和用戶名的時間。當用戶訪問它時,檢查它是否是同一個用戶。如果是不同的用戶並且一段時間過去了,請允許他登錄。 –

回答

2

一次只有一個用戶?這是一個很奇怪的要求。我以前從來沒有做過這樣的事情。以下是一種解決方法:您需要一個名爲「can_login」的二元狀態變量。您可以將其存儲在文件或數據庫中。將其初始化爲'true',然後一旦用戶成功登錄,將其切換爲'false'。這個怎麼做?在$ self> authen-> config()內部,爲POST_LOGIN_CALLBACK定義一個值。這需要指向代碼引用來檢查'can_login'值。如果用戶通過身份驗證,並且'can_login'爲true,則將'can_login'切換爲false以防止其他登錄。如果用戶通過身份驗證,並且'can_login'爲false,請調用$ self-> authen-> logout將其註銷,並將其重定向到一個頁面,指出「只能有一個!」管他呢。另外 - 請確保您在持有登錄信息的會話中創建一個超時值,以便您不會因爲某人忘記註銷而意外鎖定每個人。如果將'can_login'值存儲在文件中,則必須實施文件鎖定以避免競爭狀況。如果將其存儲在數據庫中,則必須弄清楚如何將正確的數據庫句柄傳遞給回調代碼ref(也許將其粘貼到setup()方法中的全局變量中)。像往常一樣,註銷runmode應該將用戶註銷,PLUS將'can_login'值切換回true。處理到期的舊會話的代碼也應該這樣做。您甚至可以在單獨的進程中執行此操作(例如每5分鐘運行一次的cron作業) - 過期舊會話,檢查活動登錄,如果不存在,請確保'can_login'爲true。

UPDATE

關於如何處理過期的會議,讓我們假設你有這樣的事情你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」方法下的部分)。一般形式是這樣的:

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變量。

+0

非常感謝!我會適應它。用'$ can_login'記住狀態,而不是用'$ can_login'記住狀態,是否可以在登錄runmode中使用if($#users> 1){$ self-> authen-> logout;}'。如果我可以要求登錄用戶的數量,那會是什麼樣的工作? –

+1

據我所知,身份驗證模塊中沒有任何內容會記錄當前登錄的用戶數量。如果需要,您可以以類似的方式實現它,只需創建一個$ user_count變量而不是$ can_login布爾值。更新登錄/註銷方法中的計數(並注意由於過期會話導致的註銷!)當然,如果您詢問的原始實現按需運行,計數將永遠不會是0或1以外的任何值。如果您是希望有一個「數字」的方法,使更容易,對不起! – scorpio17

+0

您的文件鎖定解決方案效果很好。更新後的功能更新。我無法弄清楚受保護的頁面如何檢查會話是否已過期。我認爲它是在那裏,如果沒有會話,我必須更改'$ can_login'的狀態? –

相關問題