-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
13 changed files
with
967 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
# Compiled Lua sources | ||
luac.out | ||
|
||
# luarocks build files | ||
*.src.rock | ||
*.zip | ||
*.tar.gz | ||
|
||
# Object files | ||
*.o | ||
*.os | ||
*.ko | ||
*.obj | ||
*.elf | ||
|
||
# Precompiled Headers | ||
*.gch | ||
*.pch | ||
|
||
# Libraries | ||
*.lib | ||
*.a | ||
*.la | ||
*.lo | ||
*.def | ||
*.exp | ||
|
||
# Shared objects (inc. Windows DLLs) | ||
*.dll | ||
*.so | ||
*.so.* | ||
*.dylib | ||
|
||
# Executables | ||
*.exe | ||
*.out | ||
*.app | ||
*.i*86 | ||
*.x86_64 | ||
*.hex | ||
|
||
.idea |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
[submodule "third-party/lua-resty-lrucache"] | ||
path = third-party/lua-resty-lrucache | ||
url = [email protected]:openresty/lua-resty-lrucache.git | ||
[submodule "third-party/lua-resty-jwt"] | ||
path = third-party/lua-resty-jwt | ||
url = [email protected]:cdbattags/lua-resty-jwt.git | ||
[submodule "third-party/lua-resty-hmac"] | ||
path = third-party/lua-resty-hmac | ||
url = [email protected]:jkeys089/lua-resty-hmac.git |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
# jwt-auth | ||
|
||
JWT Authenticaion plugin for Kong Lua plugin | ||
|
||
# Setup | ||
|
||
## Clone | ||
|
||
Plugin use some third party libs so need to clone repository recursively. | ||
With version 2.13 of Git and later | ||
``` | ||
git clone --recurse-submodules [email protected]:3gthtech/jwt-auth.git | ||
``` | ||
|
||
With version 1.9 of Git up until version 2.12 | ||
``` | ||
git clone --recursive [email protected]:3gthtech/jwt-auth.git | ||
``` | ||
|
||
## Kong Configuration | ||
|
||
### Take a file from your local clone repo `/third-party` folder. | ||
|
||
``` | ||
$ sudo cp -R /third-party/lua-resty-lrucache/lib/resty/ /usr/local/share/lua/5.1/resty | ||
$ sudo cp /third-party/lua-resty-lrucache/lib/resty/lrucache.lua /usr/local/share/lua/5.1/resty | ||
$ sudo cp -a /third-party/lua-resty-jwt/lib/resty/. /usr/local/share/lua/5.1/resty | ||
$ sudo cp -a /third-party/lua-resty-hmac/lib/resty/. /usr/local/share/lua/5.1/resty | ||
``` | ||
|
||
### Install our `jwt-auth` with kong plugin | ||
|
||
``` | ||
$ sudo cp -R /kong/plugins/jwt-auth /usr/local/share/lua/5.1/kong/plugins | ||
``` | ||
|
||
Enable plugin by adding plugin name in `/etc/kong/kong.conf` | ||
|
||
``` | ||
plugins = bundled, jwt-auth | ||
``` | ||
|
||
`bundled` is for kong default plugins. After adding this name, restart kong `sudo kong restart`. | ||
|
||
# Configuration | ||
|
||
1. Add service | ||
|
||
``` | ||
$ curl --fail -sS -X POST --url http://localhost:8001/services/ --header 'content-type: application/json' --data '{"name":"demo-service","url":"http://backend"}' | ||
``` | ||
|
||
2. Add Route | ||
|
||
``` | ||
$ curl --fail -i -sS -X POST --url http://localhost:8001/services/demo-service/routes --data 'hosts[]=backend.com' | ||
``` | ||
|
||
Before add plugin check proxy with your upstream API | ||
|
||
``` | ||
curl -i -sS -X GET --url http://localhost:8000/v1/user --header 'Host: backend.com' | ||
``` | ||
|
||
3. Add Plugin | ||
|
||
``` | ||
$ curl -v -i -sS -X POST --url http://localhost:8001/plugins/ --header 'content-type: application/json;charset=UTF-8' --data '{"config":{"check_jwt_expire":60,"login_endpoint":"/tokens","refresh_token_endpoint":"/tokens","upstream_url":"http://localhost:8081"},"name":"jwt-auth","service":{"id":"dc862e47-4475-4cb0-ac48-9f9d4f1eb869"}}' | ||
``` | ||
|
||
Schema details | ||
|
||
| Title | Description | | ||
|-------|-------------| | ||
| upstream_url | Your upstream service example: `http://localhost:8081` | | ||
| login_endpoint | Authotization endpoint from your upstream service from where you get JWT. Example: `/tokens` | | ||
| refresh_token_endpoint | Refresh token endpoint from your upstream service from where you get new JWT by refresh token. Example: `/tokens` | | ||
| check_jwt_expire | Time in second to check old JWT expire time before it expired. This time in seconds. Default value is 60 seconds. | | ||
|
||
# Usage | ||
|
||
Request to login_endpoint through kong proxy to upstream service. | ||
|
||
``` | ||
$ curl -sS -X POST --url http://localhost:8000/tokens --header 'Host: backend.com' --header 'content-type: application/json' --data '{"email":"[email protected]", "password": "admin"}' | ||
``` | ||
|
||
Get a new token from this and request to access protected resources. | ||
|
||
``` | ||
curl -i -sS -X GET --url http://localhost:8000/v1/user --header 'Host: backend.com' --header 'Authorization: Bearer <your_jwt_token>' | ||
``` | ||
|
||
# Test | ||
|
||
[Test cases](./t) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,177 @@ | ||
local validators = require "resty.jwt-validators" | ||
local jwt = require "resty.jwt" | ||
local cjson = require "cjson.safe" | ||
local pl_pretty = require "pl.pretty" | ||
local http = require "resty.http" | ||
|
||
local EXPIRE_DELTA = 20 | ||
local MAX_PENDING_SLEEPS = 40 | ||
local PENDING_EXPIRE = 0.2 | ||
local PENDING_TABLE = {} | ||
|
||
local lrucache = require "resty.lrucache.pureffi" | ||
-- it is shared by all the requests served by each nginx worker process: | ||
local worker_cache, err = lrucache.new(10000) -- allow up to 10000 items in the cache | ||
if not worker_cache then | ||
return error("failed to create the cache: " .. (err or "unknown")) | ||
end | ||
|
||
local function unexpected_error(...) | ||
local pending_key = kong.ctx.plugin.pending_key | ||
if pending_key then | ||
worker_cache:delete(pending_key) | ||
end | ||
kong.log.err(...) | ||
kong.response.exit(502, { message = "An unexpected error ocurred" }) | ||
end | ||
|
||
local function get_token(authorization) | ||
if authorization and #authorization > 0 then | ||
local from, to, err = ngx.re.find(authorization, "\\s*[Bb]earer\\s+(.+)", "jo", nil, 1) | ||
if from then | ||
return authorization:sub(from, to) -- Return token | ||
end | ||
if err then | ||
return unexpected_error(err) | ||
end | ||
end | ||
|
||
return nil | ||
end | ||
|
||
-- lru cache get operation with `pending` state support | ||
local function worker_cache_get_pending(key) | ||
for i = 1, MAX_PENDING_SLEEPS do | ||
local token_data, stale_data = worker_cache:get(key) | ||
|
||
if not token_data or stale_data then | ||
return | ||
end | ||
|
||
if token_data == PENDING_TABLE then | ||
kong.log.debug("sleep 5ms") | ||
ngx.sleep(0.005) -- 5ms | ||
else | ||
return token_data | ||
end | ||
end | ||
end | ||
|
||
local function set_pending_state(key) | ||
kong.ctx.plugin.pending_key = key | ||
worker_cache:set(key, PENDING_TABLE, PENDING_EXPIRE) | ||
end | ||
|
||
local function clear_pending_state(key) | ||
kong.ctx.plugin.pending_key = nil | ||
worker_cache:delete(key) | ||
end | ||
|
||
local function set_cache(token, body) | ||
set_pending_state(token) | ||
|
||
local status, err, exp | ||
local jwt_obj = jwt:load_jwt(token) | ||
|
||
if not jwt_obj.valid then | ||
clear_pending_state(token) | ||
return kong.response.exit(401, { message = "Invalid token" }) | ||
end | ||
|
||
local payload = jwt_obj.payload | ||
exp = payload.exp | ||
payload.refresh_token = body.refreshToken | ||
|
||
kong.log.debug("save token in cache") | ||
worker_cache:set(token, payload, | ||
exp - ngx.now() - EXPIRE_DELTA) | ||
end | ||
|
||
local function request_to_upstream_set_cache(url, jsonBody) | ||
local httpc = http.new() | ||
local headers = { | ||
["Content-Type"] = "application/json" | ||
} | ||
|
||
local res, err = httpc:request_uri(url, { | ||
method = "POST", | ||
body = jsonBody, | ||
headers = headers, | ||
ssl_verify = false | ||
}) | ||
|
||
if err then | ||
return error("failed to create the cache: " .. (err or "unknown")) | ||
end | ||
|
||
if res.status >= 300 then | ||
ngx.status = res.status | ||
ngx.header.content_type = "application/json; charset=utf-8" | ||
ngx.say(res.body) | ||
return ngx.exit(res.status) | ||
end | ||
|
||
local json, err = cjson.decode(res.body) | ||
if err then | ||
ngx.log(ngx.ERR, err) | ||
return nil, "JSON decode error: " .. err | ||
end | ||
|
||
set_cache(json.token, json) | ||
ngx.status = ngx.HTTP_OK | ||
ngx.header.content_type = "application/json; charset=utf-8" | ||
ngx.say(res.body) | ||
return ngx.exit(ngx.HTTP_OK) | ||
end | ||
|
||
local function access_handler(self, conf) | ||
local authorization = ngx.var.http_authorization | ||
local token = get_token(authorization) | ||
|
||
local method = ngx.req.get_method() | ||
local path = ngx.var.uri | ||
kong.log.debug("path : ", path) | ||
|
||
if method == "POST" and path == conf.login_endpoint then | ||
ngx.req.read_body() | ||
local body = ngx.req.get_body_data() | ||
if not body then | ||
ngx.log(ngx.ERR, "body not found") | ||
end | ||
|
||
ngx.log(ngx.DEBUG, "Request Body :::: ", body) | ||
cjson.decode_array_with_array_mt(true) | ||
local req_body = cjson.decode(body) | ||
cjson.decode_array_with_array_mt(false) | ||
|
||
if req_body.email and req_body.password then | ||
req_body = cjson.encode({ email = req_body.email, password = req_body.password }) | ||
else | ||
req_body = cjson.encode({}) | ||
end | ||
|
||
return request_to_upstream_set_cache(conf.upstream_url .. conf.login_endpoint, req_body) | ||
end | ||
|
||
if not token then | ||
return kong.response.exit(401, { message = "Failed to get bearer token from Authorization header" }) | ||
end | ||
|
||
local token_data = worker_cache_get_pending(token) | ||
if token_data then | ||
if token_data.exp - conf.check_jwt_expire <= ngx.now() then | ||
kong.log.debug("Requesting for new token") | ||
clear_pending_state(token) | ||
local req_body = cjson.encode({ grantType = "refresh_token", refreshToken = token_data.refresh_token}) | ||
return request_to_upstream_set_cache(conf.upstream_url .. conf.refresh_token_endpoint, req_body) | ||
end | ||
kong.log.debug("Allow Success, Request already validate fron cache") | ||
return | ||
else | ||
return kong.response.exit(401, { message = "Bad token" }) | ||
end | ||
end | ||
|
||
return function(self, conf) | ||
access_handler(self, conf) | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
local BasePlugin = require "kong.plugins.base_plugin" | ||
local access = require "kong.plugins.jwt-auth.access" | ||
|
||
local handler = BasePlugin:extend() | ||
handler.PRIORITY = 999 | ||
|
||
-- Your plugin handler's constructor. If you are extending the | ||
-- Base Plugin handler, it's only role is to instanciate itself | ||
-- with a name. The name is your plugin name as it will be printed in the logs. | ||
function handler:new() | ||
handler.super.new(self, "jwt-auth") | ||
end | ||
|
||
function handler:access(config) | ||
-- Eventually, execute the parent implementation | ||
-- (will log that your plugin is entering this context) | ||
handler.super.access(self) | ||
|
||
access(self, config) | ||
end | ||
|
||
return handler |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
return { | ||
no_consumer = true, | ||
fields = { | ||
upstream_url = { required = true, type = "string" }, | ||
login_endpoint = { required = true, type = "string" }, | ||
refresh_token_endpoint = { required = true, type = "string" }, | ||
check_jwt_expire = { required = true, type = "number", default = 60 } | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
FROM ubuntu:xenial | ||
|
||
ENV DEBIAN_FRONTEND noninteractive | ||
|
||
RUN apt-get -qqy update && \ | ||
apt-get -qqy install libluajit-5.1-dev && \ | ||
ln -s /usr/bin/luajit /usr/bin/lua && \ | ||
apt-get -qqy install curl git unzip net-tools uuid-runtime | ||
|
||
RUN apt-get -qqy update && apt-get -qqy install luarocks && \ | ||
luarocks install busted && \ | ||
luarocks install LuaSocket && \ | ||
luarocks install json-lua | ||
|
||
RUN apt-get -qqy install apt-transport-https ca-certificates software-properties-common && \ | ||
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | apt-key add && \ | ||
add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" && \ | ||
apt-get -qqy update && \ | ||
apt-get -qqy install docker-ce && \ | ||
apt-get -qqy install python python-dev python-distribute python-pip && \ | ||
pip install --upgrade pip==9.0.3 && \ | ||
pip install docker-compose | ||
|
||
RUN cd /opt && git clone -q https://github.com/vishnubob/wait-for-it.git | ||
|
||
RUN apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* | ||
|
||
ENV DEBIAN_FRONTEND teletype |
Oops, something went wrong.