我一直试图弄清楚是否可以缓存需要JWT身份验证和检查令牌内容的请求。
当前设置:
我一直在探索的软件:
令牌内容:
我为用户提供了一些共享内容,有些是个人内容。所以我希望能够将请求缓存到某些端点。甚至在没有调用PHP时,缓存中的数据可用。
我们说我们有用户组和团体拥有汽车。我希望能够对以下端点进行身份验证和缓存请求:
https://myapi.example.com/groups/{groupID}/cars
要对此请求进行身份验证,缓存软件必须能够将{groupID}与令牌中的groupID进行比较。但afaik Varnish只能验证令牌,但是它可以检查令牌groupID是否与URL中的groupID匹配?
Nginx有一些JWT功能,但在这里找不到任何东西来实现: https://nginx.org/en/docs/http/ngx_http_auth_jwt_module.html
还有其他软件可以实现吗?现在我正在考虑回到PHP中的缓存内容。检查那里的令牌并使用memcached或其他东西来缓存数据。
答案 0 :(得分:0)
您可以使用https://github.com/jiangwenyuan/nuster,一个基于HAProxy的缓存代理服务器
<强> 1。下载并构建,您将需要lua
wget https://github.com/jiangwenyuan/nuster/releases/download/v1.8.8.2/nuster-1.8.8.2.tar.gz
make TARGET=linux2628 USE_ZLIB=1 USE_OPENSSL=1 USE_LUA=1 LUA_LIB=/opt/lua-5.3.1/lib LUA_INC=/opt/lua-5.3.1/include
<强> 2。创建lua脚本:base64.lua,json.lua,jwt_group_match.lua
base64.lua
-- base64 FROM http://lua-users.org/wiki/BaseSixtyFour
local b='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
base64 = {}
function base64.dec(data)
data = string.gsub(data, '[^'..b..'=]', '')
return (data:gsub('.', function(x)
if (x == '=') then return '' end
local r,f='',(b:find(x)-1)
for i=6,1,-1 do r=r..(f%2^i-f%2^(i-1)>0 and '1' or '0') end
return r;
end):gsub('%d%d%d?%d?%d?%d?%d?%d?', function(x)
if (#x ~= 8) then return '' end
local c=0
for i=1,8 do c=c+(x:sub(i,i)=='1' and 2^(8-i) or 0) end
return string.char(c)
end))
end
return base64
json.lua
-- json FROM https://gist.github.com/tylerneylon/59f4bcf316be525b30ab
json = {}
function kind_of(obj)
if type(obj) ~= 'table' then return type(obj) end
local i = 1
for _ in pairs(obj) do
if obj[i] ~= nil then i = i + 1 else return 'table' end
end
if i == 1 then return 'table' else return 'array' end
end
function escape_str(s)
local in_char = {'\\', '"', '/', '\b', '\f', '\n', '\r', '\t'}
local out_char = {'\\', '"', '/', 'b', 'f', 'n', 'r', 't'}
for i, c in ipairs(in_char) do
s = s:gsub(c, '\\' .. out_char[i])
end
return s
end
function skip_delim(str, pos, delim, err_if_missing)
pos = pos + #str:match('^%s*', pos)
if str:sub(pos, pos) ~= delim then
if err_if_missing then
error('Expected ' .. delim .. ' near position ' .. pos)
end
return pos, false
end
return pos + 1, true
end
function parse_str_val(str, pos, val)
val = val or ''
local early_end_error = 'End of input found while parsing string.'
if pos > #str then error(early_end_error) end
local c = str:sub(pos, pos)
if c == '"' then return val, pos + 1 end
if c ~= '\\' then return parse_str_val(str, pos + 1, val .. c) end
-- We must have a \ character.
local esc_map = {b = '\b', f = '\f', n = '\n', r = '\r', t = '\t'}
local nextc = str:sub(pos + 1, pos + 1)
if not nextc then error(early_end_error) end
return parse_str_val(str, pos + 2, val .. (esc_map[nextc] or nextc))
end
function parse_num_val(str, pos)
local num_str = str:match('^-?%d+%.?%d*[eE]?[+-]?%d*', pos)
local val = tonumber(num_str)
if not val then error('Error parsing number at position ' .. pos .. '.') end
return val, pos + #num_str
end
json.null = {} -- This is a one-off table to represent the null value.
function json.parse(str, pos, end_delim)
pos = pos or 1
if pos > #str then error('Reached unexpected end of input.') end
local pos = pos + #str:match('^%s*', pos) -- Skip whitespace.
local first = str:sub(pos, pos)
if first == '{' then -- Parse an object.
local obj, key, delim_found = {}, true, true
pos = pos + 1
while true do
key, pos = json.parse(str, pos, '}')
if key == nil then return obj, pos end
if not delim_found then error('Comma missing between object items.') end
pos = skip_delim(str, pos, ':', true) -- true -> error if missing.
obj[key], pos = json.parse(str, pos)
pos, delim_found = skip_delim(str, pos, ',')
end
elseif first == '[' then -- Parse an array.
local arr, val, delim_found = {}, true, true
pos = pos + 1
while true do
val, pos = json.parse(str, pos, ']')
if val == nil then return arr, pos end
if not delim_found then error('Comma missing between array items.') end
arr[#arr + 1] = val
pos, delim_found = skip_delim(str, pos, ',')
end
elseif first == '"' then -- Parse a string.
return parse_str_val(str, pos + 1)
elseif first == '-' or first:match('%d') then -- Parse a number.
return parse_num_val(str, pos)
elseif first == end_delim then -- End of an object or array.
return nil, pos + 1
else -- Parse true, false, or null.
local literals = {['true'] = true, ['false'] = false, ['null'] = json.null}
for lit_str, lit_val in pairs(literals) do
local lit_end = pos + #lit_str - 1
if str:sub(pos, lit_end) == lit_str then return lit_val, lit_end + 1 end
end
local pos_info_str = 'position ' .. pos .. ': ' .. str:sub(pos, pos + 10)
error('Invalid json syntax starting at ' .. pos_info_str)
end
end
return json
jwt_group_match.lua
base64 = require("base64")
json = require("json")
function jwt_group_match(txn)
local hdr = txn.http:req_get_headers()
local jwt = hdr["jwt"]
if jwt == nil then
return false
end
_, payload, _ = jwt[0]:match"([^.]*)%.([^.]*)%.(.*)"
if payload == nil then
return false
end
local payload_dec = base64.dec(payload)
local payload_json = json.parse(payload_dec)
if txn.sf:path() == "/group/" .. payload_json["userGroupID"] .. "/cars" then
return true
end
return false
end
core.register_fetches("jwt_group_match", jwt_group_match)
第3。创建conf,比方说,nuster.conf
global
nuster cache on dict-size 1m data-size 100m
debug
lua-load jwt_group_match.lua
frontend web1
bind *:8080
mode http
default_backend app1
backend app1
mode http
http-request set-var(req.jwt_group_match) lua.jwt_group_match
nuster cache on
nuster rule group if { var(req.jwt_group_match) -m bool }
server s1 127.0.0.1:8000
server s2 127.0.0.1:8001
第3。启动nuster
./haproxy -f nuster.conf
<强> TEST 强>
payload: {
"iss": "iss",
"sub": "sub",
"userGroupID": "nuster"
}
curl http://127.0.0.1:8080/group/nuster/cars --header "jwt: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJpc3MiLCJzdWIiOiJzdWIiLCJ1c2VyR3JvdXBJRCI6Im51c3RlciJ9.hPpqQS0d4T2BQP90ZDcgxnqJ0AHmwWFqZvdxu65X3FM"
首次运行
[CACHE] To create
第二次运行
[CACHE] Hit
对于Auth部分,您可以使用https://github.com/SkyLothar/lua-resty-jwt