人机身份验证实现,安全防护,防刷防爬虫

前言

现在很多网站考虑安全,会做人机验证,可以有效的防刷,防爬虫,防止暴力破解。
你是否遇到过这个
0
这个
0
还有这个
0
 

如何实现?

如何实现人机验证,又不用和前端耦合在一起,类似waf,在第一层做验证,不通过就直接拦截。nginx支持调用lua脚本,于是可以使用nginx_lua_module来实现。
我们先看看Nginx指令处理阶段:
0
访问某个需要人机验证的页面时,在content_by_lua做处理,如果没经过人机验证,我们返回人机验证页面,浏览器人机验证后,做缓存和cookies,我定了12个小时。也就是12小时内任何页面不需要再做人机验证。
大概的流程如下:
 
0

实现人机验证步骤

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、实现效果如下:

 

0
true:验证成功,自动跳到具体页面
false:不成功,显示失败页面。
 
人机验证过程中,浏览器的访问地址不会发生变化。所有逻辑都在nginx代理层动态返回加载。
我用的是用户端无交互的人机验证。也可以对接需要交互的,就是点击验证码,或者点击什么都可以。可自行自定义
 

演示地址

 
 首次访问,以下三个页面,任意一个通过人机验证,就不需要再次进行人机验证了
 
RESTfulApi
如果没先经过页面通过人机验证,禁止调用以下接口
(正常我们都是先渲染html标签,然后js调用接口请求数据,所以逻辑上要先访问页面,才是接口)
(如果不通过网站访问,直接访问接口,基本可以判定为爬虫恶意请求)
 
posted @ 2023-10-09 16:14  suruozhong  阅读(535)  评论(0编辑  收藏  举报