1,依赖软件:nginx(openresty) mysql(存储用户表)redis(存储用户登录token,有效期1周)
create table account( uid integer not null auto_increment, username varchar(64), password varchar(64), email varchar(256), primary key(uid), unique key email(email) );
nginx配置文件如下:
location = /account { lua_need_request_body on; content_by_lua_file /usr/local/FRIENDS/code_lua/account.lua; } location = /api { access_by_lua ' local tokentool = require "tokentool" local args = ngx.req.get_uri_args(10) if args.token == nil then ngx.exit(ngx.HTTP_FORBIDDEN) end local ret = tokentool.has_token(args.token) if ret == ngx.null then ngx.exit(ngx.HTTP_FORBIDDEN) elseif ret == false then ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR) end '; content_by_lua ' ngx.say("token ok") '; }
2,依赖库:nginx-lua , lua-redis(读取redis), lua-mysql(读取mysql), lua-string(加解密)
3,代码如下:
account.lua(入口)
local mysql = require "resty.mysql" local tokentool = require "tokentool" -- post only local method = ngx.req.get_method() if method ~= "POST" then ngx.exit(ngx.HTTP_FORBIDDEN) return end -- get args local args = ngx.req.get_uri_args(10) if args.act ~= "register" and args.act ~= "login" and args.act ~= "logout" and args.act ~= "updatepwd" then ngx.exit(ngx.HTTP_BAD_REQUEST) return end local postargs = ngx.req.get_post_args(10) -- connect to mysql; local function connect() local db, err = mysql:new() if not db then return false end db:set_timeout(1000) local ok, err, errno, sqlstate = db:connect{ host = "127.0.0.1", port = 3306, database = "friends", user = "root", password = "", max_packet_size = 1024 * 1024 } if not ok then return false end return db end function register(pargs) if pargs.username == nil then pargs.username = "" end if pargs.email == nil or pargs.password == nil then ngx.exit(ngx.HTTP_BAD_REQUEST) return end local db = connect() if db == false then ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR) return end local res, err, errno, sqlstate = db:query("insert into account(username, password, email) " .. "values (\'".. pargs.username .."\',\'".. pargs.password .."\',\'".. pargs.email .."\')") if not res then ngx.exit(ngx.HTTP_NOT_ALLOWED) return end local uid = res.insert_id local token, rawtoken = tokentool.gen_token(uid) local ret = tokentool.add_token(token, rawtoken) if ret == true then ngx.say(token) else ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR) end end function login(pargs) if pargs.email == nil or pargs.password == nil then ngx.exit(ngx.HTTP_BAD_REQUEST) return end local db = connect() if db == false then ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR) return end local res, err, errno, sqlstate = db:query("select uid from account where email=\'".. pargs.email .."\' and password=\'".. pargs.password .."\' limit 1", 1) if not res then ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR) return end --local cjson = require "cjson" --ngx.say(cjson.encode(res)) if res[1] == nil then ngx.exit(ngx.HTTP_FORBIDDEN) end local uid = res[1].uid local token, rawtoken = tokentool.gen_token(uid) local ret = tokentool.add_token(token, rawtoken) if ret == true then ngx.say(token) else ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR) end end function logout(pargs) if pargs.token == nil then ngx.exit(ngx.HTTP_BAD_REQUEST) return end tokentool.del_token(pargs.token) ngx.say("ok") end -- to be done function updatepwd(pargs) local db = connect() if db == false then ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR) return end ngx.say(pargs.username .. pargs.newpassword) end if args.act == "register" then register(postargs) elseif args.act == "login" then login(postargs) elseif args.act == "updatepwd" then updatepwd(postargs) elseif args.act == "logout" then logout(postargs) end
tokentool.lua(用redis存取token,放到序号为1的redis库中)
module("tokentool", package.seeall) local redis = require "resty.redis" local aes = require "resty.aes" local str = require "resty.string" local alive_time = 3600 * 24 * 7 local redis_host = "127.0.0.1" local redis_port = 6379 local function connect() local red = redis:new() red:set_timeout(1000) local ok, err = red:connect(redis_host, redis_port) if not ok then return false end ok, err = red:select(1) if not ok then return false end return red end function add_token(token, raw_token) local red = connect() if red == false then return false end local ok, err = red:setex(token, alive_time, raw_token) if not ok then return false end return true end function del_token(token) local red = connect() if red == false then return end red:del(token) end function has_token(token) local red = connect() if red == false then return false end local res, err = red:get(token) if not res then return false end return res end -- generate token function gen_token(uid) local rawtoken = uid .. " " .. ngx.now() local aes_128_cbc_md5 = aes:new("friends_secret_key") local encrypted = aes_128_cbc_md5:encrypt(rawtoken) local token = str.to_hex(encrypted) return token, rawtoken end
使用方法:
1,注册用户,返回token curl -d "username=ciaos&email=aaa@126.com&password=12345" http://localhost/account?act=register 2,登录,返回token curl -d "email=aaa@126.com&password=12345" http://localhost/account?act=login 3,注销,删除token,返回ok curl -d "token=0bab442cd24cd055b58665d4156939655d72a7c282c916778ef2c63be9971085" http://localhost/account?act=logout
结合codeigniter还需做如下配置
if (!-e $request_filename) { rewrite ^/(.*)$ /index.php/$1 last; break; } location ~ \.php($|/) { access_by_lua ' local tokentool = require "tokentool" local args = ngx.req.get_uri_args(10) if args.token == nil then ngx.exit(ngx.HTTP_FORBIDDEN) end local ret = tokentool.has_token(args.token) if ret == ngx.null then ngx.exit(ngx.HTTP_FORBIDDEN) elseif ret == false then ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR) end ngx.req.set_uri_args({token=ret}) '; fastcgi_pass 127.0.0.1:9000; fastcgi_index index.php; fastcgi_split_path_info ^(.+\.php)(.*)$; fastcgi_param PATH_INFO $fastcgi_path_info; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; }
需要在网站根目录放置空文件account,不然nginx处理/account时会走到找不到request_filename的这个location里面。
附(ngx-lua的long-polling实现):
--[[ nginx.conf location /message { default_type text/plain; access_by_lua ' local tokentool = require "tokentool" local args = ngx.req.get_uri_args(10) if args.token == nil then ngx.exit(ngx.HTTP_FORBIDDEN) end local ret = tokentool.has_token(args.token) if ret == ngx.null then ngx.exit(ngx.HTTP_FORBIDDEN) elseif ret == false then ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR) end ngx.req.set_uri_args({token=ret}) '; content_by_lua_file /usr/local/FRIENDS/code_lua/message.lua; } --]] -- message.lua local redis = require "resty.redis" local redis_host = "127.0.0.1" local redis_port = 6379 local function connect() local red = redis:new() red:set_timeout(1000 * 1000) local ok, err = red:connect(redis_host, redis_port) if not ok then return false end return red end local function string_split (string, split) local list = {} local pos = 1 if string.find("", split, 1) then -- this would result in endless loops end while 1 do local first, last = string.find(string, split, pos) if first then -- found table.insert(list, string.sub(string, pos, first-1)) pos = last+1 else table.insert(list, string.sub(string, pos)) break end end return list end local args = ngx.req.get_uri_args() local params = string_split(args.token, ' ') local mymailbox = params[1] .. "_mailbox" local red = connect() if red == false then ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR) return end local res, err = red:llen(mymailbox) if err ~= nil then ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR) return end if res == 0 then res, err = red:brpop(mymailbox, 1000) ngx.print('[') for k, v in ipairs(res) do if k == 2 then ngx.print(v) end end ngx.print(']') else if res > 10 then res = 10 end local i = 0 ngx.print('[') for i=0, res - 1, 1 do local msg, _ = red:rpop(mymailbox) ngx.print(msg) if i ~= res - 1 then ngx.print(',') end end ngx.print(']') end ngx.exit(ngx.HTTP_OK)