美文网首页NginxJAVA
基于 OpenResty 实现 JWT 验证

基于 OpenResty 实现 JWT 验证

作者: 舌尖上的大胖 | 来源:发表于2019-05-01 01:07 被阅读0次

    〇、必备技能

    • 熟悉 Nginx
    • 对 Lua 及 OpenResty 中进行交互有基本了解
    • 熟悉 Linux 的基本操作

    一、环境准备

    • CentOS 7
    • OpenResty 1.13.6.2

    二、安装

    1、安装 OpenResty

    参考官方文档 OpenResty® Linux 包——CentOS

    $ sudo yum install yum-utils
    
    # 添加仓库
    $ sudo yum-config-manager --add-repo https://openresty.org/package/centos/openresty.repo
    
    # 安装 OpenResty
    $ sudo yum install openresty
    
    # 安装命令行工具 resty
    $ sudo yum install openresty-resty
    
    # 安装命令行工具 opm
    $ sudo yum install openresty-opm
    

    可以列出所有 openresty 仓库里头的软件包,根据需要选择安装:

    $ sudo yum --disablerepo="*" --enablerepo="openresty" list available
    

    OpenResty RPM 包 页面包含更多细节。

    2、测试 OpenResty

    修改 nginx.conf 配置文件:

    worker_processes  1;
    error_log logs/error.log;
    events {
        worker_connections 1024;
    }
    http {
        server {
            listen 8080;
            location / {
                default_type text/html;
                content_by_lua_block {
                    ngx.say("<p>hello, world</p>")
                }
            }
        }
    }
    

    启动服务,通过 cURL 或者浏览器测试:

    $ curl http://localhost:8080/
    

    返回

    <p>hello, world</p>
    

    表示 OpenResty 安装成功,Lua 脚本正常工作。

    3、安装 JWT 的 Lua 插件

    SkyLothar/lua-resty-jwt 是用于 ngx_lua 和 LuaJIT 的 Lua 实现库。在项目 README 的 Installation 部分有安装说明。本文采用的是 opm 的安装方式:

    $ sudo opm get SkyLothar/lua-resty-jwt
    

    4、测试 JWT 插件

    参考配置

    # nginx.conf
    
    lua_package_path "/path/to/lua-resty-jwt/lib/?.lua;;";
    
    server {
        default_type text/plain;
    
        location = /verify {
    
            content_by_lua_block {
                local cjson = require("cjson")
                local jwt = require("resty.jwt")
    
                local jwt_token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9" ..
                    ".eyJmb28iOiJiYXIifQ" ..
                    ".VAoRL1IU0nOguxURF2ZcKR0SGKE1gCbqwyh8u2MLAyY"
                local jwt_obj = jwt:verify("lua-resty-jwt", jwt_token)
                ngx.say(cjson.encode(jwt_obj))
            }
        }
    
        location = /sign {
            content_by_lua_block {
                local cjson = require("cjson")
                local jwt = require("resty.jwt")
    
                local jwt_token = jwt:sign(
                    "lua-resty-jwt",
                    {
                        header={typ="JWT", alg="HS256"},
                        payload={foo="bar"}
                    }
                )
                ngx.say(jwt_token)
            }
        }
    }
    

    说明:

    • lua_package_path:指定 Lua 脚本位置。
      注意:上述配置中搜索路径的最后出现了 ;; 两个半角分号,代表的是 LuaJIT 安装时的原始搜索路径,如果在前面的搜索路径里面无法搜索到需要的模块,就会依次搜索后面的路径。

    • default_type:指定输出为纯文本,无实际作用,只是为了在浏览器中方便查看换行等符号。

    • /verify:校验 JWT

    $ curl http://lh:39100/verify
    

    执行结果:

    {
      "signature": "VAoRL1IU0nOguxURF2ZcKR0SGKE1gCbqwyh8u2MLAyY",
      "reason": "everything is awesome~ :p",
      "valid": true,
      "raw_header": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9",
      "payload": {
        "foo": "bar"
      },
      "header": {
        "alg": "HS256",
        "typ": "JWT"
      },
      "verified": true,
      "raw_payload": "eyJmb28iOiJiYXIifQ"
    }
    
    • /sign:生成 JWT
    $ curl http://lh:39100/sign
    

    执行结果:

    eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIifQ.VxhQcGihWyHuJeHhpUiq2FU7aW2s_3ZJlY6h1kdlmJY
    

    得到以上执行结果,表示 JWT 插件安装成功。

    三、编写验证逻辑

    通过前面的步骤,已经完成了基本环境的搭建。后续编写校验逻辑,根据校验结果控制访问行为。

    目标:
    验证 JWT:

    • 成功:将请求转发至后台服务
    • 失败:返回 401,并以 JSON 形式返回错误原因

    思路:
    通过 Header 传 JWT:

    Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIifQ.VxhQcGihWyHuJeHhpUiq2FU7aW2s_3ZJlY6h1kdlmJY
    

    Lua 拿到 JWT 之后,校验合法性。

    代码:
    校验脚本:

    -- nginx-jwt.lua
    
    
    local cjson = require "cjson"
    local jwt = require "resty.jwt"
    
    --your secret
    local secret = "a secret key"
    
    local M = {}
    
    
    function M.auth()
        -- require Authorization request header
        local auth_header = ngx.var.http_Authorization
    
        if auth_header == nil then
            ngx.log(ngx.WARN, "No Authorization header")
            ngx.exit(ngx.HTTP_UNAUTHORIZED)
        end
    
        ngx.log(ngx.INFO, "Authorization: " .. auth_header)
    
        -- require Bearer token
        local _, _, token = string.find(auth_header, "Bearer%s+(.+)")
    
        if token == nil then
            ngx.log(ngx.WARN, "Missing token")
            ngx.exit(ngx.HTTP_UNAUTHORIZED)
        end
    
        ngx.log(ngx.INFO, "Token: " .. token)
    
        local jwt_obj = jwt:verify(secret, token)
        if jwt_obj.verified == false then
            ngx.log(ngx.WARN, "Invalid token: ".. jwt_obj.reason)
            
            ngx.status = ngx.HTTP_UNAUTHORIZED
            ngx.header.content_type = "application/json; charset=utf-8"
            ngx.say(cjson.encode(jwt_obj))
            ngx.exit(ngx.HTTP_UNAUTHORIZED)
        end
    
        ngx.log(ngx.INFO, "JWT: " .. cjson.encode(jwt_obj))
    
    end
    
    return M
    

    nginx.conf 配置:

    # nginx.conf
    
    location /check-jwt {
        default_type text/plain;
        access_by_lua_block {
            local obj = require('nginx-jwt')
            obj.auth()
        }
        proxy_pass "http://backend-server/";
    }
    

    延展:
    1、对于实际应用的场景,可以根据需要将 JWT 放在自己需要的位置,比如 Cookie 中。
    2、对于错误时的返回值,本例只是将验证结果以 JSON 形式返回,这样暴露了很多信息。可以考虑将详细信息记入日志,将裁剪过的内容返回给请求方。

    四、参考资料

    (完)

    相关文章

      网友评论

        本文标题:基于 OpenResty 实现 JWT 验证

        本文链接:https://www.haomeiwen.com/subject/ntqinqtx.html