使用 Lua 编写一个 Nginx 认证模块

标签: lua nginx 认证 | 发表时间:2015-08-01 21:38 | 作者:ronin47
出处:http://www.iteye.com

需求一览

我考虑了几种解决方案,罗列如下:

  • 用一个简单的Python/Flask模块来做代理和验证。
  • 一个使用subrequests做验证的nginx模块(nginx目前可以做到这一点)
  • 使用Lua编写一个nginxren认证模块
 

很显然,给整个系统添加额外请求将执行的不是很好,因为这将会增加延迟(特别是给每一个页面文件都增加一个请求是很让人烦恼的).这就意味着我们把 subrequest模块排除在外了。Python/Flash解决方案好像对nginx支持的也并不好,所以咱也把它排除了。就剩Lua了,当然 nginx对原生化支持得不错的。

因为我不想再扩展的服务器上对每一个请求都做认证,所以我决定生成一些令牌,这样人们就可以将它保存起来,并把它呈现给服务器,然后服务器就让请求通过。 然而,因为Lua模块没有一种保持状态的方式(我已经发现),所以我们不能将令牌随处存储。当你没有更多的内存时,怎样来验证用户所说的话呢?


48
翻译于 2年前

0人顶

 翻译的不错哦!

解决问题

加密签名的方式可是咱的救星!我们可以拿用户的用户名和过期时间数据来给用户添加签名的cookies,这样就能很容易的验证每个用户是谁了,同时我们就不用令牌了。

在nginx中,我们要做的就是直接在指定位置配置access_by_lua_file /our/file.lua,这样这个指定位置就可以保护我们的脚本了。现在,让我们一起来写代码:

 

-- Some variable declarations.
local cookie = ngx.var.cookie_MyToken
local hmac = ""
local timestamp = ""
local timestamp_time = 0

-- Check that the cookie exists.
if cookie == nil or cookie:find(":") == nil then
    -- Internally rewrite the URL so that we serve
    -- /auth/ if there's no cookie.
    ngx.exec("/auth/")
else
    -- If there's a cookie, split off the HMAC signature
    -- and timestamp.
    local divider = cookie:find(":")
    hmac = cookie:sub(divider+1)
    timestamp = cookie:sub(0, divider-1)
end

-- Verify that the signature is valid.
if hmac_sha1("some very secret string", timestamp) ~= hmac or tonumber(timestamp) < os.time() then
    -- If invalid, send to /auth/ again.
    ngx.exec("/auth/")
end
上面的代码可以直接运行。我们用一些明文来签名(这种情况下用的是一个时间戳,当然你可以用任何你想用的),之后我们用密文生成HMAC(哈希信息认证码),然后一个签名就生成了,这样用户就不能篡改为无效信息了。

 

yale8848
yale8848
翻译于 2年前

0人顶

 翻译的不错哦!

当用户试图载入一个资源的时候,我们会检查cookie里面的签名是否有效,如果是,就通过他的请求。反之,我们会把他们重定向到一个发行口令的服务器,这个服务器会验证并且在没有的情况下给予他们一个签名的口令。

明锐的你可能会发现,上面的代码存在时间上的漏洞。如果你没有发现,别难过。嗯,也许会有点难过。

这里是一段Lua的代码,用来比较两个字符串在恒定时间上的等值关系(因而能够阻止任何时间上的攻击,除非我忽视了什么,这极为可能):

function compare_strings(str1, str2)
    -- Constant-time string comparison function.
    local same = true
    for i = 1, #str1 do
        -- If the two strings' lengths are different, sub()
        -- will just return nil for the remaining length.
        c1 = str1:sub(i,i)
        c2 = str2:sub(i,i)
        if c1 ~= c2 then
            same = false
        end
    end
    return same
end
我已经在函数上应用了时间来区分,如我所知,这是一个在恒定时间下的等值字符串。不同长度的字符串会稍稍改变时间,也许是因为子过程sub应用了一个不同 的分支而导致的。而且,c1~=c2分支显然不是恒定时间的,但是在实际中,它相当接近恒定,所以于我们的例子不会有影响。我更倾向于使用XOR操作,从 而确定两个字符串的XOR结果是否为0, 不过Lua似乎不包括二进制位的XOR操作。如果我在这个判断上有误,对于任何纠正我都很感激。
自由之信
自由之信
翻译于 2年前

0人顶

 翻译的不错哦!

口令发行服务器

现在,我们已经写了一些很棒的口令检查代码,所有需要做的,只是写一个服务器来真正的发行这些口令。我本可以用Python以及Flask来写这个服务 器,不过我还是想用Go做一个尝试,因为我是一个计算机语言潮人而且Go看上去“酷”。使用Python大概会快一些,不过我乐意用Go。

这个服务器会弹出一个HTTP基础验证的表单,检查你输入的帐户,如果正确,它会给你一个签名的口令,适合于一个小时的代理服务器访问。这样,你只需要验证外部服务一次,而随后的身份验证的检查将在nginx层面,而且会相当的快。

自由之信
自由之信
翻译于 2年前

0人顶

 翻译的不错哦!

请求处理器

写一个处理器,来弹出一个基本的验证窗体不是很难,但是Go没有完美的文档,所以我必须自己一点点寻猎。其实非常简单,最终,这里就是HTTP基本验证的Go代码:

func handler(w http.ResponseWriter, r *http.Request) {
    if username := checkAuth(r); username == "" {
        w.Header().Set("WWW-Authenticate", `Basic realm="The kingdom of Stavros"`)
        w.WriteHeader(401)
        w.Write([]byte("401 Unauthorized\n"))
    } else {
        fmt.Printf("Authenticated user %v.\n", username)
        token := getToken()
        setTokenCookie(w, token)
        fmt.Fprintf(w, "<html><head><script>location.reload()</script></head></html>")
    }
}
自由之信
自由之信
翻译于 2年前

0人顶

 翻译的不错哦!

设置口令和cookie

一旦我们验证了一个用户之后,我们需要给他们的口令设置一个cookie。我门只需要做我们用Lua做过的同样的事情,如上,只是更加简单,因为Go在标准库里面就包括一个真加密包。这个代码一样很直接明了,即使没有完全文档化:

func getToken() string {
    expiration := int(time.Now().Unix()) + 3600
    mac := hmac.New(sha1.New, []byte("some very secret string"))
    mac.Write([]byte(fmt.Sprintf("%v", expiration)))
    expectedMAC := fmt.Sprintf("%x", mac.Sum(nil))

    return fmt.Sprintf("%v:%s", expiration, expectedMAC)
}

func setTokenCookie(w http.ResponseWriter, token string) {
    rawCookie := fmt.Sprintf("MyToken=%s", token)
    expire := time.Now().Add(time.Hour)
    cookie := http.Cookie{"MyToken",
        token,
        "/",
        ".example.com",
        expire,
        expire.Format(time.UnixDate),
        3600,
        false,
        true,
        rawCookie,
        []string{rawCookie}}
    http.SetCookie(w, &cookie)
}
自由之信
自由之信
翻译于 2年前

0人顶

 翻译的不错哦!

尝试把他们放在一起

来完成我们这一大段美妙的组合,我们只需要一个函数,用来检查由用户提供的验证信息,而且我们做到了!这里是我从一些库里面汲取出来的代码,当前它只是检查一个特定的用户名/密码的组合,所以和第三方的服务的集成就做为留给读者的作业吧:

func checkAuth(r *http.Request) string {
    s := strings.SplitN(r.Header.Get("Authorization"), " ", 2)
    if len(s) != 2 || s[0] != "Basic" {
        return ""
    }

    b, err := base64.StdEncoding.DecodeString(s[1])
    if err != nil {
        return ""
    }
    pair := strings.SplitN(string(b), ":", 2)
    if len(pair) != 2 {
        return ""
    }
    if pair[0] != "username" || pair[1] != "password" {
        return ""
    }
    return pair[0]
}
自由之信
自由之信
翻译于 2年前

0人顶

 翻译的不错哦!

结论

我到目前对于nginx的Lua模块还是有着相当的喜欢。它允许你在web服务器的请求/响应周期里面做一些简单的操作,而且对于某些操作,比如为代理服 务器做验证的检查,是很有意义的。这些事情对于一个不可编程的web服务器,一直很难,因此我们极可能需要写自己的HTTP代理服务。

上面的代码相当的简短,而且优雅,所以我对于上面的所有都感到高兴。我不能确定,这对于响应添加了多少额外的时间,不过,做一个验证是有好处的,我想这将值得去做(而且应该足够快,所以不是一个问题)。

另一个好处就是,你可以仅使用一个在nginxlocationblock里面的单独的directive来开启它,所以没有需要跟踪的配置项。我发现, 总体而言,这是一个非常优雅的解决方案,而且我很高兴的了解到nginx可以让我去做这样的事情,可能是将来我需要去做的。

如果你有任何建言或者是反馈,请留下你的评语(特别是如果我把某些地方给弄错了)。

 

转自:http://www.oschina.net/translate/writing-an-nginx-authentication-module-in-lua



已有 0 人发表留言,猛击->> 这里<<-参与讨论


ITeye推荐



相关 [lua nginx 认证] 推荐:

使用 Lua 编写一个 Nginx 认证模块

- - 开源软件 - ITeye博客
我考虑了几种解决方案,罗列如下:. 用一个简单的Python/Flask模块来做代理和验证. 一个使用subrequests做验证的nginx模块(nginx目前可以做到这一点). 使用Lua编写一个nginxren认证模块. 很显然,给整个系统添加额外请求将执行的不是很好,因为这将会增加延迟(特别是给每一个页面文件都增加一个请求是很让人烦恼的).这就意味着我们把 subrequest模块排除在外了.

nginx+lua+memcache实现灰度发布

- - 开源软件 - ITeye博客
灰度发布在百度百科中解释:. 灰度发布是指在黑与白之间,能够平滑过渡的一种发布方式. AB test就是一种灰度发布方式,让一部分用户继续用A,一部分用户开始用B,如果用户对B没有什么反对意见,那么逐步扩大范围,把所有用户都迁移到B上面 来. 灰度发布可以保证整体系统的稳定,在初始灰度的时候就可以发现、调整问题,以保证其影响度.

使用varnish + nginx + lua搭建网站的降级系统

- - 博学无忧
通常一个网站数据库挂掉后,后果将是非常严重的. 对于一些网站来说,当数据库挂掉后,如果能提供基本的浏览服务,也是不错的. 本文将尝试使用varnish + nginx + lua 搭建网站降级系统来实现整个目标. 降级方案的目标是,当网站出现致命故障时(如出现500错误,不能提供服务),可以把缓存的页面数据展现给用户.

基于Lua+Kafka+Heka的Nginx Log实时监控系统

- - 开源软件 - ITeye博客
在我们的系统架构中,Nginx作为所有HTTP请求的入口,是非常重要的一层. 每天产生大量的Nginx Access Log,闲置在硬盘上实在是太浪费资源了. 所以,能不能把Nginx日志利用起来,实时监控每个业务的访问趋势、用户行为、请求质量和后端异常呢,这就是本文要探讨的主题. 错误码告警(499、500、502和504);.

Nginx 通过 Lua + Redis 实现动态封禁 IP

- - 编程学习网
为了封禁某些爬虫或者恶意用户对服务器的请求,我们需要建立一个动态的 IP 黑名单. 对于黑名单之内的 IP ,拒绝提供服务. 实现 IP 黑名单的功能有很多途径:. 1、在操作系统层面,配置 iptables,拒绝指定 IP 的网络请求;. 2、在 Web Server 层面,通过 Nginx 自身的 deny 选项 或者 lua 插件 配置 IP 黑名单;.

nginx 配置 https 的双向认证

- - 操作系统 - ITeye博客
SSL 的双向认证就是,客户端要获取服务端的证书,检查下服务端是不是我可以信任的主机,否则我就认为那个站点的内容不可信任,不应该去访问你(浏览器会告诉你),同时服务端也要检查客户端的证书,客户端如果不是服务端所信任的,那服务端也会认为,你不是我的合法用户,我拒绝给你提供服务. 所以,要让 HTTPS 的双向认证顺利完成,就要在服务端给定一个证书,这个证书是浏览器可信任的,同时客户端(浏览器)也要发送给服务端一个证书,服务器端也要信任这个证书.

图文:CentOS 下对 Nginx + Tomcat 配置 SSL 实现服务器 / 客户端双向认证

- - CSDN博客推荐文章
1.1 nginx 包及其依赖包下载. 出于模块的依赖性,Nginx 依赖以下三个包:. gzip 模块需要 zlib 库( http://www.zlib.net/);. rewrite 模块需要 pcre 库( http://www.pcre.org/);. ssl 功能需要 openssl 库( http://www.openssl.org/);.

5本Lua免费电子书

- sospartan - 读写网 ReadWriteWeb
在最新的编程语言排名中,Lua超过了JavaScript进入了前十名──许多人使用Lua进行“魔兽世界”的脚本编写. 所以,在本周的免费资源推荐中我们找到了一些免费的学习Lua的电子书,无论你想使.

Lua 下实现抢占式多线程

- Coder(码农) - 云风的 BLOG
Lua 5.2 的开发进度可以回溯到 2010 年 1 月. 漫长的流程到今天已经快两年过去,终于等到了 beta 版. 我十分期待它可以在 2011 年内正式发布. 在这几经折腾的两年里,许多新特性企图挤进 5.2 版,又最终被否决. 当我们审视改进列表,似乎看不到太多耳目一新的东西. 但如果仔细阅读一下源代码,就会发现,大部分地方都重新实现过了,以配合这些表面上看起来不大的修改.

编写高性能的Lua代码

- - 九点 科技
Lua是一门以其性能著称的脚本语言,被广泛应用在很多方面,尤其是游戏. 像《魔兽世界》的插件,手机游戏《大掌门》《神曲》《迷失之地》等用Lua来写游戏逻辑. 所以大部分时候我们不需要去考虑性能问题. Knuth有句名言:“过早优化是万恶之源”. 其意思就是过早优化是不必要的,会浪费大量时间,而且容易导致代码混乱.