認証リクエストの例

説明

この例はサブリクエストの結果に基づいた認証を実装します。2xx ステータス アクセスを返すサブリクエストが許可される場合、401と403は認証の失敗と見なされ、そのほかの全てのコードはエラーとなります。

このモジュールの完全なソースは以下で見つけることができます: http://mdounin.ru/hg/ngx_http_auth_request_module/

コードのブレークダウン

We need some aspects of NGINX’s core, configuration and http functions and structures so we include these.

#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>

このモジュールのための二つの設定ディレクティブがあります:

  • auth_request - サブリクエストのためのURIを設定する (あるいは off)
  • auth_request_set - 認証が成功した場合に、指定されたリクエストの変数を指定された値に設定する

以下の構造はこの情報がどうやって格納されるかを定義します。

typedef struct {
    ngx_str_t                 uri;
    ngx_array_t              *vars;
} ngx_http_auth_request_conf_t;

モジュール内で使われる様々なコールバックを使って物事の状態を保持するためにコンテキスト構造が必要です。この構造はコンテキストを定義します。

done 変数はサブリクエストが完了したかどうかを格納し、status はサブリクエストの状態コードを保持し、 subrequestはサブリクエストの情報を含むngx_http_request_t 構造です。

typedef struct {
    ngx_uint_t                done;
    ngx_uint_t                status;
    ngx_http_request_t       *subrequest;
} ngx_http_auth_request_ctx_t;

この構造は auth_request_set ディレクティブのための変数を保持するためのものです。

typedef struct {
    ngx_int_t                 index;
    ngx_http_complex_value_t  value;
    ngx_http_set_variable_pt  set_handler;
} ngx_http_auth_request_variable_t;

幾つかの関数のプロトタイプの宣言は、コード内で後で使うために必要とされます。

static ngx_int_t ngx_http_auth_request_handler(ngx_http_request_t *r);
static ngx_int_t ngx_http_auth_request_done(ngx_http_request_t *r,
    void *data, ngx_int_t rc);
static ngx_int_t ngx_http_auth_request_set_variables(ngx_http_request_t *r,
    ngx_http_auth_request_conf_t *arcf, ngx_http_auth_request_ctx_t *ctx);
static ngx_int_t ngx_http_auth_request_variable(ngx_http_request_t *r,
    ngx_http_variable_value_t *v, uintptr_t data);
static void *ngx_http_auth_request_create_conf(ngx_conf_t *cf);
static char *ngx_http_auth_request_merge_conf(ngx_conf_t *cf,
    void *parent, void *child);
static ngx_int_t ngx_http_auth_request_init(ngx_conf_t *cf);
static char *ngx_http_auth_request(ngx_conf_t *cf, ngx_command_t *cmd,
    void *conf);
static char *ngx_http_auth_request_set(ngx_conf_t *cf, ngx_command_t *cmd,
    void *conf);

ここで、このモジュールのための変数を定義するためにngx_command_t を使う必要があります。

NGX_HTTP_MAIN_CONF declares that it can be used inside the http configuration block, NGX_HTTP_SRV_CONF declares that it can be used in the server configuration block, NGX_HTTP_LOC_CONF declares that it can be used in the location configuration block. NGX_CONF_TAKE1 はこのディレクティブに1つの引数が必要なことを述べ、 NGX_CONF_TAKE2 はこのディレクティブのために二つの引数が必要なことを述べます。

このコード内でさらに宣言されているngx_http_auth_requestはNGINX設定の中でauth_request が見つかった時に引き起こされるコールバックです。このコード内でさらに宣言されているngx_http_auth_request_setngx_request_setが見つかった時に引き起こされるコールバックです。

NGX_HTTP_LOC_CONF_OFFSET はこの設定オプションがlocation設定ブロックコンテキストでローカルであることを述べます。

変数を扱うために独自のコールバックを使うので、変数にオフセットを定義する必要がありません。つまりこれは0に設定されます。

最後に、コマンドのリストは ngx_null_commandを使って終了されていなければなりません。

static ngx_command_t  ngx_http_auth_request_commands[] = {

    { ngx_string("auth_request"),
      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
      ngx_http_auth_request,
      NGX_HTTP_LOC_CONF_OFFSET,
      0,
      NULL },

    { ngx_string("auth_request_set"),
      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE2,
      ngx_http_auth_request_set,
      NGX_HTTP_LOC_CONF_OFFSET,
      0,
      NULL },

      ngx_null_command
};

ngx_http_module_t 構造はモジュールコンテキストとモジュールのためのコールバックをセットアップするために使われます。For this module we are interested in the postconfiguration and location block configuration callbacks.

static ngx_http_module_t  ngx_http_auth_request_module_ctx = {
    NULL,                                  /* preconfiguration */
    ngx_http_auth_request_init,            /* postconfiguration */

    NULL,                                  /* create main configuration */
    NULL,                                  /* init main configuration */

    NULL,                                  /* create server configuration */
    NULL,                                  /* merge server configuration */

    ngx_http_auth_request_create_conf,     /* create location configuration */
    ngx_http_auth_request_merge_conf       /* merge location configuration */
};

ngx_module_t構造は、NGINXがモジュールをセットアップする方法を知るために必要とされます。この構造のインスタンス名がモジュールのソース内のconfigファイルの中の一つと同じであることが重要です。

構造は常にNGX_MODULE_V1のヘッダとNGX_MODULE_V1_PADDINGのフッタを持たなければなりません。

このモジュールはHTTPモジュールで、NGX_HTTP_MODULEを使って宣言されます。このモジュールのためのスレッドとプロセスのコールバックを必要としません。

ngx_module_t  ngx_http_auth_request_module = {
    NGX_MODULE_V1,
    &ngx_http_auth_request_module_ctx,     /* module context */
    ngx_http_auth_request_commands,        /* module directives */
    NGX_HTTP_MODULE,                       /* module type */
    NULL,                                  /* init master */
    NULL,                                  /* init module */
    NULL,                                  /* init process */
    NULL,                                  /* init thread */
    NULL,                                  /* exit thread */
    NULL,                                  /* exit process */
    NULL,                                  /* exit master */
    NGX_MODULE_V1_PADDING
};

このハンドラコードはアクセスフェーズの間の各リクエストごとに呼ばれます。これを後のコード内のngx_http_auth_request_init関数内のハンドラリストの中でセットアップするでしょう。

static ngx_int_t
ngx_http_auth_request_handler(ngx_http_request_t *r)
{
    ngx_table_elt_t               *h, *ho;
    ngx_http_request_t            *sr;
    ngx_http_post_subrequest_t    *ps;
    ngx_http_auth_request_ctx_t   *ctx;
    ngx_http_auth_request_conf_t  *arcf;

設定から認証リクエストurlディレクティブ設定を取得します。空(ディレクティブ内でoffを設定)の場合、リクエストがチェーン内の次のハンドラに回されなければならないことを意味する NGX_DECLINEDを返します。

arcf = ngx_http_get_module_loc_conf(r, ngx_http_auth_request_module);

if (arcf->uri.len == 0) {
    return NGX_DECLINED;
}

認証のためのサブリクエストが送信されたが応答をまだ受け取っていない場合は、NGINXに次のイベントループで再試行させる NGX_AGAINを送信します。

ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
               "auth request handler");

ctx = ngx_http_get_module_ctx(r, ngx_http_auth_request_module);

if (ctx != NULL) {
    if (!ctx->done) {
        return NGX_AGAIN;
    }

以下のコメントが示すように、内部リダイレクトのために必要とされる変数が設定されます。

/*
 + as soon as we are done - explicitly set variables to make
 + sure they will be available after internal redirects
 */

if (ngx_http_auth_request_set_variables(r, arcf, ctx) != NGX_OK) {
    return NGX_ERROR;
}

そしてサブリクエストのための応答ステータスをチェックします。もしforbiddenであれば、単にこれを返します。unauthorizedであればクライアントに “WWW-Authenticate” ヘッダを送信しunauthorizedステータスを返します。

/* return appropriate status */

if (ctx->status == NGX_HTTP_FORBIDDEN) {
    return ctx->status;
}

if (ctx->status == NGX_HTTP_UNAUTHORIZED) {
    sr = ctx->subrequest;

    h = sr->headers_out.www_authenticate;

    if (!h && sr->upstream) {
        h = sr->upstream->headers_in.www_authenticate;
    }

    if (h) {
        ho = ngx_list_push(&r->headers_out.headers);
        if (ho == NULL) {
            return NGX_ERROR;
        }

        *ho = *h;

        r->headers_out.www_authenticate = ho;
    }

    return ctx->status;
}

応答コードが200から300の間であれば、認証が承認されます。

if (ctx->status >= NGX_HTTP_OK
    && ctx->status < NGX_HTTP_SPECIAL_RESPONSE)
{
    return NGX_OK;
}

If we have got this far then we got an unexpected error code.

ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
              "auth request unexpected status: %d", ctx->status);

return NGX_HTTP_INTERNAL_SERVER_ERROR;

以下のコードのブロックは認証サブリクエストがまだ送信されていない場所です。最初にサブリクエストのためにコンテキストのためのメモリを割り当てる必要があり、そしてサブリクエスト自身のためのメモリを割り当てる必要があります。

}

ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_auth_request_ctx_t));
if (ctx == NULL) {
    return NGX_ERROR;
}

ps = ngx_palloc(r->pool, sizeof(ngx_http_post_subrequest_t));
if (ps == NULL) {
    return NGX_ERROR;
}

ハンドラはサブリクエストが完了した時に呼ばれる関数です。この場合、関数ngx_http_auth_request_doneに設定しています。このコールバックのためのコンテキストデータも設定されます。

ps->handler = ngx_http_auth_request_done;
ps->data = ctx;

これで、設定されたURIと上で設定された変数を使ってサブリクエストが引き起こされます。The NGX_HTTP_SUBREQUEST_WAITED flag serializes subrequests instead of the default of running them in parallel.

if (ngx_http_subrequest(r, &arcf->uri, NULL, &sr, ps,
                        NGX_HTTP_SUBREQUEST_WAITED)
    != NGX_OK)
{
    return NGX_ERROR;
}

幾つかの最終的な設定がサブリクエスト上で変更され、モジュールコンテキストが次の呼び出しで必要とされる情報を使ってこの関数に設定されます。

    /*
     + allocate fake request body to avoid attempts to read it and to make
     + sure real body file (if already read) won't be closed by upstream
     */

    sr->request_body = ngx_pcalloc(r->pool, sizeof(ngx_http_request_body_t));
    if (sr->request_body == NULL) {
        return NGX_ERROR;
    }

    sr->header_only = 1;

    ctx->subrequest = sr;

    ngx_http_set_ctx(r, ctx, ngx_http_auth_request_module);

    return NGX_AGAIN;
}

この関数は上の関数内で設定されたサブリクエストの完了によって引き起こされるコールバックです。

適切な応答をするために、ngx_http_auth_request_handler によって読み込まれるctxデータを設定します。

static ngx_int_t
ngx_http_auth_request_done(ngx_http_request_t *r, void *data, ngx_int_t rc)
{
    ngx_http_auth_request_ctx_t   *ctx = data;

    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                   "auth request done s:%d", r->headers_out.status);

    ctx->done = 1;
    ctx->status = r->headers_out.status;

    return rc;
}

この関数はメインのリクエスト内でサブリクエストからの変数を格納することを目的としています。

static ngx_int_t
ngx_http_auth_request_set_variables(ngx_http_request_t *r,
    ngx_http_auth_request_conf_t *arcf, ngx_http_auth_request_ctx_t *ctx)
{
    ngx_str_t                          val;
    ngx_http_variable_t               *v;
    ngx_http_variable_value_t         *vv;
    ngx_http_auth_request_variable_t  *av, *last;
    ngx_http_core_main_conf_t         *cmcf;

    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                   "auth request set variables");

    if (arcf->vars == NULL) {
        return NGX_OK;
    }

    cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);
    v = cmcf->variables.elts;

    av = arcf->vars->elts;
    last = av + arcf->vars->nelts;

    while (av < last) {
        /*
         + explicitly set new value to make sure it will be available after
         + internal redirects
         */

        vv = &r->variables[av->index];

        if (ngx_http_complex_value(ctx->subrequest, &av->value, &val)
            != NGX_OK)
        {
            return NGX_ERROR;
        }

        vv->valid = 1;
        vv->not_found = 0;
        vv->data = val.data;
        vv->len = val.len;

        if (av->set_handler) {
            /*
             + set_handler only available in cmcf->variables_keys, so we store
             + it explicitly
             */

            av->set_handler(r, vv, v[av->index].data);
        }

        av++;
    }

    return NGX_OK;
}

新しい変数がauth_request_setディレクティブを使って指定される場合、関数ngx_http_auth_request_set` が呼ばれます。同様にこれはその変数のためのgetハンドラを初期化するために以下の関数を呼びます。

static ngx_int_t
ngx_http_auth_request_variable(ngx_http_request_t *r,
    ngx_http_variable_value_t *v, uintptr_t data)
{
    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                   "auth request variable");

    v->not_found = 1;

    return NGX_OK;
}

この関数は設定初期化で呼ばれます。変数を保持するために必要とされるメモリを割り当てます。

static void *
ngx_http_auth_request_create_conf(ngx_conf_t *cf)
{
    ngx_http_auth_request_conf_t  *conf;

    conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_auth_request_conf_t));
    if (conf == NULL) {
        return NULL;
    }

    /*
     + set by ngx_pcalloc():
     *
     +     conf->uri = { 0, NULL };
     */

    conf->vars = NGX_CONF_UNSET_PTR;

    return conf;
}

設定ディレクティブは設定ブロックの異なるレベルで使うことができます。このマージ関数はディレクティブが子に至るまでマージされることを保証します。

static char *
ngx_http_auth_request_merge_conf(ngx_conf_t *cf, void *parent, void *child)
{
    ngx_http_auth_request_conf_t *prev = parent;
    ngx_http_auth_request_conf_t *conf = child;

    ngx_conf_merge_str_value(conf->uri, prev->uri, "");
    ngx_conf_merge_ptr_value(conf->vars, prev->vars, NULL);

    return NGX_CONF_OK;
}

モジュールの初期化の間、この関数はngx_http_auth_request_handlerを差し込むために呼ばれます。

static ngx_int_t
ngx_http_auth_request_init(ngx_conf_t *cf)
{
    ngx_http_handler_pt        *h;
    ngx_http_core_main_conf_t  *cmcf;

フェーズハンドラがHTTPコアモジュール設定に格納されているため、HTTPコアモジュール設定を取得します。

cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);

新しいエントリがアクセスフェーズハンドラに生成され、この新しいエントリへのポインタが返されます。

h = ngx_array_push(&cmcf->phases[NGX_HTTP_ACCESS_PHASE].handlers);
if (h == NULL) {
    return NGX_ERROR;
}

新しいハンドラはリクエストハンドラ関数へのポインタに設定されます。これでアクセスフェーズの間に呼ばれる関数のチェーン内に含まれます。

    *h = ngx_http_auth_request_handler;

    return NGX_OK;
}

This function is called to process the auth_request directive when set and validates it accordingly.

static char *
ngx_http_auth_request(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
    ngx_http_auth_request_conf_t *arcf = conf;

    ngx_str_t        *value;

このブロックのためのauth_request ディレクティブが既にある場合は、このことを示すエラーが返します。

if (arcf->uri.data != NULL) {
    return "is duplicate";
}

value = cf->args->elts;

auth_request ディレクティブがoff に設定されている場合、それを無効にします。

if (ngx_strcmp(value[1].data, "off") == 0) {
    arcf->uri.len = 0;
    arcf->uri.data = (u_char *) "";

    return NGX_CONF_OK;
}

そうでなければ、ディレクティブの値を格納します。

    arcf->uri = value[1];

    return NGX_CONF_OK;
}

This function is called to process the auth_request_set directive when set and validates it accordingly.

static char *
ngx_http_auth_request_set(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
    ngx_http_auth_request_conf_t *arcf = conf;

    ngx_str_t                         *value;
    ngx_http_variable_t               *v;
    ngx_http_auth_request_variable_t  *av;
    ngx_http_compile_complex_value_t   ccv;

    value = cf->args->elts;

設定しようとしている変数が$ で始まらないバイアは、エラーを投げます。そして変数名を使うために$ をスキップします。

if (value[1].data[0] != '$') {
    ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                       "invalid variable name \"%V\"", &value[1]);
    return NGX_CONF_ERROR;
}

value[1].len--;
value[1].data++;

まだ認証リクエスト変数が無い場合は、配列を生成します。

if (arcf->vars == NGX_CONF_UNSET_PTR) {
    arcf->vars = ngx_array_create(cf->pool, 1,
                                  sizeof(ngx_http_auth_request_variable_t));
    if (arcf->vars == NULL) {
        return NGX_CONF_ERROR;
    }
}

認証リクエスト変数の配列内に新しい変数を生成し、新しいエントリへのポインタを取得します。

av = ngx_array_push(arcf->vars);
if (av == NULL) {
    return NGX_CONF_ERROR;
}

これで、定義された名前を使って変数自身を生成し、それを変更可能な変数に設定します。

v = ngx_http_add_variable(cf, &value[1], NGX_HTTP_VAR_CHANGEABLE);
if (v == NULL) {
    return NGX_CONF_ERROR;
}

新しい変数は生成した認証リクエスト変数へ所属します。

av->index = ngx_http_get_variable_index(cf, &value[1]);
if (av->index == NGX_ERROR) {
    return NGX_CONF_ERROR;
}

変数のためのgetハンドラがまだ無い場合は設定されます。

if (v->get_handler == NULL) {
    v->get_handler = ngx_http_auth_request_variable;
    v->data = (uintptr_t) av;
}

av->set_handler = v->set_handler;

変数のための値がコンパイルされ格納されます。

    ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));

    ccv.cf = cf;
    ccv.value = &value[2];
    ccv.complex_value = &av->value;

    if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
        return NGX_CONF_ERROR;
    }

    return NGX_CONF_OK;
}
TOP
inserted by FC2 system