防刷验证

1.“问题-答案”模式的图片验证

常见的图片验证有字母数字组合简单算术题简单选择题等等,这种类型的验证码的可行性在于人类能够轻松识别、判断,而机器比较难区分,但随着人工智能和机器学习的不断发展,这类验证码越来越容易破解了,不过生成图片的时候做一定程度的干扰(比如字符重叠、增加干扰线条、使用非纯色背景、甚至采用gif)可以加大破解成本。

1.1 工作流程(以登录验证为例,其它业务场景同样适用)

1.进入登录页,客户端向服务端发送预登录请求;

2.服务端生成随机字符串verify_code(通常为字母+数字组合),并以字符串为内容生成图片verify_image,同时创建一个唯一的verify_key与字符串绑定在服务端存储(可以设置有效期),将verify_imageverify_key一并作为预登录的响应结果返回客户端;

3.用户登录,客户端向服务端发送登录请求,以用户输入的账号、密码、验证码input_code以及预登录请求得到verify_key作为请求参数;

4.服务端接收登录请求后检查参数是否完整,若参数完整,则根据verify_key获取存储的verify_codeinput_code进行比较(无论是否一致均删除此条记录,保证一个验证码只能验证一次),完全一致则继续执行账号、密码验证,否则直接登录失败。

1.2 代码实现

VerifyCode.kt (服务端生成图片)

import java.awt.BasicStroke
import java.awt.Color
import java.awt.Font
import java.awt.Graphics2D
import java.awt.image.BufferedImage
import java.io.File
import java.io.IOException
import java.io.OutputStream
import java.util.Random

import javax.imageio.ImageIO
import javax.imageio.stream.FileImageOutputStream

/**
 * 验证码工具类
 * 代码来自 https://blog.csdn.net/yangxuwang888/article/details/81431705
 */
class VerifyCode {
    private val w = 70
    private val h = 35
    private val r = Random()
    // {"宋体", "华文楷体", "黑体", "华文新魏", "华文隶书", "微软雅黑", "楷体_GB2312"}
    private val fontNames = arrayOf("宋体", "华文楷体", "黑体", "微软雅黑", "楷体_GB2312")
    private val codes = "23456789abcdefghjkmnopqrstuvwxyzABCDEFGHJKMNPQRSTUVWXYZ"
    private val bgColor = Color(255, 255, 255)

    // 向图片中画4个字符
    /**
     * 首字符的基线位于用户空间的 (x, h-5) 位置处
     * 原点在左上角,X轴递增的方向是从左向右;Y轴是从上到下
     * 在提供的坐标位于基线上最左边字符的情况下,可以从右到左呈现字形
     * h-5表示y轴方向,向上偏移了5
     */
    val image: BufferedImage
        get() {
            val image = createImage()
            val g2 = image.graphics as Graphics2D
            val sb = StringBuilder()
            for (i in 0..3) {
                val s = randomChar() + ""
                sb.append(s)
                val x = i.toFloat() * 1.0f * w.toFloat() / 4
                g2.font = randomFont()
                g2.color = randomColor()
                g2.drawString(s, x, (h - 5).toFloat())
            }
            text = sb.toString()
            drawLine(image)
            return image
        }

    private fun randomColor(): Color {
        val red = r.nextInt(150)
        val green = r.nextInt(150)
        val blue = r.nextInt(150)
        return Color(red, green, blue)
    }

    private fun randomFont(): Font {
        val index = r.nextInt(fontNames.size)
        val fontName = fontNames[index]
        val style = r.nextInt(4)
        val size = r.nextInt(5) + 24
        return Font(fontName, style, size)//指定字体名称、样式和点大小,创建一个新 Font。
    }

    //画干扰的线条
    private fun drawLine(image: BufferedImage) {
        val num = 3//画三条
        val g2 = image.graphics as Graphics2D
        for (i in 0 until num) {
            val x1 = r.nextInt(w)
            val y1 = r.nextInt(h)
            val x2 = r.nextInt(w)
            val y2 = r.nextInt(h)
            g2.stroke = BasicStroke(1.5f)
            g2.color = Color.BLUE
            g2.drawLine(x1, y1, x2, y2)
        }
    }

    private fun randomChar(): Char {
        val index = r.nextInt(codes.length)
        return codes[index]
    }

    private fun createImage(): BufferedImage {
        val image = BufferedImage(w, h, BufferedImage.TYPE_INT_RGB)
        val g2 = image.graphics as Graphics2D
        g2.color = this.bgColor
        g2.fillRect(0, 0, w, h)
        return image
    }

    companion object {
        var text: String? = null

        @Throws(IOException::class)
        fun output(image: BufferedImage, out: OutputStream) {
            ImageIO.write(image, "JPEG", out)
        }
    }
}

/*
fun main(args: Array) {
    for (i in 1..10) {
        val code = VerifyCode()
        val image = code.image
        //获取图片
        ImageIO.write(image, "jpg", FileImageOutputStream(File("/tmp/$i.jpg")))
        //获取图片中的文字
        println(VerifyCode.text)
    }
}
*/

TestController.kt (基于SpringMVC实现的用于测试的API)

import org.springframework.beans.factory.annotation.Autowired
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RestController
import javax.imageio.ImageIO
import javax.servlet.http.HttpServletResponse
import sun.misc.BASE64Encoder
import java.io.ByteArrayOutputStream


@RestController
class TestController {

    @Autowired
    private lateinit var response: HttpServletResponse

    /**
     * 直接返回图片
     */
    @GetMapping("/test")
    fun test() {
        val code = VerifyCode()
        val image = code.image
        //获取图片
        ImageIO.write(image, "jpg", response.outputStream)
        //获取图片中的文字
        println(VerifyCode.text)
    }

    /**
     * 将图片base64编码返回
     */
    @GetMapping("/test2")
    fun test2(): String {
        val code = VerifyCode()
        val image = code.image
        val byteArrayOutputStream = ByteArrayOutputStream()//io流
        ImageIO.write(image, "jpg", byteArrayOutputStream)//写入流中
        val bytes = byteArrayOutputStream.toByteArray()//转换成字节
        val encoder = BASE64Encoder()
        var base64 = encoder.encodeBuffer(bytes).trim()//转换成base64串
        base64 = base64.replace("\n".toRegex(), "").replace("\r".toRegex(), "")//删除 \r\n
        return base64
    }
}

test.html (显示验证码的测试页面)




    
    Title






其它逻辑按部就班实现起来没有什么花头,不再一一赘述。

2.利用验证行为的特征区分人机差异的行为验证

典型案例:极验验证码

「行为验证」不单纯基于“问题-答案”的模式来区别人机,而是基于完成验证过程中的行为模式和行为特征,通过深度学习对行为数据进行高维分析,构建人机边界。
摘自 https://docs.geetest.com/install/overview/prodes/

具体实现原理和交互逻辑可以查看极验官网。当然,极验行为验证的核心技术在于机器学习,这部分内容的实现在极验的后台,对使用者而言是不可见的。

3.其它验证

  • 淘宝使用过的选择你曾经购买过的商品图片...
  • 12306使用过的小朋友认图片(体验很差,被喷成狗屎)
  • ......

你可能感兴趣的:(防刷验证)