人机身份验证实现,安全防护,防刷防爬虫
前言
现在很多网站考虑安全,会做人机验证,可以有效的防刷,防爬虫,防止暴力破解。
你是否遇到过这个
这个
还有这个
如何实现?
如何实现人机验证,又不用和前端耦合在一起,类似waf,在第一层做验证,不通过就直接拦截。nginx支持调用lua脚本,于是可以使用nginx_lua_module来实现。
我们先看看Nginx指令处理阶段:
访问某个需要人机验证的页面时,在content_by_lua做处理,如果没经过人机验证,我们返回人机验证页面,浏览器人机验证后,做缓存和cookies,我定了12个小时。也就是12小时内任何页面不需要再做人机验证。
大概的流程如下:
实现人机验证步骤
1、申请谷歌recaptcha接口参数,https://www.google.com/recaptcha/admin
2、安装lua环境,nginx编译安装lua-nginx-module
3、nginx配置需要做人机验证的页面
# 网站需要人机验证的页面
location ~ ^(/index|/public|/search|search/(\d*))$ {
content_by_lua_file lua/recaptcha.lua;
}
4、nginx配置需要过滤的restfulApi
# 人机验证通过,需要过滤的接口
location ~ ^(/api1|api2|api3)$ {
include vhost/valid.conf;
access_by_lua_file lua/api_filter.lua;
}
5、编写recaptcha.lua做页面人机验证逻辑
-- 1、解密cookies,ip是否已存在缓存
-- local headers = ngx.req.get_headers()
-- 如果已存在验证过的cookies cap,解析正确则不拦截
local zhong_cap = ngx.var.cookie_cap
if zhong_cap and #zhong_cap > 0 then
local aes_128_cbc_with_iv = assert(aes:new(key,nil, aes.cipher(128,"cbc"), {iv=iv}))
-- AES 128 CBC with IV and no SALT
local cipherBytes = ngx.decode_base64(zhong_cap)
local cache_ip = aes_128_cbc_with_iv:decrypt(cipherBytes)
if cache_ip ~= nil and cache_ip and #cache_ip > 0 then
ngx.log(ngx.INFO, "-------------cache_ip-------------"..cache_ip)
local request_ip = ngx.var.ip
ngx.log(ngx.INFO, "-------------request_ip-------------"..request_ip)
if cache_ip == request_ip then
cap_cache = "1"
-- redis续期
redis_util.set_redis("cap:"..request_ip,cap,cap_second)
-- ip已经缓存过,直接放行
ngx.log(ngx.INFO, "-------------正常转发-------------")
ngx.exec("@proxy_pass")
return;
end
end
end
ngx.log(ngx.INFO, "-------------人机验证-------------"..json.encode(ngx.var.request_uri))
local html =
"<script>"
.." window.onload = function() {"
.." window.challenge_conf = {'is_interactive':1,'cserver_addr':'"/recaptcha/html/cap3.html','rule_id':1 };"
.." var challenge_uri = '"/recaptcha/html/cap3.html';"
.." var xhr = new XMLHttpRequest();"
.." xhr.open('GET', challenge_uri + '?&' + Math.random());"
.." xhr.send();"
.." xhr.onload = function() {"
.." if (xhr.status == 200) {"
.." console.log(xhr.response);"
.." document.write(xhr.response);"
.." document.close();"
.." }"
.." };"
.." }"
.."</script>"
.."<p>加载中...</p>"
ngx.header.content_type="text/html;charset=utf8"
ngx.say(html)
return、
6、编写api_filter过滤是否通过人机验证逻辑
-- 判断白名单(白名单直接通过)
for key, value in ipairs(white_apis) do
local url = value["url"]
local args = value["args"]
if(url == request_uri) then
ngx.log(ngx.INFO,"3")
if not args then
-- 白名单,无参数,直接通过
ngx.exec("@proxy_pass")
return
else
local flag = 1
for args_k,args_v in pairs(args) do
local request_v = tostring(arg_json[args_k])
args_v = tostring(args_v)
if(args_v=="" or args_v == nil or args_v == 'nil')then
if(request_v == "" or request_v == nil or request_v == 'nil')then
else
flag = 0
end
else
if(args_v == request_v) then
else
flag = 0
end
end
end
if(flag == 1)then
ngx.log(ngx.INFO,"白名单通过"..request_url)
ngx.exec("@proxy_pass")
return;
end
end
end
end
-- 判断接口是否已通过人机验证
local remoteIp = ngx.var.ip
local val = redis_util.get_redis("cap:"..remoteIp)
ngx.log(ngx.INFO,"缓存"..json.encode(val))
if(val == "null")then
ngx.exit(403)
-- ngx.header.content_type="application/json;charset=UTF-8"
-- local ret = {
-- code= "200",
-- data= {
-- current= 1,
-- size=15,
-- },
-- msg= "操作成功",
-- success= true,
-- }
-- ngx.say(json.encode(ret))
return;
else
ngx.exec("@proxy_pass")
return;
end
7、实现效果如下:
true:验证成功,自动跳到具体页面
false:不成功,显示失败页面。
人机验证过程中,浏览器的访问地址不会发生变化。所有逻辑都在nginx代理层动态返回加载。
我用的是用户端无交互的人机验证。也可以对接需要交互的,就是点击验证码,或者点击什么都可以。可自行自定义
演示地址
首次访问,以下三个页面,任意一个通过人机验证,就不需要再次进行人机验证了
RESTfulApi
如果没先经过页面通过人机验证,禁止调用以下接口
(正常我们都是先渲染html标签,然后js调用接口请求数据,所以逻辑上要先访问页面,才是接口)
(如果不通过网站访问,直接访问接口,基本可以判定为爬虫恶意请求)