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