nginx进行token验证

前言

最近在做图片服务的时候,一开始用的tomcat,图片的url后面拼接上token,在tomcat里进行token验证后再把二进制流返回给前台。不过,我们项目中有一个页面,在进去的时候,会一次性加载1000张左右的图片,等图片加载完,花都谢了。

不过也不能把后台校验去了,所以就有了在nginx上做token验证的想法,同事说可以开发脚本,随后就有了接下来的故事。

OpenResty

OpenResty就是我需要用到的集成了nginx和lua开发环境的框架,该框架甚至集成了redis连接包等,里面的东西很多,具体的可以看这篇文章:OpenResty 最佳实践,本篇文章只说具体实现。另外高喊一句,lua牛批。

不过在搭建lua的开发环境的时候,可能是因为我是Windows系统,所以环境没能安装成功, 不过我按照Idea+lua 开发环境搭建(一)文章里说的,成功运行lua。虽然。。。我搭好lua环境后,只是用来练习语法和部分标准库了,nginx的开发没用上。。。

它的一生

第一版中,我只是在里面通过Redis做了token校验,只要请求过来,就会先去Redis里面判断一下是否有这个token,如果有的话就返回图片。不过返回图片卡住了好久,按照《OpenResty 最佳实践》中说的,我返回图片流给页面,页面并不能看到图片。于是我决定利用子查询来做,将该请求转到nginx中的另一个location下。

第二版加入了token缓存,在缓存中存储接收到的token,等下一个请求来时,先去缓存中看有没有,如果没有的话就去redis中查一下。虽然这么设置了,图片还是没有达到想要的速度,只能跟前端的大哥商量,说能不能让图片懒加载,一次请求1000个真的有点搞。。。

最后应用的代码

nginx.conf: 应用了


#user  nobody;
worker_processes  2;

error_log  logs/error.log  info;

#pid        logs/nginx.pid;


events {
    worker_connections  1024;
}


http {
    include       mime.types;
    default_type  application/octet-stream;
	# 声明创建缓存
	lua_shared_dict my_cache 64m;

    #log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
    #                  '$status $body_bytes_sent "$http_referer" '
    #                  '"$http_user_agent" "$http_x_forwarded_for"';

    #access_log  logs/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    #keepalive_timeout  0;
    keepalive_timeout  65;

    #gzip  on;

    server {
        listen       80;
        server_name  localhost;

        charset UTF-8;

        # access_log  logs/host.access.log info;

        location ^~ /images/ {
			
			content_by_lua_file lua/auth/token.lua;
			
        }
		
		location ^~ /static/images/ {

			alias	E:/static/images/toyota/ImageGroup/;
		}
    }
}

lua/auth/token.lua: 负责token验证的逻辑

local arg = ngx.req.get_uri_args()
-- get请求参数中T就是token
local token = arg.T

if not token then
    ngx.status = ngx.HTTP_FORBIDDEN
    ngx.say("No token,dude.")
    ngx.exit(200)
end

-- 先去本地缓存中查找是否存在该token
local cache_ngx = ngx.shared.my_cache
local value = cache_ngx:get(token)


if not value then
	-- 本地缓存没有,去Redis中找
	-- 从reids中获取token,如果不存在,返回406(Not Acceptable)
    -- require设置的路径是从lua包下开始算起的
	local redis = require "api.redis"
	local red = redis:new()
    -- 存储在Redis中的token有前缀
	local token_key = "_token_"..token
	ok, err = red:get(token_key)

	if not ok then
		-- token已过期
		ngx.status = ngx.HTTP_FORBIDDEN
		ngx.log(ngx.INFO, "Token passed away.")
		ngx.exit(406)
	else
		
		value = ok
		-- 在nginx缓存中添加该token,设置过期时间为30分钟
		cache_ngx:set(token, "", 30 * 60)
	end
end


if value then
    -- 截取访问的图片名称
    -- 获取uri
    local uri = ngx.var.request_uri
    local t_index = string.find(uri, "?T")
    -- uri的起始部分必定有/images/, 所以从9位置开始截取
    local file_path = string.sub(uri, 9, t_index - 1)
    if string.len(file_path) < 5 then
        ngx.exit(404)
    end
    file_path = "/static/images/"..file_path

    res = ngx.location.capture(
            file_path,
            {method = ngx.HTTP_GET}
    )
    ngx.say(res.body)
end

lua/api/redis.lua: 负责redis的创建及设置

---
--- redis util
--- Generated by EmmyLua(https://github.com/EmmyLua)
--- Created by SSM.
--- DateTime: 2020/3/24 10:01
---

local redis_c = require "resty.redis"

local ok, new_tab = pcall(require, "table.new")
if not ok or type(new_tab) ~= "function" then
    new_tab = function (narr, nrec) return {} end
end

local _M = new_tab(0, 155)
_M._VERSION = "0.01"

local commands = {
    "append",            "auth",              "bgrewriteaof",
    "bgsave",            "bitcount",          "bitop",
    "blpop",             "brpop",
    "brpoplpush",        "client",            "config",
    "dbsize",
    "debug",             "decr",              "decrby",
    "del",               "discard",           "dump",
    "echo",
    "eval",              "exec",              "exists",
    "expire",            "expireat",          "flushall",
    "flushdb",           "get",               "getbit",
    "getrange",          "getset",            "hdel",
    "hexists",           "hget",              "hgetall",
    "hincrby",           "hincrbyfloat",      "hkeys",
    "hlen",
    "hmget",              "hmset",      "hscan",
    "hset",
    "hsetnx",            "hvals",             "incr",
    "incrby",            "incrbyfloat",       "info",
    "keys",
    "lastsave",          "lindex",            "linsert",
    "llen",              "lpop",              "lpush",
    "lpushx",            "lrange",            "lrem",
    "lset",              "ltrim",             "mget",
    "migrate",
    "monitor",           "move",              "mset",
    "msetnx",            "multi",             "object",
    "persist",           "pexpire",           "pexpireat",
    "ping",              "psetex",            "psubscribe",
    "pttl",
    "publish",      --[[ "punsubscribe", ]]   "pubsub",
    "quit",
    "randomkey",         "rename",            "renamenx",
    "restore",
    "rpop",              "rpoplpush",         "rpush",
    "rpushx",            "sadd",              "save",
    "scan",              "scard",             "script",
    "sdiff",             "sdiffstore",
    "select",            "set",               "setbit",
    "setex",             "setnx",             "setrange",
    "shutdown",          "sinter",            "sinterstore",
    "sismember",         "slaveof",           "slowlog",
    "smembers",          "smove",             "sort",
    "spop",              "srandmember",       "srem",
    "sscan",
    "strlen",       --[[ "subscribe",  ]]     "sunion",
    "sunionstore",       "sync",              "time",
    "ttl",
    "type",         --[[ "unsubscribe", ]]    "unwatch",
    "watch",             "zadd",              "zcard",
    "zcount",            "zincrby",           "zinterstore",
    "zrange",            "zrangebyscore",     "zrank",
    "zrem",              "zremrangebyrank",   "zremrangebyscore",
    "zrevrange",         "zrevrangebyscore",  "zrevrank",
    "zscan",
    "zscore",            "zunionstore",       "evalsha"
}


local mt = { __index = _M }


local function is_redis_null( res )
    if type(res) == "table" then
        for k,v in pairs(res) do
            if v ~= ngx.null then
                return false
            end
        end
        return true
    elseif res == ngx.null then
        return true
    elseif res == nil then
        return true
    end

    return false
end


-- change connect address and password as you need
function _M.connect_mod( self, redis )
    redis:set_timeout(self.timeout)
	local ok, err = redis:connect("192.168.1.8", 6379)
    -- 没有密码的话,下面这句话就可以去掉了
	ok, err = redis:auth("123456")
    return ok, err
end


function _M.set_keepalive_mod( redis )
    -- put it into the connection pool of size 100, with 60 seconds max idle time
    return redis:set_keepalive(60000, 100)
end


function _M.init_pipeline( self )
    self._reqs = {}
end


function _M.commit_pipeline( self )
    local reqs = self._reqs

    if nil == reqs or 0 == #reqs then
        return {}, "no pipeline"
    else
        self._reqs = nil
    end

    local redis, err = redis_c:new()
    if not redis then
        return nil, err
    end

    local ok, err = self:connect_mod(redis)
    if not ok then
        return {}, err
    end

    redis:init_pipeline()
    for _, vals in ipairs(reqs) do
        local fun = redis[vals[1]]
        table.remove(vals , 1)

        fun(redis, unpack(vals))
    end

    local results, err = redis:commit_pipeline()
    if not results or err then
        return {}, err
    end

    if is_redis_null(results) then
        results = {}
        ngx.log(ngx.WARN, "is null")
    end
    -- table.remove (results , 1)

    self.set_keepalive_mod(redis)

    for i,value in ipairs(results) do
        if is_redis_null(value) then
            results[i] = nil
        end
    end

    return results, err
end


function _M.subscribe( self, channel )
    local redis, err = redis_c:new()
    if not redis then
        return nil, err
    end

    local ok, err = self:connect_mod(redis)
    if not ok or err then
        return nil, err
    end

    local res, err = redis:subscribe(channel)
    if not res then
        return nil, err
    end

    res, err = redis:read_reply()
    if not res then
        return nil, err
    end

    redis:unsubscribe(channel)
    self.set_keepalive_mod(redis)

    return res, err
end


local function do_command(self, cmd, ... )
    if self._reqs then
        table.insert(self._reqs, {cmd, ...})
        return
    end

    local redis, err = redis_c:new()
    if not redis then
        return nil, err
    end

    local ok, err = self:connect_mod(redis)
    if not ok or err then
        return nil, err
    end

    local fun = redis[cmd]
    local result, err = fun(redis, ...)
    if not result or err then
        -- ngx.log(ngx.ERR, "pipeline result:", result, " err:", err)
        return nil, err
    end

    if is_redis_null(result) then
        result = nil
    end

    self.set_keepalive_mod(redis)

    return result, err
end

--- 把声明所有的方法
for i = 1, #commands do
    local cmd = commands[i]
    _M[cmd] =
    function (self, ...)
        return do_command(self, cmd, ...)
    end
end


function _M.new(self, opts)
    opts = opts or {}
    local timeout = (opts.timeout and opts.timeout * 1000) or 1000
    local db_index= opts.db_index or 0

    return setmetatable({
        timeout = timeout,
        db_index = db_index,
        _reqs = nil }, mt)
end


return _M

总结

OpenResty真的很强大,有时间的话真的想透彻的学学,下次一定。。。。

你可能感兴趣的:(nginx,OpenResty,nginx)