这是一个可以让nginx的请求具备认证功能的模块。它可以做很多极具创造力的功能,这可能是我最推荐的一个模块。现在它已经是nginx内置的模块,只是默认是未开启的。曾经它也是第三方模块,其作者现在是nginx源码维护者之一。有着如此渊源和优秀使其被nginx收录,自然是情理之中。
在我们经历过的项目中,用户登陆认证是个非常常见的功能点。我们可能有很多的子项目,这些子项目有着共同的功能,
那就是都需要经过登陆认证后才能访问。作为有经验的你,希望把它独立出来。可能你已经抽象出健壮的库,让每个子系统直接调用它。当然这是很好的方案,但我们想让开发人员的日子更舒服些,他们不用关心是否有认证,甚至在代码中看不到任何认证的影子,架构师就已经神奇的帮他们搞定了这块。
1、先来看如何实现它
> ./configure --with-http_auth_request_module && make && make install
server {
listen 80;
root html;
location / {
index index.html index.php;
}
#这个将在下面中被使用
location /auth {
proxy_pass http://xx.xx.xx.xx; #你可以用proxy_pass
#return 200;#可以直接让它通过
#其它也可以,只要让这个location正常使用。
}
location ~ \.php$ {
auth_request /auth; #这里将使专有php具备认证功能
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
}
你要做的就是提供个 http://xx.xx.xx.xx 接口。
200:认证成功
403:认证失败
401:很特殊
xxx:其它有问题
2、让你成为专家
如果仅仅这样,当然不足以把它称为创造力。我们将看它的实现,这样你可以做更多有想法的事。我在nginx的源码分析系列里提过,nginx有个神奇的机制叫子请求(subrequest)。故名思义,子请求是从请求里发出的内部请求,至于这个内部请求是处理简单的路径,比如/test.html,还是具备远程处理能力/test.php,完全由你决定,而且它永远不会阻塞,这是非常重要的,这也是我们用接口的方式独立出认证功能的优势,相比代码的阻塞,它在高并发下效果更好。
现在开始分析它的实现:src/http/modules/ngx_http_auth_request_module.c
a、先看指令,只有两个
"auth_request" => ngx_http_auth_request(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
arcf->uri = value[1]; 这里有个细节,这样的实现使其不能使用变量。我已经有个具备变量的代码。
}
"auth_request_set" 这个指令是在请求通过认证后作变量的设置,这个很不错的,先压着。
b、阶段设置
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);
if (h == NULL) {
return NGX_ERROR;
}
*h = ngx_http_auth_request_handler;
return NGX_OK;
}
subrequest的源码有点复杂,将用新篇分析它,这里不作分析,auth_request是依赖于sub_request的。只需知道
ngx_http_auth_request_handler代码里只要返回 return NGX_AGAIN;这函数会一直被调用(一直这个词有点吓你,
想象它不会放过fastcgi的handler,一直在fastcgi之前调用)。因为auth_request发生在ACCESS阶段, fastcgi在CONTENT阶段,ACCESS在CONTENT之前。
3、请求处理 ngx_http_auth_request_handler
static ngx_int_t
ngx_http_auth_request_handler(ngx_http_request_t *r)
{
...
ctx = ngx_http_get_module_ctx(r, ngx_http_auth_request_module);
if (ctx != NULL) {
/* 一直拒绝往fastcgi走,直到done */
if (!ctx->done) {
return NGX_AGAIN;
}
/* 调用顺序:3 有时阅读文档不如阅读源码 */
/* 设置auth_request_set的变量 */
if (ngx_http_auth_request_set_variables(r, arcf, ctx) != NGX_OK) {
return NGX_ERROR;
}
/* 如果是403,当作失败*/
if (ctx->status == NGX_HTTP_FORBIDDEN) {
return ctx->status;
}
/* 这是401,有兴趣自己去搜索下,这功能还是很实用的 */
if (ctx->status == NGX_HTTP_UNAUTHORIZED) {
sr = ctx->subrequest;
...
return ctx->status;
}
/* 2xx 当作成功 */
if (ctx->status >= NGX_HTTP_OK
&& ctx->status < NGX_HTTP_SPECIAL_RESPONSE)
{
return NGX_OK;
}
/* 其它当作失败 */
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
...
ps = ngx_palloc(r->pool, sizeof(ngx_http_post_subrequest_t));
ps->handler = ngx_http_auth_request_done;
ps->data = ctx;
/* 调用顺序:1 这里发出子请求*/
ngx_http_subrequest(r, &arcf->uri, NULL, &sr, ps, NGX_HTTP_SUBREQUEST_WAITED);
...
ctx->subrequest = sr;
return NGX_AGAIN;
}
/* 调用顺序:2 设置返回结果 */
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;
ctx->done = 1;
ctx->status = r->headers_out.status;
return rc;
}
static ngx_int_t
ngx_http_auth_request_handler(ngx_http_request_t *r)
{
...
ctx = ngx_http_get_module_ctx(r, ngx_http_auth_request_module);
if (ctx != NULL) {
/* 一直拒绝往fastcgi走,直到done */
if (!ctx->done) {
return NGX_AGAIN;
}
/* 调用顺序:3 有时阅读文档不如阅读源码 */
/* 设置auth_request_set的变量 */
if (ngx_http_auth_request_set_variables(r, arcf, ctx) != NGX_OK) {
return NGX_ERROR;
}
/* 如果是403,当作失败*/
if (ctx->status == NGX_HTTP_FORBIDDEN) {
return ctx->status;
}
/* 这是401,有兴趣自己去搜索下,这功能还是很实用的 */
if (ctx->status == NGX_HTTP_UNAUTHORIZED) {
sr = ctx->subrequest;
...
return ctx->status;
}
/* 2xx 当作成功 */
if (ctx->status >= NGX_HTTP_OK
&& ctx->status < NGX_HTTP_SPECIAL_RESPONSE)
{
return NGX_OK;
}
/* 其它当作失败 */
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
...
ps = ngx_palloc(r->pool, sizeof(ngx_http_post_subrequest_t));
ps->handler = ngx_http_auth_request_done;
ps->data = ctx;
/* 调用顺序:1 这里发出子请求*/
ngx_http_subrequest(r, &arcf->uri, NULL, &sr, ps, NGX_HTTP_SUBREQUEST_WAITED);
...
ctx->subrequest = sr;
return NGX_AGAIN;
}
/* 调用顺序:2 设置返回结果 */
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;
ctx->done = 1;
ctx->status = r->headers_out.status;
return rc;
}
到这里已经没有任何秘密了。结合返回值和auth_request_set可以做很多更有趣的,比如我写的那篇nginx动态代理。
甚至你可以拿它写成更适合你的模块。期待您的分享。
本文结束!