经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » Java相关 » Java » 查看文章
从构建分布式秒杀系统聊聊验证码
来源:cnblogs  作者:小柒2012  时间:2018/9/28 17:06:40  对本文有异议

前言

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

验证码

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

目前常见的验证码形式多为图片验证码,即数字、字母、文字、图片物体等形式的传统字符验证码。这类验证码看似简单易操作,但实际用户体验较差(参见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:

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

页面元素:

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

JS回调:

  1. <script type="text/javascript">
  2. window.callback = function(res){
  3. console.log(res)
  4. // res(未通过验证)= {ret: 1, ticket: null}
  5. // res(验证成功) = {ret: 0, ticket: "String", randstr: "String"}
  6. if(res.ret === 0){
  7. startSeckill(res)
  8. }
  9. }
  10. //后台验证ticket,并进入秒杀队列
  11. function startSeckill(res){
  12. $.ajax({
  13. url : "startSeckill",
  14. type : 'post',
  15. data : {'ticket' : res.ticket,'randstr':res.randstr},
  16. success : function(result) {
  17. //验证是否通过,提示用户
  18. }
  19. });
  20. }
  21. </script>

后端

  1. @Api(tags = "秒杀商品")
  2. @RestController
  3. @RequestMapping("/seckillPage")
  4. public class SeckillPageController {
  5. @Autowired
  6. private ActiveMQSender activeMQSender;
  7. //自定义工具类
  8. @Autowired
  9. private HttpClient httpClient;
  10. //这里自行配置参数
  11. @Value("${qq.captcha.url}")
  12. private String url;
  13. @Value("${qq.captcha.aid}")
  14. private String aid;
  15. @Value("${qq.captcha.AppSecretKey}")
  16. private String appSecretKey;
  17. @RequestMapping("/startSeckill")
  18. public Result startSeckill(String ticket,String randstr,HttpServletRequest request) {
  19. HttpMethod method =HttpMethod.POST;
  20. MultiValueMap<String, String> params= new LinkedMultiValueMap<String, String>();
  21. params.add("aid", aid);
  22. params.add("AppSecretKey", appSecretKey);
  23. params.add("Ticket", ticket);
  24. params.add("Randstr", randstr);
  25. params.add("UserIP", IPUtils.getIpAddr(request));
  26. String msg = httpClient.client(url,method,params);
  27. /**
  28. * response: 1:验证成功,0:验证失败,100:AppSecretKey参数校验错误[required]
  29. * evil_level:[0,100],恶意等级[optional]
  30. * err_msg:验证错误信息[optional]
  31. */
  32. //{"response":"1","evil_level":"0","err_msg":"OK"}
  33. JSONObject json = JSONObject.parseObject(msg);
  34. String response = (String) json.get("response");
  35. if("1".equals(response)){
  36. //进入队列、假数据而已
  37. Destination destination = new ActiveMQQueue("seckill.queue");
  38. activeMQSender.sendChannelMess(destination,1000+";"+1);
  39. return Result.ok();
  40. }else{
  41. return Result.error("验证失败");
  42. }
  43. }
  44. }

自定义请求工具类 HttpClient:

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

获取IP地址工具类 IPUtils :

  1. /**
  2. * IP地址
  3. */
  4. public class IPUtils {
  5. private static Logger logger = LoggerFactory.getLogger(IPUtils.class);
  6. /**
  7. * 获取IP地址
  8. * 使用Nginx等反向代理软件, 则不能通过request.getRemoteAddr()获取IP地址
  9. * 如果使用了多级反向代理的话,X-Forwarded-For的值并不止一个,而是一串IP地址,X-Forwarded-For中第一个非unknown的有效IP字符串,则为真实IP地址
  10. */
  11. public static String getIpAddr(HttpServletRequest request) {
  12. String ip = null;
  13. try {
  14. ip = request.getHeader("x-forwarded-for");
  15. if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
  16. ip = request.getHeader("Proxy-Client-IP");
  17. }
  18. if (StringUtils.isEmpty(ip) || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
  19. ip = request.getHeader("WL-Proxy-Client-IP");
  20. }
  21. if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
  22. ip = request.getHeader("HTTP_CLIENT_IP");
  23. }
  24. if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
  25. ip = request.getHeader("HTTP_X_FORWARDED_FOR");
  26. }
  27. if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
  28. ip = request.getRemoteAddr();
  29. }
  30. } catch (Exception e) {
  31. logger.error("IPUtils ERROR ", e);
  32. }
  33. // 使用代理,则获取第一个IP地址
  34. if (StringUtils.isEmpty(ip) && ip.length() > 15) {
  35. if (ip.indexOf(",") > 0) {
  36. ip = ip.substring(0, ip.indexOf(","));
  37. }
  38. }
  39. return ip;
  40. }
  41. }

案例效果图

启动项目访问:http://localhost:8080/seckill/1000.shtml

定制接入

在系统登录的时候,我们需要先校验用户名以及密码,然后调用验证码操作,这里就需要我们定制接入了。

  1. <!-- 项目中使用了Vue -->
  2. <div class="log_btn" @click="login" >登录</div>
  1. login: function () {
  2. //这里校验用户名以及密码
  3. // 直接生成一个验证码对象
  4. var captcha = new TencentCaptcha('2001344788', function(res) {
  5. if(res.ret === 0){//回调成功
  6. var data = {'username':username,'password':password,'ticket':res.ticket,'randstr':res.randstr}
  7. $.ajax({
  8. type: "POST",
  9. url: "sys/loginCaptcha",
  10. data: data,
  11. dataType: "json",
  12. success: function(result){
  13. //校验是否成功
  14. }
  15. });
  16. }
  17. });
  18. captcha.show(); // 显示验证码
  19. },

后台监控

腾讯后台还提供了简单实用的数据监控,如下:

小结

总体来说,系统接入人机验证码还是很方便的,并没有技术难点,难点已经被提供商封装,我们只需要简单的调用即可。

秒杀案例:https://gitee.com/52itstyle/spring-boot-seckill

演示案例(点击生成按钮):http://jichou.52itstyle.com

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号