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認證和校驗的實現原理。