从构建分布式秒杀系统聊聊验证码

栏目: 服务器 · 发布时间: 5年前

内容简介:为了拦截大部分请求,秒杀案例前端引入了验证码。淘宝上很多人吐槽,等输入完秒杀活动结束了,对,结束了...... 当然了,验证码的真正作用是,有效拦截刷单操作,让羊毛党空手而归。那么到底什么是验证码呢?验证码作为一种人机识别手段,其终极目的,就是区分正常人和机器的操作。我们常见的互联网注册、登录、发帖、领优惠券、投票等等应用场景,都有被机器刷造成各类损失的风险。

从构建分布式秒杀系统聊聊验证码

前言

为了拦截大部分请求,秒杀案例前端引入了验证码。淘宝上很多人吐槽,等输入完秒杀活动结束了,对,结束了...... 当然了,验证码的真正作用是,有效拦截刷单操作,让羊毛党空手而归。

验证码

那么到底什么是验证码呢?验证码作为一种人机识别手段,其终极目的,就是区分正常人和机器的操作。我们常见的互联网注册、登录、发帖、领优惠券、投票等等应用场景,都有被机器刷造成各类损失的风险。

目前常见的验证码形式多为图片验证码,即数字、字母、文字、图片物体等形式的传统字符验证码。这类验证码看似简单易操作,但实际用户体验较差(参见12306网站),且随着OCR技术和打码平台的利用,图片比较容易被破解,被破解之后就形同虚设。

这里我们使用腾讯的智能人机安全验证码,告别传统验证码的单点防御,十道安全栅栏打造立体全面的安全验证,将黑产拒之门外。

场景

从构建分布式秒杀系统聊聊验证码

下面我们来瞅瞅验证码轻松解决了那些场景安全问题:

  • 登录注册,为你防护撞库攻击、阻止注册机批量注册
  • 活动秒杀,有效拦截刷单操作,让羊毛党空手而归
  • 点赞发帖,有效解决广告屠版、恶意灌水、刷票问题
  • 数据保护,防止自动机、爬虫盗取网页内容和数据

申请

申请地址: https://007.qq.com/product.html

在线体验: https://007.qq.com/online.html

只要一个QQ就可以免费申请,对于一般的企业OA系统或者个人博客网站,验证码免费套餐足够了已经,具备以下特点:

  • 2000次/小时安全防护
  • 支持免验证+分级验证
  • 三分钟快速接入
  • 全功能配置后台
  • 支持HTTPS
  • 阈值内流量无广告

2000次/小时的安全防护,一般很少达到如此效果,当然了即时超出阈值,顶多也就是多个广告而已。

接入

快读接入: https://007.qq.com/quick-start.html

接入与帮助提供了多种客户端和服务端的接入案例,这里我们使用我们秒杀案例中最熟悉的 Java 语言来接入。

前端

引入JS:

<script src="https://ssl.captcha.qq.com/TCaptcha.js"></script>

页面元素:

<!--点击此元素会自动激活验证码,不一定是button,其他标签也可以-->
<!--id : 元素的id(必须)-->
<!--data-appid : AppID(必须)-->
<!--data-cbfn : 回调函数名(必须)-->
<!--data-biz-state : 业务自定义透传参数(可选)-->
<button id="TencentCaptcha"
        data-appid="*********"
        data-cbfn="callback">验证</button>

JS回调:

<script type="text/javascript">
    window.callback = function(res){
        console.log(res)
        // res(未通过验证)= {ret: 1, ticket: null}
        // res(验证成功) = {ret: 0, ticket: "String", randstr: "String"}
        if(res.ret === 0){
            startSeckill(res)
        }
    }
    //后台验证ticket,并进入秒杀队列
    function startSeckill(res){
        $.ajax({
            url : "startSeckill",
            type : 'post',
            data : {'ticket' : res.ticket,'randstr':res.randstr},
            success : function(result) {
                //验证是否通过,提示用户
            }
        });
    }
</script>

后端

@Api(tags = "秒杀商品")
@RestController
@RequestMapping("/seckillPage")
public class SeckillPageController {
    
    @Autowired
    private KafkaSender kafkaSender;
    //自定义 工具 类
    @Autowired
    private HttpClient httpClient;
    //这里自行配置参数
    @Value("${qq.captcha.url}")
    private String url;
    @Value("${qq.captcha.aid}")
    private String aid;
    @Value("${qq.captcha.AppSecretKey}")
    private String appSecretKey;
    
    @RequestMapping("/startSeckill")
    public Result  startSeckill(String ticket,String randstr,HttpServletRequest request) {
        HttpMethod method =HttpMethod.POST;
        MultiValueMap<String, String> params= new LinkedMultiValueMap<String, String>();
        params.add("aid", aid);
        params.add("AppSecretKey", appSecretKey);
        params.add("Ticket", ticket);
        params.add("Randstr", randstr);
        params.add("UserIP", IPUtils.getIpAddr(request));
        String msg = httpClient.client(url,method,params);
        /**
         * response: 1:验证成功,0:验证失败,100:AppSecretKey参数校验错误[required]
         * evil_level:[0,100],恶意等级[optional]
         * err_msg:验证错误信息[optional]
         */
        //{"response":"1","evil_level":"0","err_msg":"OK"}
        JSONObject json = JSONObject.parseObject(msg);
        String response = (String) json.get("response");
        if("1".equals(response)){
            //进入队列、假数据而已
            kafkaSender.sendChannelMess("seckill",1000+";"+1);
            return Result.ok();
        }else{
            return Result.error("验证失败");
        }
    }
}

自定义请求工具类 HttpClient:

@Service
public class HttpClient {
    public String client(String url, HttpMethod method, MultiValueMap<String, String> params){
        RestTemplate client = new RestTemplate();
        HttpHeaders headers = new HttpHeaders();
        //  请勿轻易改变此提交方式,大部分的情况下,提交方式都是表单提交
        headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
        HttpEntity<MultiValueMap<String, String>> requestEntity = new HttpEntity<MultiValueMap<String, String>>(params, headers);
        //  执行HTTP请求
        ResponseEntity<String> response = client.exchange(url, HttpMethod.POST, requestEntity, String.class);
        return response.getBody();
    }
}

获取IP地址工具类 IPUtils :

/**
 * IP地址
 */
public class IPUtils {

    private static Logger logger = LoggerFactory.getLogger(IPUtils.class);

    /**
     * 获取IP地址
     * 使用Nginx等反向代理软件, 则不能通过request.getRemoteAddr()获取IP地址
     * 如果使用了多级反向代理的话,X-Forwarded-For的值并不止一个,而是一串IP地址,X-Forwarded-For中第一个非unknown的有效IP字符串,则为真实IP地址
     */
    public static String getIpAddr(HttpServletRequest request) {
        String ip = null;
        try {
            ip = request.getHeader("x-forwarded-for");
            if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
                ip = request.getHeader("Proxy-Client-IP");
            }
            if (StringUtils.isEmpty(ip) || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
                ip = request.getHeader("WL-Proxy-Client-IP");
            }
            if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
                ip = request.getHeader("HTTP_CLIENT_IP");
            }
            if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
                ip = request.getHeader("HTTP_X_FORWARDED_FOR");
            }
            if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
                ip = request.getRemoteAddr();
            }
        } catch (Exception e) {
            logger.error("IPUtils ERROR ", e);
        }
        // 使用代理,则获取第一个IP地址
        if (StringUtils.isEmpty(ip) && ip.length() > 15) {
            if (ip.indexOf(",") > 0) {
                ip = ip.substring(0, ip.indexOf(","));
            }
        }
        return ip;
    }
}
从构建分布式秒杀系统聊聊验证码

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

The Little Typer

The Little Typer

Daniel P. Friedman、David Thrane Christiansen、Duane Bibby、Robert Harper、Conor Mcbride / MIT Press / 2018-10-16 / GBP 30.00

An introduction to dependent types, demonstrating the most beautiful aspects, one step at a time. A program's type describes its behavior. Dependent types are a first-class part of a language, and are......一起来看看 《The Little Typer》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具