ngx_http_auth_basic_module源碼解析(基於nginx1.17.9)

auth_basic模塊是nginx中比較簡單的模塊。地址在http://nginx.org/en/docs/http/ngx_http_auth_basic_module.html。我們通過分析這個模塊的代碼,不僅知道如何使用,還可以瞭解到http認證的實現。該模塊支持http認證和驗證的功能。支持兩個配置。

location / {
	// 在瀏覽器彈框裏的提示語
    auth_basic           "hello";
    // 用戶輸入密碼後,以password文件來判斷是否正確
    auth_basic_user_file /usr/local/nginx/conf/password;
}

密碼文件格式如下

name:password

1 模塊使用

我們先看一下使用。該模塊對密碼格式有一定的要求,具體可參考文檔。我們可以在https://tool.lu/htpasswd/網站生成密碼,然後粘貼到我們的密碼文件。比如我的機器下,密碼文件路徑是/usr/local/nginx/conf/password。內容是

theanarkh:MTTuFPm3y4m2o

打開瀏覽器輸入服務器地址。查看回包。

這時候瀏覽器會彈出一個輸入用戶名密碼的彈框。用戶輸入後,瀏覽器再次請求服務器。我們看一下請求頭

這時候nginx會到password文件去匹配用戶輸入的用戶名和密碼。完成驗證。

2 模塊原理

auth_basic模塊是http模塊,首先看一下配置

static ngx_command_t  ngx_http_auth_basic_commands[] = {

    { ngx_string("auth_basic"),
      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LMT_CONF
                        |NGX_CONF_TAKE1,
      ngx_http_set_complex_value_slot,
      NGX_HTTP_LOC_CONF_OFFSET,
      offsetof(ngx_http_auth_basic_loc_conf_t, realm),
      NULL },

    { ngx_string("auth_basic_user_file"),
      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LMT_CONF
                        |NGX_CONF_TAKE1,
      ngx_http_auth_basic_user_file,
      NGX_HTTP_LOC_CONF_OFFSET,
      offsetof(ngx_http_auth_basic_loc_conf_t, user_file),
      NULL },

      ngx_null_command
};


static ngx_http_module_t  ngx_http_auth_basic_module_ctx = {
    NULL,                                  /* preconfiguration */
    ngx_http_auth_basic_init,              /* postconfiguration */

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

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

    ngx_http_auth_basic_create_loc_conf,   /* create location configuration */
    ngx_http_auth_basic_merge_loc_conf     /* merge location configuration */
};


ngx_module_t  ngx_http_auth_basic_module = {
    NGX_MODULE_V1,
    &ngx_http_auth_basic_module_ctx,       /* module context */
    ngx_http_auth_basic_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
};

瞭解nginx模塊基本原理的同學對上面的代碼應該會比較熟悉。這裏就不具體分析。我們從nginx初始化流程到用戶請求的順序分析上面的函數。
1 ngx_http_auth_basic_create_loc_conf

static void *
ngx_http_auth_basic_create_loc_conf(ngx_conf_t *cf)
{
    ngx_http_auth_basic_loc_conf_t  *conf;

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

    return conf;
}

該函數代碼比較簡單,就是創建一個和該模塊相關的結構體。

struct {
    ngx_http_complex_value_t  *realm;
    ngx_http_complex_value_t   user_file;
} ngx_http_auth_basic_loc_conf_t

這個結構體就是保存nginx支持的兩個配置。該函數在nginx初始化的時候調用,目的是創建保存配置的結構體。
2 註冊nginx access階段的鉤子

static ngx_int_t
ngx_http_auth_basic_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);
    if (h == NULL) {
        return NGX_ERROR;
    }

    *h = ngx_http_auth_basic_handler;

    return NGX_OK;
}

從代碼中我們可以看到該模塊是屬於nginx 11個階段的access階段。即在nginx處理用戶請求時,會調用ngx_http_auth_basic_handler函數。

3 解析nginx,保存用戶的配置
在nginx啓動後,解析到auth_basic,auth_basic_user_file這兩個配置時,就保存到ngx_http_auth_basic_loc_conf_t結構體。比如解析和保存auth_basic_user_file配置。

static char *
ngx_http_auth_basic_user_file(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
    ngx_http_auth_basic_loc_conf_t *alcf = conf;

    ngx_str_t                         *value;
    ngx_http_compile_complex_value_t   ccv;

    if (alcf->user_file.value.data) {
        return "is duplicate";
    }

    value = cf->args->elts;

    ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));
	// 保存用戶配置的文件路徑
    ccv.cf = cf;
    ccv.value = &value[1];
    ccv.complex_value = &alcf->user_file;
    ccv.zero = 1;
    ccv.conf_prefix = 1;

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

    return NGX_CONF_OK;
}

4 處理用戶請求
該模塊的ngx_http_auth_basic_handler是處理用戶請求的,我們看一下核心邏輯。

// 解析http請求頭中的驗證字段
rc = ngx_http_auth_basic_user(r);
// http請求頭沒有驗證頭Basic
if (rc == NGX_DECLINED) {
    return ngx_http_auth_basic_set_realm(r, &realm);
 }
 // 有驗證字段則,打開nginx.conf裏配置的文件,然後讀取內容
 fd = ngx_open_file(user_file.data, NGX_FILE_RDONLY, NGX_FILE_OPEN, 0);
 ngx_read_file(&file, buf + left, NGX_HTTP_AUTH_BUF_SIZE - left,offset);
 // 驗證用戶輸入
 ngx_http_auth_basic_crypt_handler(r, &pwd, &realm);

我們首先看ngx_http_auth_basic_user(解析http驗證頭),然後看ngx_http_auth_basic_crypt_handler(驗證http驗證頭)。最後ngx_http_auth_basic_set_realm(沒有http驗證頭或者校驗失敗則返回瀏覽器401和驗證頭),

ngx_int_t
ngx_http_auth_basic_user(ngx_http_request_t *r)
{
    ngx_str_t   auth, encoded;
    ngx_uint_t  len;
	// 請求頭的authorization字段的值,格式Authorization: Basic xxx
    encoded = r->headers_in.authorization->value;
    // 繞過Basic ,指向密碼第一個字符和最後一個字符
    encoded.len -= sizeof("Basic ") - 1;
    encoded.data += sizeof("Basic ") - 1;
	// 忽略密碼前置空格,後置空格由http解析器處理了
    while (encoded.len && encoded.data[0] == ' ') {
        encoded.len--;
        encoded.data++;
    }
	// auth保存解碼後的內容
    auth.len = ngx_base64_decoded_length(encoded.len);
    auth.data = ngx_pnalloc(r->pool, auth.len + 1);
  	// Authorization頭的內容是經過了base64編碼的,這裏decode一下
    if (ngx_decode_base64(&auth, &encoded) != NGX_OK) {
        r->headers_in.user.data = (u_char *) "";
        return NGX_DECLINED;
    }

    auth.data[auth.len] = '\0';
	// 找到第一個冒號,Authorization頭的內容格式是user:password
    for (len = 0; len < auth.len; len++) {
        if (auth.data[len] == ':') {
            break;
        }
    }
	// 沒有冒號說明是不合法格式
    if (len == 0 || len == auth.len) {
        r->headers_in.user.data = (u_char *) "";
        return NGX_DECLINED;
    }
	// 保存http解析結果,用於後面驗證
    r->headers_in.user.len = len;
    r->headers_in.user.data = auth.data;
    r->headers_in.passwd.len = auth.len - len - 1;
    r->headers_in.passwd.data = &auth.data[len + 1];

    return NGX_OK;
}

上面函數就是http Authorization頭的解析邏輯,我們從中可以看到http驗證的一些實現原理。解析完後就開始驗證。

static ngx_int_t
ngx_http_auth_basic_crypt_handler(ngx_http_request_t *r, ngx_str_t *passwd,
    ngx_str_t *realm)
{
    ngx_int_t   rc;
    u_char     *encrypted;
	
    rc = ngx_crypt(r->pool, r->headers_in.passwd.data, passwd->data,
                   &encrypted);
	// 驗證成功,則返回ok
    if (ngx_strcmp(encrypted, passwd->data) == 0) {
        return NGX_OK;
    }
	// 否則設置驗證頭,返回給瀏覽器
    return ngx_http_auth_basic_set_realm(r, realm);
}

該函數就是從用戶配置的文件中,解析出一系列的用戶名和密碼,然後和瀏覽器傳過來的值對比。如何校驗失敗或者瀏覽器沒有設置驗證頭,則nginx返回驗證頭和401錯誤碼。

static ngx_int_t
ngx_http_auth_basic_set_realm(ngx_http_request_t *r, ngx_str_t *realm)
{
    size_t   len;
    u_char  *basic, *p;
	// 設置http協議回頭中的頭
    r->headers_out.www_authenticate = ngx_list_push(&r->headers_out.headers);
    
    len = sizeof("Basic realm=\"\"") - 1 + realm->len;

    basic = ngx_pnalloc(r->pool, len);
    p = ngx_cpymem(basic, "Basic realm=\"", sizeof("Basic realm=\"") - 1);
    p = ngx_cpymem(p, realm->data, realm->len);
    *p = '"';

    r->headers_out.www_authenticate->hash = 1;
    ngx_str_set(&r->headers_out.www_authenticate->key, "WWW-Authenticate");
    r->headers_out.www_authenticate->value.data = basic;
    r->headers_out.www_authenticate->value.len = len;

    return NGX_HTTP_UNAUTHORIZED;
}

該函數設置http回包裏的驗證頭。告訴瀏覽器,需要輸入用戶名和密碼。

總結:ngx_http_auth_basic_module模塊實現了http認證和驗證的功能,我們通過分析該模塊的代碼,不僅瞭解瞭如何使用nginx,還可以瞭解到http認證和校驗的實現原理。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章