【问题标题】:Caching API requests with JWT auth使用 JWT auth 缓存 API 请求
【发布时间】:2018-11-09 08:38:02
【问题描述】:

我一直试图弄清楚是否可以缓存需要 JWT 身份验证和检查令牌内容的请求。

当前设置:

  • PHP API
  • Nginx 服务请求

我一直在探索的软件:

  • Nginx
  • 清漆

令牌内容:

  • iss...(常规 JWT 内容)
  • 用户组ID

我有一些供用户共享的内容,有些是个人的。所以我希望能够缓存对某些端点的请求。当缓存中有数据时,甚至无需调用 PHP。

假设我们有用户组和组拥有汽车。我希望能够对以下端点进行身份验证和缓存请求:

https://myapi.example.com/groups/{groupID}/cars

要验证此请求,缓存软件必须能够将 {groupID} 与令牌中的 groupID 进行比较。但是afaik Varnish只能验证token,但它可以检查token groupID是否与URL中的groupID匹配?

Nginx 有一些 JWT 功能,但在这里找不到任何东西来实现这一点: https://nginx.org/en/docs/http/ngx_http_auth_jwt_module.html

有没有其他软件可以做到这一点?现在我正在考虑回到 PHP 中缓存的东西。在那里检查令牌并使用 memcached 或其他东西来缓存数据。

【问题讨论】:

    标签: php nginx caching jwt varnish


    【解决方案1】:

    可以使用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.开始养育

    ./haproxy -f nuster.conf
    

    测试

    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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-07-23
      • 1970-01-01
      • 2015-01-24
      • 2021-01-15
      • 2016-12-16
      • 2020-03-28
      • 2021-10-13
      • 2015-04-12
      相关资源
      最近更新 更多