使用Lua和OpenResty搭建驗證碼伺服器
Lua下有個Lua-GD圖形庫,通過簡單的Lua語句就能控制、生成圖片。
環境說明:
- 作業系統:RHEL6.4
- RHEL系統預設已安裝RPM包的Lua-5.1.4,但其只具有Lua基本功能,不提供
lua.h
等,但 Lua-GD 編譯需要用到lua.h
,故 Lua 需要編譯安裝。 - Lua-GD 版本號格式為
X.Y.XrW
,其中X.Y.Z
代表gd版本,W
代表效力版本,所以 lua-gd 版本:lua-gd-2.0.33r2
相對應 gd 版本為:gd-2.0.33
,須注意保持一致。 - 因生成gif的lua指令碼中用到md5加密,故需編譯安裝md5。
- 因為生成圖片需要唯一命名,故依賴 UUID
另外:
以下操作均以root使用者執行,並且以下指令碼的當前目錄為/opt
,即所有的下載的檔案都會儲存在/opt
目錄下。
需要安裝的軟體如下:
- OpenResty:WEB應用伺服器,部署lua程式碼,提供URL供使用者呼叫和訪問
- LuaJIT:LUA程式碼直譯器,使用OpenResty中整合的版本
- GD庫:C圖形庫
- Lua-GD庫:Lua繫結的C圖形庫,使得lua可呼叫gd
- Lua-Resty-UUID庫:用於生成UUID,保證圖片命名唯一性
- LuaSocket:lua 的 socket 庫
安裝lua
安裝編譯所需軟體包:
$ yum install -y make gcc
下載並編譯安裝 lua-5.1:
$ yum install -y readline-devel
$ wget http://www.lua.org/ftp/lua-5.1.4.tar.gz
$ tar lua-5.1.4.tar.gz
$ cd lua-5.1.4
$ make linux
$ make linux install
安裝 gd
GD版本:gd-2.0.33
下載地址: http://www.boutell.com/gd/http/gd-2.0.33.tar.gz
$ yum install -y libjpeg-devel libpng-devel freetype-devel fontconfig-devel libXpm-devel
$ wget http://www.boutell.com/gd/http/gd-2.0.33.tar.gz
$ tar zvxf gd-2.0.33.tar.gz
$ cd gd-2.0.33
$ ./configure
$ make && make install
安裝 Lua-gd 庫
Lua-GD版本:lua-gd-2.0.33r2
開發手冊可參考: http://ittner.github.io/lua-gd/manual.html
說明:
須先完成gd的安裝,且版本號必須為gd-2.0.33 呼叫Lua-GD庫的lua程式碼須由OpenResty中整合的LuaJIT解釋執行
$ wget http://sourceforge.net/projects/lua-gd/files/lua-gd/lua-gd-2.0.33r2%20(for%20Lua%205.1)/lua-gd-2.0.33r2.tar.gz/download?use_mirror=jaist
$ tar zvxf lua-gd-2.0.33r2.tar.gz
$ cd lua-gd-2.0.33r2
接寫來修改Makefile檔案:
- 註釋第36~42行
- 開啟第48~52行註釋,並做如下修改
OUTFILE=gd.so
CFLAGS=-Wall `gdlib-config --cflags` -I/usr/local/include/lua -O3 //第49行,修改 lua 的 C 庫標頭檔案所在路徑
GDFEATURES=`gdlib-config --features |sed -e "s/GD_/-DGD_/g"`
LFLAGS=-shared `gdlib-config --ldflags` `gdlib-config --libs` -llua -lgd //第51行,取消lua庫版本號51
INSTALL_PATH=/usr/local/lib/lua/5.1 //第52行,設定 gd.so 的安裝路徑
$(CC) -fPIC -o ... //第70行,gcc 編譯,新增 -fPIC 引數
然後編譯:
$ make && make install
安裝 md5
$ yum install unzip
$ wget https://github.com/keplerproject/md5/archive/master.zip -O md5-master.zip
$ unzip md5-master.zip
$ cd md5-master
$ make && make install
安裝 Lua-resty-UUID 庫
呼叫系統的UUID模組生成的由32位16進位制(0-f)陣列成的的串,本模組進一步壓縮為62進位制。正如你所想,生成的UUID越長,理論衝突率就越小,請根據業務需要自行斟酌。 基本思想為把系統生成的16位元組(128bit)的UUID轉換為62進位制(a-zA-Z0-9),同時根據業務需要進行截斷。
下載地址: https://github.com/dcshi/lua-resty-UUID/archive/master.zip
$ yum -y install libuuid-devel
$ wget https://github.com/dcshi/lua-resty-UUID/archive/master.zip -O lua-resty-UUID-master.zip
$ unzip lua-resty-UUID-master.zip
$ cd lua-resty-UUID-master/clib
$ make
下載nginx sysguard模組
如果nginx被攻擊或者訪問量突然變大,nginx會因為負載變高或者記憶體不夠用導致伺服器當機,最終導致站點無法訪問。 今天要談到的解決方法來自淘寶開發的模組nginx-http-sysguard,主要用於當負載和記憶體達到一定的閥值之時,會執行相應的動作,比如直接返回503,504或者其他的。一直等到記憶體或者負載回到閥值的範圍內,站點恢復可用。簡單的說,這幾個模組是讓nginx有個緩衝時間,緩緩。
$ wget https://github.com/alibaba/nginx-http-sysguard/archive/master.zip -O nginx-http-sysguard-master.zip
$ unzip nginx-http-sysguard-master.zip
安裝 OpenResty
OpenResty(也稱為 ngx_openresty)是一個全功能的 Web 應用伺服器。它打包了標準的 Nginx 核心,很多的常用的第三方模組,以及它們的大多數依賴項。 OpenResty 中的 LuaJIT 元件預設未啟用,需使用
--with-luajit
選項在編譯 OpenResty 時啟用,使用--add-module
,新增上sysguard模組
安裝的版本:1.2.7.6
下載地址:
先安裝依賴軟體,然後在編譯程式碼,編譯時使用--perfix
選項指定 OpenResty 的安裝目錄,--with-luajit
選項啟用 LuaJIT 元件。
$ yum -y install gcc make gmake openssl-devel pcre-devel readline-devel zlib-devel
$ wget http://openresty.org/download/ngx_openresty-1.2.7.6.tar.gz
$ tar zvxf ngx_openresty-1.2.7.6.tar.gz
$ cd ngx_openresty-1.2.7.6
$ ./configure --with-luajit --with-http_stub_status_module --add-module=/opt/nginx-http-sysguard-master/
$ gmake && gmake install
建立軟連線:
$ ln -s /usr/local/openresty/nginx/sbin/nginx /usr/sbin/nginx
安裝 Redis Server
Lua 指令碼功能是 Reids 2.6 版本的最大亮點, 通過內嵌對 Lua 環境的支援, Redis 解決了長久以來不能高效地處理 CAS (check-and-set)命令的缺點, 並且可以通過組合使用多個命令, 輕鬆實現以前很難實現或者不能高效實現的模式。
$ wget http://redis.googlecode.com/files/redis-2.6.14.tar.gz
$ tar zvxf redis-2.6.14.tar.gz
$ cd redis-2.6.14
$ make && make install
$ mkdir -p /usr/local/redis/conf
$ cp redis.conf /usr/local/redis/conf/
安裝 LuaSocket 庫
LuaSocket是一個Lua擴充套件庫,它能很方便地提供SMTP、HTTP、FTP等網路議訪問操作。
LuaSocket版本:luasocket-2.0-beta2
$ wget http://files.luaforge.net/releases/luasocket/luasocket/luasocket-2.0.2/luasocket-2.0.2.tar.gz
$ tar zvxf luasocket-2.0.2.tar.gz
$ cd luasocket-2.0.2
$ make -f makefile.Linux
安裝 redis-lua 庫
Redis-Lua版本:2.0
下載地址: https://github.com/nrk/redis-lua/archive/version-2.0.zip
$ wget https://github.com/nrk/redis-lua/archive/version-2.0.zip
$ unzip redis-lua-version-2.0.zip
$ cd redis-lua-version-2.0
然後,拷貝redis.lua至所需目錄。
lua呼叫方式如下:
local redis = require(“redis”)
安裝 zklua
zklua 僅依賴 zookeeper c API 實現,一般存在於
zookeeper-X.Y.Z/src/c
, 因此你需要首先安裝 zookeeper c API。
zookeeper c API 安裝:
$ wget http://mirrors.cnnic.cn/apache/zookeeper/zookeeper-3.4.5/
$ tar zvxf zookeeper-3.4.5
$ cd zookeeper-3.4.5/src/c
$ ./configure
$ make && make install
然後安裝zklua:
$ wget https://github.com/forhappy/zklua/archive/master.zip -O zklua-master.zip
$ unzip zklua-master.zip
$ cd zklua-master
$ make && make install
修改配置檔案
配置openresty
openresty安裝在/usr/local/openresty
目錄,在其目錄下建立lualib,用於存放上面安裝的一些動態連線庫
mkdir -p /usr/local/openresty/lualib/captcha
cp lua-resty-UUID-master/clib/libuuidx.so /usr/local/openresty/lualib/captcha/ #拷貝uuid的庫檔案
cp -r lua-resty-UUID-master/lib/* /usr/local/openresty/lualib/captcha/
cp luasocket-2.0.2/luasocket.so.2.0 /usr/local/openresty/lualib/captcha/ #拷貝luasocket的庫檔案到/usr/local/openresty/lualib/captcha/
ln -s /usr/local/openresty/lualib/captcha/luasocket.so.2.0 /usr/local/openresty/lualib/captcha/socket.so
cp redis-lua-version-2.0/src/redis.lua /usr/local/openresty/lualib/captcha/ #拷貝reis.lua到/usr/local/openresty/lualib/captcha/
mkdir -p /usr/local/openresty/lualib/zklua #拷貝zklua檔案到/usr/local/openresty/lualib/captcha/
cp cd zklua-master/zklua.so /usr/local/openresty/lualib/zklua/
配置nginx
建立www使用者:
useradd -M -s /sbin/nologin www
編輯ngnix.conf,內容如下:
user www;
worker_processes 31;
error_log logs/error.log;
pid logs/nginx.pid;
worker_rlimit_nofile 65535;
events {
worker_connections 1024;
use epoll;
}
http {
include mime.types;
default_type application/octet-stream;
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;
tcp_nodelay on;
keepalive_timeout 65;
gzip on;
gzip_min_length 1K;
gzip_buffers 4 8k;
gzip_comp_level 2;
gzip_types text/plain image/gif image/png image/jpg application/x-javascript text/css application/xml text/javascript;
gzip_vary on;
upstream redis-pool{
server 127.0.0.1:10005;
keepalive 1024;
}
server {
sysguard on;
sysguard_load load=90 action=/50x.html;
server_tokens off;
listen 10002;
server_name localhost;
charset utf-8;
location / {
root html;
index index.html index.htm;
}
#-----------------------------------------------------------------------------------------
# 驗證碼生成
location /captcha {
set $percent 0;
set $modecount 1;
content_by_lua_file /usr/local/openresty/nginx/luascripts/luajit/captcha.lua;
}
#-----------------------------------------------------------------------------------------
# 驗證碼校驗
location /captcha-check {
content_by_lua_file /usr/local/openresty/nginx/luascripts/luajit/captcha-check.lua;
}
# 驗證碼刪除
location /captcha-delete {
content_by_lua_file /usr/local/openresty/nginx/luascripts/luajit/captcha-delete.lua;
}
#-----------------------------------------------------------------------------------------
# 樣式1-靜態圖片
location /mode1 {
content_by_lua_file /usr/local/openresty/nginx/luascripts/luajit/mode/mode1.lua;
}
#-----------------------------------------------------------------------------------------
# redis中新增key-value
location /redisSetQueue {
internal;
set_unescape_uri $key $arg_key;
set_unescape_uri $val $arg_val;
redis2_query rpush $key $val;
redis2_pass redis-pool;
}
# redis中獲取captcha-string
location /redisGetStr {
internal;
set_unescape_uri $key $arg_key;
redis2_query lindex $key 0;
redis2_pass redis-pool;
}
# redis中獲取captcha-image
location /redisGetImg {
internal;
set_unescape_uri $key $arg_key;
redis2_query lindex $key 1;
redis2_pass redis-pool;
}
#-----------------------------------------------------------------------------------------
location ~.*.(gif|jpg|png)$ {
expires 10s;
}
error_page 404 /404.html;
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
}
上面將 ngnix 的埠修改為10002。
/usr/local/openresty/nginx/luascripts/luajit/captcha.lua 是用於生成驗證碼,內容如下:
--中控指令碼
--
--部分應用預先生成
--部分應用實時生成,並且隨機選擇生成樣式
--
----------------------------------------------------------------------------------------------
package.path = "/usr/local/openresty/lualib/?.lua;/usr/local/openresty/lualib/captcha/?.lua;"
package.cpath = "/usr/local/openresty/lualib/?.so;/usr/local/openresty/lualib/captcha/?.so;"
----------------------------------------------------------------------------------------------
--設定隨機種子
local resty_uuid=require("resty.uuid")
math.randomseed(tonumber(resty_uuid.gennum20()))
-----------------------------------------------------------------------------------------
--
--[[ 預先生成 ]]
--
if math.random(1,99)<tonumber(ngx.var.percent) then
--在redis的預先生成key中隨機選擇keyid
local kid=math.random(1,ngx.var.pregencount)
local res = ngx.location.capture(`/redisGetImg`,{ args = { key = kid } })
if res.status==200 then
local parser=require("redis.parser")
local pic=parser.parse_reply(res.body)
ngx.header.content_type="application/octet-stream"
--在header中返回用於去redis中查詢記錄的key
ngx.header.picgid=kid
--在body中返回captcha
ngx.say(pic)
ngx.exit(200)
end
end
-----------------------------------------------------------------------------------------
--
--[[ 實時生成 ]]
--
--隨機選擇captcha模式X
local mode=math.random(1,ngx.var.modecount)
--呼叫modeX.lua,生成captcha
local res = ngx.location.capture("/mode"..mode)
if res.status==200 then
ngx.header.content_type="application/octet-stream"
--在header中返回用於去redis中查詢記錄的key
ngx.header.picgid=res.header.picgid
--在body中返回captcha
ngx.say(res.body)
ngx.exit(200)
end
/usr/local/openresty/nginx/luascripts/luajit/captcha-check.lua 用於校驗驗證碼:
--[[captcha check]]
----------------------------------------------------------------------------------------------
package.path = "/usr/local/openresty/lualib/?.lua;/usr/local/openresty/lualib/captcha/?.lua;"
package.cpath = "/usr/local/openresty/lualib/?.so;/usr/local/openresty/lualib/captcha/?.so;"
----------------------------------------------------------------------------------------------
--獲取請求中引數
local uriargs = ngx.req.get_uri_args()
local picgid = uriargs["image"]
local ustr=string.lower(uriargs["str"])
--查詢redis中key為picgid的記錄
local res = ngx.location.capture(`/redisGetStr`,{ args = { key = picgid } })
if res.status==200 then
local parser=require("redis.parser")
local reply=parser.parse_reply(res.body)
local rstr=string.lower(reply)
--匹配使用者輸入字串與redis中記錄的字串,一致返回True,否則返回False
ngx.header.content_type="text/plain"
if ustr == rstr then
ngx.say("True")
else
ngx.say("False")
end
--匹配操作後刪除redis中該key記錄
local redis = require(`redis`)
local client = redis.connect(`127.0.0.1`, 10005)
client:del(picgid)
end
/usr/local/openresty/nginx/luascripts/luajit/mode/mode1.lua 是生成靜態驗證碼圖片:
--靜態圖片
------------------------------------------------------------------------------------------------
package.path = "/usr/local/openresty/lualib/?.lua;/usr/local/openresty/lualib/captcha/?.lua;"
package.cpath = "/usr/local/openresty/lualib/?.so;/usr/local/openresty/lualib/captcha/?.so;"
------------------------------------------------------------------------------------------------
--Redis中插入記錄方法
function setRedis(skey, sval)
local res = ngx.location.capture(`/redisSetQueue`, {args= {key=skey,val=sval}})
if res.status == 200 then
return true
else
return false
end
end
--設定隨機種子
local resty_uuid=require("resty.uuid")
math.randomseed(tonumber(resty_uuid.gennum20()))
--在32個備選字元中隨機篩選4個作為captcha字串
local dict={`A`,`B`,`C`,`D`,`E`,`F`,`G`,`H`,`J`,`K`,`L`,`M`,`N`,`P`,`Q`,`R`,`S`,`T`,`U`,`V`,`W`,`X`,`Y`,`Z`,`2`,`3`,`4`,`5`,`6`,`7`,`8`,`9`}
local stringmark=""
for i=1,4 do
stringmark=stringmark..dict[math.random(1,32)]
end
--圖片基本info
--picgid
local filename= "1"..resty_uuid.gen20()..".png"
--圖片78x26
local xsize = 78
local ysize = 26
--字型大小
local wsize = 17.5
--干擾線(yes/no)
local line = "yes"
--載入模組
local gd=require(`gd`)
--建立皮膚
local im = gd.createTrueColor(xsize, ysize)
--定義顏色
local black = im:colorAllocate(0, 0, 0)
local grey = im:colorAllocate(202,202,202)
local color={}
for c=1,100 do
color[c] = im:colorAllocate(math.random(100),math.random(100),math.random(100))
end
--畫背景
x, y = im:sizeXY()
im:filledRectangle(0, 0, x, y, grey)
--畫字元
gd.useFontConfig(true)
for i=1,4 do
k=(i-1)*16+3
im:stringFT(color[math.random(100)],"Arial:bold",wsize,math.rad(math.random(-10,10)),k,22,string.sub(stringmark,i,i))
end
--干擾線點
if line=="yes" then
for j=1,math.random(3) do
im:line(math.random(xsize),math.random(ysize),math.random(xsize),math.random(ysize),color[math.random(100)])
end
for p=1,20 do
im:setPixel(math.random(xsize),math.random(ysize),color[math.random(100)])
end
end
--流輸出
local fp=im:pngStr(75)
--redis中新增picgid為key,string為value的記錄
setRedis(filename,stringmark)
--response header中傳參picgid
ngx.header.content_type="text/plain"
ngx.header.picgid=filename
--頁面返回pic
ngx.say(fp)
--nginx退出
ngx.exit(200)
配置redis
在/usr/local/openresty/redis/conf/
建立redis-10005.conf檔案,內容如下:
daemonize yes
pidfile /usr/local/openresty/redis/redis-10005.pid
port 10005
timeout 300
tcp-keepalive 10
loglevel notice
logfile /usr/local/openresty/redis/redis-10005.log
databases 16
save 900 1
save 300 10
save 60 10000
stop-writes-on-bgsave-error yes
rdbcompression yes
rdbchecksum yes
dbfilename dump-10005.rdb
dir /usr/local/openresty/redis
slave-serve-stale-data yes
slave-read-only yes
repl-disable-tcp-nodelay no
slave-priority 100
appendonly no
appendfsync everysec
no-appendfsync-on-rewrite no
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
lua-time-limit 5000
slowlog-log-slower-than 10000
slowlog-max-len 128
hash-max-ziplist-entries 512
hash-max-ziplist-value 64
list-max-ziplist-entries 512
list-max-ziplist-value 64
set-max-intset-entries 512
zset-max-ziplist-entries 128
zset-max-ziplist-value 64
activerehashing yes
client-output-buffer-limit normal 0 0 0
client-output-buffer-limit slave 256mb 64mb 60
client-output-buffer-limit pubsub 32mb 8mb 60
hz 10
配置驗證碼伺服器
在/etc/ld.so.conf.d/
目錄建立captcha.conf,內容如下:
$ vim /etc/ld.so.conf.d/captcha.conf
/usr/local/lib
/usr/local/openresty/lualib
/usr/local/openresty/lualib/captcha
/usr/local/openresty/lualib/zklua
/usr/local/openresty/luajit/lib
測試
生成驗證碼
URL:http://IP:10002/captcha
然後從響應Header中獲取圖片的picgid=XXXXX
驗證碼校驗
URL:http://IP:10002/captcha-check?image=XXXXX&str=ABCD http://IP:10002/captcha-check?image=XXXXX&str=ABCD&delete=true 或 http://IP:10002/captcha-check?image=XXXXX&str=ABCD&delete=false
引數說明如下:
- 引數image:要校驗的驗證碼圖片的picgid。
- 引數str:使用者輸入的驗證碼字串。
- 引數delete:當且僅當傳該引數且引數值為false時,校驗完成之後該驗證碼記錄不被刪除,驗證碼未過期之前可多次校驗,用於非同步校驗應用中;否則,若不傳該引數或者其值為true,校驗完成之後該驗證碼記錄刪除。
驗證碼刪除
URL:http://IP:10002/captcha-delete?image=XXXXX
其中image為要刪除的驗證碼圖片的picgid。
相關文章
- 安裝lua和openrestyREST
- openresty使用lua操作mysqlRESTMySql
- openresty+redis配合 lua指令碼封停 IPRESTRedis指令碼
- Lua OpenResty容器化(考古歷程)REST
- 使用 Nginx + Lua(OpenResty)開發高效能Web應用NginxRESTWeb
- OpenResty + Lua 動態增加 Zuul 節點RESTZuul
- openresty及lua的隨機函式REST隨機函式
- OpenResty debugger: lua-resty-replREST
- JavaScript驗證碼生成和驗證效果JavaScript
- [lua][openresty]程式碼覆蓋率檢測的解決方式REST
- cetnos7下openresty使用luarocks 進行lua的包管理REST
- 使用 Testinfra 和 Ansible 驗證伺服器狀態伺服器
- OpenResty體驗REST
- 驗證碼機制之驗證碼重複使用
- OpenResty+lua+redis+mysql多級快取RESTRedisMySql快取
- openresty通過lua增加隨機traceidREST隨機
- nginx+lua(OpenResty),實現訪問限制NginxREST
- 使用JCaptcha生成驗證碼APT
- Redis 使用 Lua 指令碼替代 SETNX / DECR 保證原子性Redis指令碼
- 模擬使用者登入,內含驗證碼驗證和request等操作
- mac搭建openresty服務MacREST
- PHP算式驗證碼和漢字驗證碼的實現方法PHP
- Google的kaptcha驗證碼使用GoAPT
- Redis篇:事務和lua指令碼的使用Redis指令碼
- Redis使用Lua指令碼Redis指令碼
- Struts 驗證框架 配置和使用框架
- 驗證碼原理及驗證
- Linux 搭建互信後,仍需要密碼驗證Linux密碼
- js驗證使用者名稱和密碼為空程式碼JS密碼
- 用 Nginx + Lua(OpenResty) 開發高效能 Web 應用NginxRESTWeb
- 驗證碼---js重新整理驗證碼JS
- 使用node+puppeteer破解驗證碼
- 關於OpenResty中使用lua-resty-jwt出現的一個異常symbol not foundRESTJWTSymbol
- 在 Ubuntu 上使用原始碼安裝 OpenRestyUbuntu原始碼REST
- js 驗證使用者名稱和密碼是否為空JS密碼
- oracle本地驗證和密碼檔案Oracle密碼
- 驗證碼的識別和運用
- 在國內使用Google驗證碼reCaptchaGoAPT