1. ホーム

nginxサードパーティモジュール ngx_http_auth_request_module を解説します。

2022-03-09 23:36:24

nginxの強力な拡張性とカスタマイズ性は、それ自身の機能の多くがモジュール化されていることを保証するだけでなく、多くの素晴らしいサードパーティモジュールを生み出しています。 ngx_http_auth_request_module はその一つです。

このモジュールは、サーバー内部から返されるサブリクエストの結果に基づいて、ユーザー認証の制御を可能にします。例えば、サブリクエストが 4xx を返した場合はパーミッションがないことを示し、2xx を返した場合はクライアントにリソースを返し、401 エラーが返ってきた場合はサブリクエストの認証ヘッダをクライアントに渡します。このようにして、サーバーの内部ロジックが認証を制御することができ、多くのビジネスロジック層のスクリプトコードを排除することができます (もちろん、ビジネスを扱うときにphpの力を否定することはできません)。

nginx が提供する、サーバー内部でのサブリクエストへのアクセスや制御のための関数を、主に学習用(主に主要なデータ構造やキーコードの注釈)に使って見ましょう。

/*
 * Copyright (C) Maxim Dounin
 */


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


typedef struct {
    ngx_str_t uri; // address of the uri of the subrequest
} ngx_http_auth_request_conf_t;

typedef struct {
    ngx_uint_t done; // mark if the subrequest is done
    ngx_uint_t status; // status code of the subrequest response: 2xx,4xx,5xx...
    ngx_http_request_t *subrequest; //pointer to the subrequest
} ngx_http_auth_request_ctx_t; //context of auth_request module


//handler function for auth_request
static ngx_int_t ngx_http_auth_request_handler(ngx_http_request_t *r);

// function to be called at the end of the subrequest
static ngx_int_t ngx_http_auth_request_done(ngx_http_request_t *r,
    void *data, ngx_int_t rc);
	
// The following three functions are the initial configuration of the module, which is familiar to everyone
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 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_conf_set_str_slot,
      NGX_HTTP_LOC_CONF_OFFSET,
      offsetof(ngx_http_auth_request_conf_t, uri),
      NULL },

      ngx_null_command
};


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


static ngx_int_t
ngx_http_auth_request_handler(ngx_http_request_t *r)
{
    ngx_table_elt_t *h, *ho; //nginx a table structure that can be treated as a simple associative structure key -> value
    ngx_http_request_t *sr; //subrequest
    ngx_http_post_subrequest_t *ps; // callback structure at the end of the subrequest, which can be registered as a callback function
    ngx_http_auth_request_ctx_t *ctx; //context of the parent request, can hold some intermediate content
    ngx_http_auth_request_conf_t *arcf; //Auth_request configuration information

    arcf = ngx_http_get_module_loc_conf(r, ngx_http_auth_request_module);

    if (arcf->uri.len == 0) { //if there is no command configuring this module, then return N
            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); //headers array structure, call ngx_list_push to add a new element
                if (ho == NULL) {
                    return NGX_ERROR;
                }

                *ho = *h;

                r->headers_out.www_authenticate = ho;
            }

            return ctx->status;
        }

        if (ctx->status >= NGX_HTTP_OK // subrequest authentication passed then return correct resource
            && ctx->status < NGX_HTTP_SPECIAL_RESPONSE)
        {
            return NGX_OK;
        }

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

        return NGX_HTTP_INTERNAL_SERVER_ERROR;
    }

	//parent request 1st entry ngx_http_auth_request_handler function
    ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_auth_request_ctx_t)); //first time into the parent request, need to initialize the context
    if (ctx == NULL) {
        return NGX_ERROR;
    }

	//initialize the child request callback structure
    ps = ngx_palloc(r->pool, sizeof(ngx_http_post_subrequest_t));
    if (ps == NULL) {
        return NGX_ERROR;
    }

	//register the callback function at the end of the subrequest to mount the parent request's context into the subrequest's data pointer
	//so that the second argument of the ngx_http_auth_request_done function is the parent request's context information
    ps->handler = ngx_http_auth_request_done;
    ps->data = ctx;

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

    sr->header_only = 1; // subrequest returns with only the response header information

    ctx->subrequest = sr; //parent request context records pointer to subrequest

    ngx_http_set_ctx(r, ctx, ngx_http_auth_request_module); //Save the parent request context

    return NGX_AGAIN;
}


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; //get the context of the parent request

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

    ctx->done = 1; // Mark subrequest completed and log subrequest response headers
    ctx->status = r->headers_out.status;

    return rc;
}


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.len = { 0, NULL }
     */

    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, "");

    return NGX_CONF_OK;
}


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;

    cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);

    h = ngx_array_push(&cmcf->phases[NGX_HTTP_ACCESS_PHASE].handlers); //mount to the PHASE stage of ACCESS
    if (h == NULL) {
        return NGX_ERROR;
    }

    *h = ngx_http_auth_request_handler;

    return NGX_OK;
}



以下は、Emilerのコメントの一部を参考にしたものです。 nginxモジュールの先行開発 セクションで、サブリクエストを開始する関数の定義から始まり、サブリクエストについてより詳しく説明します。

ngx_int_t ngx_http_subrequest(ngx_http_request_t *r,
    ngx_str_t *uri, ngx_str_t *args, ngx_http_request_t **psr, 
        ngx_http_post_subrequest_t *ps, ngx_uint_t flags)

*r denotes the parent request
*uri and *args point to the uri address and pass parameters of the subrequest, respectively
**psr pointer to the subrequest
*ps callback structure at the end of the child request
flags e.g. NGX_HTTP_SUBREQUEST_WAITED

次に、ハンドラモジュール (スコープは Location) を解析しているので、ハンドラ関数は子リクエストからは呼び出されません (子リクエストは通常他のロケーションや単なる他のサーバーを訪れるという事実を考えてください)。したがって、親と子リクエストを区別する問題は発生しません。しかし、フィルタに関数を追加したい場合は、次のようにします。

if (r == r->main) { 
    //parent request (r->parent) or ancestor request (r->main)
} else {
    //child request
}

このように、複数のサブリクエストを順番に作ることができないかと考えるかもしれません。

int rc1, rc2, rc3;
rc1 = ngx_http_subrequest(r, uri1, ...) ;
rc2 = ngx_http_subrequest(r, uri2, ...) ;
rc3 = ngx_http_subrequest(r, uri3, ...) ;

nginx はシングルスレッドであり、以下のようなイベントシグナルを通じて個々のリクエストを駆動する必要があるため、Emiler は否定的な答えを返します。

NGX_OKです。サブリクエストは完了しました
NGX_DONE。クライアントがブラウザを閉じるなどして接続をリセットした
NGX_ERROR: 内部エラーが発生しました
NGX_AGAIN: nginx は処理に入る前にシグナルを取得するために時間が必要です。

各サブリクエストの進行状況を知るためには、これらを呼び出し側の関数に適宜返す必要があります。

typedef struct {
    ngx_array_t uris; //array of uris of all subrequests
    int i; // the current subrequest being processed
} my_ctx_t;

static ngx_int_t
ngx_http_multiple_uris_body_filter(ngx_http_request_t *r, ngx_chain_t *in)
{
    my_ctx_t *ctx;
    int rc = NGX_OK;
    ngx_http_request_t *sr;

	// subrequest
    if (r ! = r->main) { 
        return ngx_http_next_body_filter(r, in);
    }

	//parent request, need to send multiple child requests
    ctx = ngx_http_get_module_ctx(r, my_module);
    if (ctx == NULL) {
        /* populate ctx and ctx->uris here */
    }
    while (rc == NGX_OK && ctx->i < ctx->uris.nerts) {
	    // Check the status of each sub-request as it is initiated, and if it doesn't complete, return the result and add ctx->i to say that the next request needs to be sent the next time it comes in
        rc = ngx_http_subrequest(r, &((ngx_str_t *)ctx->uris.elts)[ctx->i++],
            NULL /* args */, &sr, NULL /* cb */, 0 /* flags */);
    }

    return rc; /* NGX_OK/NGX_ERROR/NGX_DONE/NGX_AGAIN */
}



あなたはそれを行う方法(上記の順序ではない)同時に複数の接続を起動したい場合は、Emilerが導入されていない、あなたはnginxを参照することができると述べたssiモジュールが付属して、コードのロジックは非常に複雑ですが、私もssiモジュールコンテキストを見ている待ち変数がある子リクエストと親リクエストの順序を確保するために、非常に明確ではありませんようです。私はあなたのいくつかが出てきて、それを導入することができます願っています。