Can nginx support FastCGI Authorizers?
lhmwzy
lhmwzy at gmail.com
Mon Jan 12 14:51:51 MSK 2009
I use FastCGI Authorizers to exam the valid of user and protect some
especial directory.
I use perl to implement it.
the script:
#!/usr/bin/perl
use strict;
use DBI;
use FCGI;
use constant PATH_NEVER => 0;
use constant PATH_MAYBE => 1;
use constant PATH_ALWAYS => 2;
# normally, I would abstract this stuff out into a sitewide config module,
# but for didactic reasons, I'll just define some constants here:
use constant COOKIE_NAME => 'AUTH_TOKEN';
use constant DBI_DSN => 'dbi:mysql:hostname=DBHOSTNAME;database=DBNAME';
use constant DBI_USR => 'monty';
use constant DBI_PWD => 'widenius';
use constant AUTH_ERR => -1;
use constant AUTH_NOK => 0;
use constant AUTH_OK => 1;
use constant AUTH_QUERY => <<AQ_SQL;
select count(*) as authorized
from login_table
where user = ?
and token = ?
and expiry > unix_timestamp()
AQ_SQL
use vars qw($DBH $STH $N);
sub _init ();
sub _exit ();
sub authorized ($$$);
sub get_login_cookie ();
sub query_decode (;@);
_init();
for ($N = 0; FCGI::accept() >= 0; $N++)
{
# check the path to see if we want/need to authorize access:
my $path_auth = check_path($ENV{REQUEST_URI});
if($path_auth == PATH_MAYBE)
{
my $auth = undef();
my $user;
my $token;
my $cookie;
# get the login cookie and decompose it into user + token:
# cookie format: USERNAME:OPAQUETOKENFROMDB
$cookie = get_login_cookie();
($user,$token) = split(/:/,$cookie,2);
# check to see if an unexpired entry exists in the db:
$auth = authorized($STH, $user, $token);
if($auth == AUTH_OK)
{
# return 200 Ok, and set the AUTH_USER_NAME env variable
# in case there is a dynamic content generator:
# variables you want to set for the requested script/page
# need to be prefixed w. the string 'Variable-',
# or they will be passed back to the client, not the server.
print(STDOUT "Status: 200 Authorized\r\n");
print(STDOUT "Variable-AUTH_USER_NAME: $user\r\n");
print(STDOUT "\r\n");
}
elsif($auth == AUTH_NOK)
{
# Not authorized.
# You can make your login page the default
# 401 page with the htaccess ErrorDocument 401 directive:
print(STDOUT "Status: 401 Not Authorized\r\n");
print(STDOUT "WWW-Authenticate: basic realm=\"foo\"\r\n");
print(STDOUT "\r\n");
}
else
{
# Waah. Something blew up.
print(STDOUT "Status: 500 Internal Auth Error\r\n");
print(STDOUT "\r\n");
}
}
elsif($path_auth == PATH_NEVER)
{
# we never allow anyone in to these:
print(STDOUT "Status: 403 Denied\r\n");
print(STDOUT "\r\n");
}
elsif($path_auth == PATH_ALWAYS)
{
# these we don't really care about, just let them in.
# your error pages, icon, etc should all fall into this
# category, as should your login page:
print(STDOUT "Status: 200 Ok\r\n");
print(STDOUT "\r\n");
}
else
{
# This should not be able to happen: If it does,
# your site needs attention from you:
print(STDOUT "Status: 500 Internal Auth Error\r\n");
print(STDOUT "\r\n");
}
}
_exit();
##############
# Access rules, first match wins:
# /auth/login.cgi is always allowed
# /share/* and /icons/* are always allowed
# /lib/* and /auth/* are always disallowed
# anything else must be explicitly authorized
sub check_path ($)
{
my $uri = $_[0];
#warn("check_path($_[0])\n");
if ($uri =~ m@/auth/login.cgi@){ return PATH_ALWAYS }
if ($uri =~ m@/share/|/icons/@){ return PATH_ALWAYS }
if ($uri =~ m@/lib/|/auth/@) { return PATH_NEVER }
return PATH_MAYBE;
}
sub authorized ($$$)
{
my $rv;
my $row;
my $sth = $_[0];
my $user = $_[1];
my $token = $_[2];
if(!$sth->execute($user,$token))
{
warn("DBI error: ", $sth->errstr(), "\n");
return AUTH_ERR;
}
if($row = $sth->fetchrow_arrayref())
{
# only interested in one column in this case
$rv = $row->[0] ? AUTH_OK : AUTH_NOK;
}
else
{
warn("DBI error: ", $sth->errstr(), "\n");
$rv = AUTH_ERR;
}
# paranoia: empty out the sql result buffer, just in case
# so that it's clean for the next invocation
while ($sth->{Active}) { $sth->fetchrow_arrayref() }
return $rv;
}
# open a database connection and prepare the query
sub _init ()
{
$DBH = DBI->connect(DBI_DSN, DBI_USR, DBI_PWD)
|| die("DBI->connect() failed: $DBI::errstr\n");
$STH = $DBH->prepare(AUTH_QUERY)
|| die("prepare(AUTH_QUERY) failed: ",$DBH->errstr(),"\n");
}
# clean up and close down
sub _exit ()
{
$STH->finish();
$DBH->disconnect();
}
# extract a login cookie from the headers:
# the assumption is made here that any... unusual characters
# in the cookie have been %XX encoded:
sub get_login_cookie ()
{
my $cval = undef();
if(exists($ENV{HTTP_COOKIE}))
{
my @cookie = split(/; /,$ENV{HTTP_COOKIE});
COOKIE:
foreach my $ck (@cookie)
{
my($n, $v) = query_decode(split(/=/,$ck,2));
if($n eq COOKIE_NAME) { $cval = $v; last COOKIE }
}
}
return $cval;
}
# %XX decode a string or strings:
sub query_decode (;@)
{
my @str = @_;
my $item;
foreach $item (@str)
{
$item =~ tr/+/ /;
$item =~ s/\%([A-F\d]{2})/chr(hex($1))/gei;
}
return wantarray ? (@str) : $str[0];
}
END
/* mysql login table create statement */
create table login_table (token char(32) not null,
user char(32) not null,
expiry int(11) not null)
2009/1/12 Igor Sysoev <is at rambler-co.ru>:
> On Mon, Jan 12, 2009 at 07:24:09PM +0800, lhmwzy wrote:
>
>> lighttpd web server and zeus web server both support FastCGI Authorizers.
>> FastCGI servers? what's your mean?
>
> I mean FastCGI-backend - PHP, Django, etc.
> nginx, lighty, Zeus, and Apache are just proxies that convert a HTTP request
> to FastCGI one and pass the request to a real FastCGI-server for processing.
>
More information about the nginx
mailing list