[入门]使用Openresty构建认证网关

在单体应用中, 我们可以通过 cookie + session, 或者 JSON web token, 将认证逻辑在单体应用中实现, 简单高效, 还特别省事.

然而这几年随着服务化潮流越来越火(我觉得这是必然趋势, 想想我们人类社会是如何运作的), 很多以前单体应用不存在的问题, 现在已成为对单体应用拆分过程中的第一个障碍, 比如系统的认证体系.

如果每个拆出来的服务都要做一次认证(就是程序员多写几份认证的代码啦), 对于有理想有追求的灵魂の码农来说, 是绝对无法接受的.你说认证代码copy就好了, 不用重新写.no no no, 这样搞出来的架构不仅看着别扭, 代码闻着就觉得臭, 而且迟早有一天会出问题.

解决单体应用拆分服务后的认证问题其实很常规, 回想下祖师爷们帮我们总结的一句话: "Any problem in computer science can be solved by another layer of indirection." 我们可以在所有服务前面增加一层认证服务.

[入门]使用Openresty构建认证网关_第1张图片
Paste_Image.png

看到认证服务这一层用来作为用户请求的总入口, 有Nginx或者Apache使用经验的同学自然而然就想到它们. 如果能把认证模块的功能整合进Nginx或者Apache这些Web服务器, 那岂不是更完美 ?

而这篇文章的主角: Openresty,就可以帮助我们简单快速得完成这个想法.这是一个由春哥(Github)发起的项目.你可以将Openresty看做Nginx + 常用模块构成的软件包, 但是最重要的功能是我们可以使用Lua在Nginx实现Web框架才能实现的逻辑, 接下来文章将会开始介绍如何使用Openresty, 将上面提到的认证服务整合进Nginx里.

安装

Openresty有两种安装方式, 一种是使用源码编译安装.一种使用官方提供的预编译包:
具体可参考官网的安装文档

Hello world

如果你没修改过Openresty的安装位置, 默认会被安装在/use/local/openresty目录下.我们现在可以尝试写一个Hello world级别的demo.

为Openresty创建工作目录并创建配置文件:

mkdir ~/openresty_work
cd ~/openresty_work
touch nginx.conf

接下来在nginx.conf里面配置一个路由规则

worker_processes 1;
error_log logs/error.log;

events {
    worker_connections 1024;
}

http {
    server {
        listen 8080;
        location /hello {
            default_type text/html;
            content_by_lua_block {
                ngx.say("Hello Openresty.")
            }           
        }
    }
}

跟普通的Nginx配置文件比起来, 上面的配置多了一个content_by_lua_block指令, 正是通过调用该指令, 访问该路由的时候,才会输出相应的内容.这个指令是由Openresty中的LuaNginxModule模块提供的功能, 请求进来的时候, Nginx会启动lua的虚拟机, 输出的内容则由lua提供.

我们可以使用content_by_lua_file指令替代content_by_lua_block, 将相关的lua代码写进文件里.

location /hello {
            content_by_lua_file lua/hello.lua;
        }

--- hello.lua
ngx.say("Hello Openresty.")

有了上面的铺垫,接下来可以开始构建我们的认证服务,认证的方式使用JWT

Openresty将一个请求的生命周期划分为4个阶段:

[入门]使用Openresty构建认证网关_第2张图片
Paste_Image.png

我们的认证服务将会挂载在第二阶段, 即 Rewrite/Access Phase 上.

接下来准备一个需要用到的库:
lua-resty-jwt
clone下来后放到hello.lua文件所在的文件夹,并将lua_package_path配置为:

lua_package_path "/root/openresty_work/lua/?.lua;/root/openresty_work/lua/lua-resty-jwt/lib/?.lua;;";

构建的思路也很简单, 对用户提供一个登录请求, 验证身份后将jwt token分发给用户.用户接下来访问需要认证的接口, 则在header里面加入该token, 请求进入Openresty后由lua提取出token进行认证.

Nginx配置文件

server {
                listen 8080;
                location /hello {
                        content_by_lua_file lua/hello.lua;
                }
                location /login {
                        content_by_lua_file lua/sign.lua;
                }
                location /service1 {
                        access_by_lua_file lua/verify.lua;
                        # 需要反向代理在这配置
                }
                location /service {
                        access_by_lua_file lua/verify.lua;
                        # ...
                }
        }

下面是配置中相关的lua文件
sign.lua ↓:

local jwt = require 'resty.jwt'

-- 只允许POST请求
if ngx.req.get_method() ~= 'POST' then
    ngx.status = 405
    ngx.say("Mehtod Not Allow")
    return
end

-- 获取请求body
ngx.req.read_body()
local body_raw = ngx.req.get_body_data()
local body_json = cjson.decode(body_raw)
local username = body_json['username']
local password = body_json['password']

if not username or not password then
    ngx.log(ngx.ERR, username, password)
    ngx.status = 400
    ngx.say('无法获取账号或者密码')
    return
end

-- 验证账号和密码是否正确,如果验证失败则做如下处理
if not this_is_a_auth_method(username, password) then
    ngx.status = 401
    ngx.say('认证失败')
    return
end

verify.lua ↓:

local jwt = require 'resty.jwt'

-- 从请求中提取header并从header从获取token字段
local headers = ngx.req.get_headers()
local token = headers['token']

-- 检查token是否存在
if not token then 
    ngx.status = 400
    ngx.say('无法获取token')
    return 
end 

-- 验证token
local jwt_obj = jwt:verify(vars.jwt_salt(), token)
if not jwt_obj['verified'] then 
    ngx.status = 401
    ngx.say('无效的token')
    return 
end 

至此一个使用Openresty构建的认证网关的雏形已经出来了.需要说明的一句是, 上面的代码由于没有公司相关的运行环境,笔者没有经过测试和验证.所以只可阅读,不可复制后直接运行 :)

如果想把这套认证网关用在生产环境上, 还有很多东西需要考虑.比如跨域问题, 静态文件的代理问题等等.

个人接触Openresty的时间也不长, 文中难免会有地方写错了或者表达得很差, 欢迎发评论或者发邮件给我指正: [email protected] ,感谢.

对于Openresty, 个人认为要对它产生兴趣,关键在于认不认可让Nginx承担除了Web服务器之外更多的业务, 对于Openresty, 它能带来的好处有:

  1. 极致的性能.上文没有提到Openresty的性能, 其实Openresty的编程模型和NodeJS很像, 在Openresty的世界里面,所有东西都是非阻塞的,更难得可贵的是, 它不需要使用NodeJS中的回调函数, 代码写起来其实还是同步模型, 配合C语言编写的Nginx, 最快的脚本语言lua+luajit解释器,这套方案的性能无可挑剔了.

  2. 降低了Nginx模块的开发难度. Nginx + C/C++能做的, Openresty用lua都能做.开发效率高了, 性能还不怎么降, 何乐而不为呢?

参考资料

  1. Openresty最佳实践
  2. lua-resty-jwt

你可能感兴趣的:([入门]使用Openresty构建认证网关)