Nginx的auth_basic模块详解

1.相关指令

1
2
3
4
5
6
7
8
9
Syntax:	auth_basic string | off;
Default:
auth_basic off;
Context: http, server, location, limit_except

Syntax: auth_basic_user_file file;
Default: —
Context: http, server, location, limit_except

2.配置解析函数

首先看下指令auth_basic的配置解析函数ngx_http_set_complex_value_slot,这是个通用的函数,这里不再赘述
接下来看下指令auth_basic_user_file的配置解析函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
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 != NGX_CONF_UNSET_PTR) {
return "is duplicate";
}

//申请内存
alcf->user_file = ngx_palloc(cf->pool, sizeof(ngx_http_complex_value_t));
if (alcf->user_file == NULL) {
return NGX_CONF_ERROR;
}

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

3.handler函数

1.接下来看下ngx_http_auth_basic_handler的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
static ngx_int_t
ngx_http_auth_basic_handler(ngx_http_request_t *r)
{
off_t offset;
ssize_t n;
ngx_fd_t fd;
ngx_int_t rc;
ngx_err_t err;
ngx_str_t pwd, realm, user_file;
ngx_uint_t i, level, login, left, passwd;
ngx_file_t file;
ngx_http_auth_basic_loc_conf_t *alcf;
u_char buf[NGX_HTTP_AUTH_BUF_SIZE];
enum {
sw_login,
sw_passwd,
sw_skip
} state;

alcf = ngx_http_get_module_loc_conf(r, ngx_http_auth_basic_module);

if (alcf->realm == NULL || alcf->user_file == NULL) {
return NGX_DECLINED;
}

// 编译变量
if (ngx_http_complex_value(r, alcf->realm, &realm) != NGX_OK) {
return NGX_ERROR;
}

if (realm.len == 3 && ngx_strncmp(realm.data, "off", 3) == 0) {
return NGX_DECLINED;
}
// 从authorization header中解析出user以及password
rc = ngx_http_auth_basic_user(r);
// 没有解析出user password, 添加必要header,返回401
if (rc == NGX_DECLINED) {

ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
"no user/password was provided for basic authentication");

return ngx_http_auth_basic_set_realm(r, &realm);
}

if (rc == NGX_ERROR) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}

// 编译变量
if (ngx_http_complex_value(r, alcf->user_file, &user_file) != NGX_OK) {
return NGX_ERROR;
}

// 打开文件
fd = ngx_open_file(user_file.data, NGX_FILE_RDONLY, NGX_FILE_OPEN, 0);
// 打开失败
if (fd == NGX_INVALID_FILE) {
err = ngx_errno;

if (err == NGX_ENOENT) {
level = NGX_LOG_ERR;
rc = NGX_HTTP_FORBIDDEN;

} else {
level = NGX_LOG_CRIT;
rc = NGX_HTTP_INTERNAL_SERVER_ERROR;
}

ngx_log_error(level, r->connection->log, err,
ngx_open_file_n " \"%s\" failed", user_file.data);

return rc;
}

ngx_memzero(&file, sizeof(ngx_file_t));

file.fd = fd;
file.name = user_file;
file.log = r->connection->log;

state = sw_login;
passwd = 0;
login = 0;
left = 0;
offset = 0;

for ( ;; ) {
i = left;
// 读文件
n = ngx_read_file(&file, buf + left, NGX_HTTP_AUTH_BUF_SIZE - left,
offset);

if (n == NGX_ERROR) {
rc = NGX_HTTP_INTERNAL_SERVER_ERROR;
goto cleanup;
}
// 文件读完
if (n == 0) {
break;
}
// 状态机处理
for (i = left; i < left + n; i++) {
switch (state) {

case sw_login:
if (login == 0) {
// 跳过注释
if (buf[i] == '#' || buf[i] == CR) {
state = sw_skip;


break;
}
// 跳过换行符
if (buf[i] == LF) {
break;
}
}
// 长度不匹配,直接跳过
if (buf[i] != r->headers_in.user.data[login]) {
state = sw_skip;
break;
}
//username匹配完成
if (login == r->headers_in.user.len) {
state = sw_passwd;
passwd = i + 1;
}

login++;

break;
//开始解析密码
case sw_passwd:
if (buf[i] == LF || buf[i] == CR || buf[i] == ':') {
buf[i] = '\0';

pwd.len = i - passwd;
pwd.data = &buf[passwd];
//处理密码
rc = ngx_http_auth_basic_crypt_handler(r, &pwd, &realm);
goto cleanup;
}

break;

case sw_skip:
if (buf[i] == LF) {
state = sw_login;
login = 0;
}

break;
}
}
//密码没有解析完,继续解析
if (state == sw_passwd) {
left = left + n - passwd;
ngx_memmove(buf, &buf[passwd], left);
passwd = 0;

} else {
left = 0;
}
// 偏移n
offset += n;
}
// 解析到密码段
if (state == sw_passwd) {
pwd.len = i - passwd;
pwd.data = ngx_pnalloc(r->pool, pwd.len + 1);
if (pwd.data == NULL) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
//拷贝密码
ngx_cpystrn(pwd.data, &buf[passwd], pwd.len + 1);
//对密码进行处理
rc = ngx_http_auth_basic_crypt_handler(r, &pwd, &realm);
goto cleanup;
}

ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
"user \"%V\" was not found in \"%s\"",
&r->headers_in.user, user_file.data);
// 调用函数,设置auth header
rc = ngx_http_auth_basic_set_realm(r, &realm);

cleanup:
// 关闭文件
if (ngx_close_file(file.fd) == NGX_FILE_ERROR) {
ngx_log_error(NGX_LOG_ALERT, r->connection->log, ngx_errno,
ngx_close_file_n " \"%s\" failed", user_file.data);
}

ngx_explicit_memzero(buf, NGX_HTTP_AUTH_BUF_SIZE);

return rc;
}


接下来看下ngx_http_auth_basic_crypt_handler函数实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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;
// 使用header中的passwd作为key进行编码
rc = ngx_crypt(r->pool, r->headers_in.passwd.data, passwd->data,
&encrypted);

ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"rc: %i user: \"%V\" salt: \"%s\"",
rc, &r->headers_in.user, passwd->data);

if (rc != NGX_OK) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
// 比较编码后的字符串
if (ngx_strcmp(encrypted, passwd->data) == 0) {
return NGX_OK;
}

ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"encrypted: \"%s\"", encrypted);

ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
"user \"%V\": password mismatch",
&r->headers_in.user);

return ngx_http_auth_basic_set_realm(r, realm);
}

接下来看看ngx_http_auth_basic_set_realm的函数实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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;
// 从headers list中获取一个元素
r->headers_out.www_authenticate = ngx_list_push(&r->headers_out.headers);
if (r->headers_out.www_authenticate == NULL) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
//初始化header len
len = sizeof("Basic realm=\"\"") - 1 + realm->len;
// 申请内存
basic = ngx_pnalloc(r->pool, len);
if (basic == NULL) {
r->headers_out.www_authenticate->hash = 0;
r->headers_out.www_authenticate = NULL;
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
//复制header
p = ngx_cpymem(basic, "Basic realm=\"", sizeof("Basic realm=\"") - 1);
p = ngx_cpymem(p, realm->data, realm->len);
*p = '"';
// 设置header
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;
//返回401
return NGX_HTTP_UNAUTHORIZED;
}

以上就是这个auth-basic模块的实现