整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

php实现简易验证码(仅供初学者测试玩玩)

php结合html怎么实现验证码,这可能是很多人想知道的,尤其是php初学者。其实想要实现验证码的方式有很多种,但是对于初学者来说,有得可能过于复杂不适合新手。这里提供一个很简易的,仅供初学者试玩,不要觉得瞧不起,再复杂的技术也是从一些简单的代码延伸出来的。

1、首先新建一个php文件,输入html基本模板,然后将下面这段php代码插入body里:

这段代码首先声明了编码格式UTF-8,其次定义了一个空字符,接着通过字符串连接功能和mt_rand()随机函数,生成四个颜色随机的随机数。

2、接下来就要写html代码

这里要说一下:做一个隐藏的div保存生成的验证码,后面可以用来跟用户输入的进行比对。

3、js验证

点击提交按钮,获取到隐藏div里的文本值和输入框的值,进行比较,判断值是否一致,从而判断验证成功与否。

当然这只是给新手练手,提供一个思路用的。真正做网站验证码比这个复杂多,要考虑很多安全因素。

天星期三了,如果不发生什么不可抗力事件,再过两天就放假了。

倒计时两天

不过不要高兴太早,三天后就是年度最大电商节了,钱包是不是又要瘦身了。

me too

话说小编这两天在忙着给项目做登录界面,涉及到验证码,找了一下,发现现在网上大部分验证码都是后台生成图片,在前台显示,验证也是在后台验证。小编就在想,只用前端技术能不能做出验证码呢?经过一番努力,终于做出来了,先看大家一下效果图

感觉看着和图片没差

下面给大家一步一步讲解代码:

HTML代码:

<table>

<tr>

<td style="width: 70px;height: 40px;"><span >验证码:</span></td>

/* 输入框*/

<td><input id="Text3" type="text" class="txtCode"/></td>

/* 画布,用来显示验证码 */

<td><canvas id="canvas" width="120" height="40"></canvas></td>

</tr>

</table>

<script>

document.getElementById("Text3").addEventListener("change",defined);

//给输入验证码的input添加监听事件,当输入框的值改变的时候,触发defined()函数。

var code = " ";

function defined() {

var text = document.getElementById("Text3").value.toUpperCase();

//获取输入框的值,并用toUpperCase()将其转化为大写。

function clearAndUpdate() {

//定义clearAndUpdate()函数。用于在验证码错误的情况下刷新验证码和清空输入框的值。

document.getElementById("Text3").value = '';

//清空输入框的值。

drawPic();

调用drawPic(),刷新验证码。

}

//对验证码进行验证。

if(text.length < 0){//判断为空的情况,弹出提示框。

alert("请输入验证码");

}else if(text.length !==4){//判断验证码位数不等于4的情况。

alert("请输入正确格式的验证码");

clearAndUpdate();

}else if(text == code){//比较验证码

alert("通过验证");

}else{

alert("验证码错误");//其他情况

clearAndUpdate();

}

}

下面是生成验证码的代码,是利用画布生成类似图片的验证码。

//生成一个随机数

function randomNum(min,max){

return Math.floor( Math.random()*(max-min)+min);//在max和min之间生成随机数。

}

//生成一个随机色

function randomColor(min,max){//采用rgb颜色,注意颜色是0-255。

var r = randomNum(min,max);

var g = randomNum(min,max);

var b = randomNum(min,max);

return "rgb("+r+","+g+","+b+")";

}

drawPic();

//点击验证码,则刷新验证码

document.getElementById("canvas").onclick = function(e){

e.preventDefault();

drawPic();

};

//绘制验证码图片

function drawPic(){

var canvas=document.getElementById("canvas");//获取画布容器

var width=canvas.width;//分别获取画布的宽和高。

var height=canvas.height;

var ctx = canvas.getContext('2d');//获取该canvas的2D绘图环境对象

ctx.textBaseline = 'bottom';设置文本基线是画布的底部。

//绘制背景色

ctx.fillStyle = randomColor(200,240); //颜色若太深可能导致看不清

ctx.fillRect(0,0,width,height);//画出矩形,要记得ctx.fillStyle放在ctx.fillRect哦。

//绘制文字

var str = 'ABCEFGHJKLMNPQRSTWXY123456789';//选择全部大写字母和数字,这下知道为啥要把获取的值转化为大写了吧。

code = "";//定义一个变量code用于存储生成的验证码。

for(var i=0; i<4; i++){//这里i<4是生成4位数的验证码。

var txt = str[randomNum(0,str.length)];//随机获取str的一个元素。

code += txt;//将元素加入到code里。

ctx.fillStyle = randomColor(50,160); //随机生成字体颜色

ctx.font = randomNum(15,30)+'px SimHei'; //随机生成字体大小

var x = 10+i*25;//元素在水平方向上的位置。

var y = randomNum(25,35);//元素在竖直方向上的位置,尽量保持在中间,防止部分元素在画布外。

var deg = randomNum(-45, 45);//随机生成旋转角度。

//修改坐标原点和旋转角度

ctx.translate(x,y);//平移元素

ctx.rotate(deg*Math.PI/180);//旋转元素

ctx.fillText(txt, 0,0);

//恢复坐标原点和旋转角度

ctx.rotate(-deg*Math.PI/180);

ctx.translate(-x,-y);

}

//绘制干扰线

for(var i=0; i<2; i++){

ctx.strokeStyle = randomColor(40,180);//干扰线颜色。

ctx.beginPath();//开始绘制。

ctx.moveTo( randomNum(0,width), randomNum(0,height) );//起点位置

ctx.lineTo( randomNum(0,width), randomNum(0,height) );//终点位置

ctx.stroke();

}

/**绘制干扰点**/

for(var i=0; i<50; i++){

ctx.fillStyle = randomColor(0,255);

ctx.beginPath();

ctx.arc(randomNum(0,width),randomNum(0,height), 1, 0, 2*Math.PI);绘制点,下面说arc函数。

ctx.fill();

}

}

</script>

arc() 方法创建弧/曲线

context.arc(x,y,r,sAngle,eAngle,counterclockwise);

x圆的中心的 x 坐标。

y圆的中心的 y 坐标。

r圆的半径。

sAngle起始角,以弧度计。(弧的圆形的三点钟位置是 0 度)。

eAngle结束角,以弧度计。

counterclockwise可选。规定应该逆时针还是顺时针绘图。False = 顺时针,true = 逆时针。

这就是全部的代码了。

在页面上试一下,首先是随机生成的验证码,

开始输入不是4位数的验证码,点击确定后会刷新验证码。

然后是4位数,但是不正确的验证码,

当我们输入正确的验证码时,

这就是我做的验证码功能了,都是原生的js,没有用后台的知识,小伙伴有没有理解哦。

最近因为项目快要交付了,所有人都开始动员起来测试系统,因为小编参与了整个的开发流程,所以很多同事遇到bug都来问我,需要在哪里改,东西写在哪了。第一次知道改bug这么费精力,改了这个,又出来好几个新的,简直爆炸。

打死也不承认是我写的

不说了,一会经理要问我改多少bug了。

不敢跑,不敢跑

AJ-Captcha行为验证码,包含滑动拼图、文字点选两种方式,UI支持弹出和嵌入两种方式。后端提供Java实现,前端提供了php、angular、html、vue、uni-app、flutter、android、ios等代码示例。


源码仓库以及协议

源码地址:https://gitee.com/anji-plus/captcha

基于apache2协议,开源免费商用,但需要保留版权声明

Apache 2.0 开源协议具有以下特点:

1. 授权:该协议允许任何人自由使用、复制、修改、分发和销售被许可软件的副本。

2. 版权声明:被许可软件的副本必须包含原始版权声明和许可声明。

3. 专利授权:该协议授予了对软件相关专利的非专属授权,这意味着使用该软件的人不会受到专利侵权的指控。

4. 责任限制:被许可软件是按"原样"提供的,没有任何明示或暗示的担保和条件。使用者对软件的使用有责任承担风险。

5. 分发修改版本:使用者可以基于被许可软件创建衍生作品,并将其分发。然而,衍生作品必须遵循Apache 2.0协议,并包含相应的版权声明和许可声明。

总体而言,Apache 2.0 开源协议提供了灵活的许可方式,鼓励创新和共享。它广泛应用于许多开源软件项目,包括Apache HTTP服务器等。

项目启动

  • 后端工程

提供了go、php、java(springboot、springmvc)多种版本,我们这里以springboot为例

直接运行 StartApplication.java,配置文件先不修改 ,使用默认的,我们一会儿再来看看都有些什么配置项

  • 前端

提供的版本有很多,我们这里以vue为例演示

本地启动执行命令,记得修改接口地址

npm install
npm run dev

启动成功后可以看到演示界面

功能体验

进入内部页面后,还有前端代码集成示例,可以非常方便的集成到自己的项目中去

技术细节

  • 整体时序图

整体时序图

  • 后端的配置项

captcha/service/springboot/src/resources/application.properties

spring.application.name=captcha-service
server.port=8080

# 滑动验证,底图路径,不配置将使用默认图片
# 支持全路径
# 支持项目路径,以classpath:开头,取resource目录下路径,例:classpath:images/jigsaw
aj.captcha.jigsaw=classpath:images/jigsaw
# 滑动验证,底图路径,不配置将使用默认图片
# 支持全路径
# 支持项目路径,以classpath:开头,取resource目录下路径,例:classpath:images/pic-click
aj.captcha.pic-click=classpath:images/pic-click

# 对于分布式部署的应用,我们建议应用自己实现CaptchaCacheService,比如用Redis或者memcache,
# 参考CaptchaCacheServiceRedisImpl.java
# 如果应用是单点的,也没有使用redis,那默认使用内存。
# 内存缓存只适合单节点部署的应用,否则验证码生产与验证在节点之间信息同步,导致失败。
# !!! 注意啦,如果应用有使用spring-boot-starter-data-redis,
# 请打开CaptchaCacheServiceRedisImpl.java注释。
# redis ----->  SPI: 在resources目录新建META-INF.services文件夹(两层),参考当前服务resources。
# 缓存local/redis...
aj.captcha.cache-type=local
# local缓存的阈值,达到这个值,清除缓存
#aj.captcha.cache-number=1000
# local定时清除过期缓存(单位秒),设置为0代表不执行
#aj.captcha.timing-clear=180
#spring.redis.host=10.108.11.46
#spring.redis.port=6379
#spring.redis.password=
#spring.redis.database=2
#spring.redis.timeout=6000

# 验证码类型default两种都实例化。
aj.captcha.type=default
# 汉字统一使用Unicode,保证程序通过@value读取到是中文,可通过这个在线转换
# https://tool.chinaz.com/tools/unicode.aspx 中文转Unicode
# 右下角水印文字(我的水印)
aj.captcha.water-mark=我的水印
# 右下角水印字体(不配置时,默认使用文泉驿正黑)
# 由于宋体等涉及到版权,我们jar中内置了开源字体【文泉驿正黑】
# 方式一:直接配置OS层的现有的字体名称,比如:宋体
# 方式二:自定义特定字体,请将字体放到工程resources下fonts文件夹,支持ttf\ttc\otf字体
# aj.captcha.water-font=WenQuanZhengHei.ttf
# 点选文字验证码的文字字体(文泉驿正黑)
# aj.captcha.font-type=WenQuanZhengHei.ttf
# 校验滑动拼图允许误差偏移量(默认5像素)
aj.captcha.slip-offset=5
# aes加密坐标开启或者禁用(true|false)
aj.captcha.aes-status=true
# 滑动干扰项(0/1/2)
aj.captcha.interference-options=2

#点选字体样式 默认Font.BOLD
aj.captcha.font-style=1
#点选字体字体大小
aj.captcha.font-size=25
#点选文字个数,存在问题,暂不支持修改
#aj.captcha.click-word-count=4


aj.captcha.history-data-clear-enable=false

# 接口请求次数一分钟限制是否开启 true|false
aj.captcha.req-frequency-limit-enable=false
# 验证失败5次,get接口锁定
aj.captcha.req-get-lock-limit=5
# 验证失败后,锁定时间间隔,s
aj.captcha.req-get-lock-seconds=360
# get接口一分钟内请求数限制
aj.captcha.req-get-minute-limit=30
# check接口一分钟内请求数限制
aj.captcha.req-check-minute-limit=30
# verify接口一分钟内请求数限制(暂用不上,可后台直接调用captchaService)
#aj.captcha.req-verify-minute-limit=30

可以看到后端可以配置的选项还是很多的,可以切换缓存类型,展示底图,接口限制等等

  • 接口分析

我们尝试滑动一次,发现在整个请求过程中,前端向后端发出了2个接口请求

第1个是滑动停止时,带着滑动码类型和位置信息请求check校验接口,如果滑动得是正确得位置,则里面得result字段为ture

http://localhost:8080/captcha/check

{"captchaType":"blockPuzzle","pointJson":"Ld/yNtPlENUtOoX2qdKHvNO5y/X8LU+vOUPWfitmrjc=","token":"3d5591a033d8482c89e0a77f477a9365"}

{
"repCode": "0000",
"repMsg": null,
"repData": {
"captchaId": null,
"projectCode": null,
"captchaType": "blockPuzzle",
"captchaOriginalPath": null,
"captchaFontType": null,
"captchaFontSize": null,
"secretKey": null,
"originalImageBase64": null,
"point": null,
"jigsawImageBase64": null,
"wordList": null,
"pointList": null,
"pointJson": "Ld/yNtPlENUtOoX2qdKHvNO5y/X8LU+vOUPWfitmrjc=",
"token": "3d5591a033d8482c89e0a77f477a9365",
"result": true,
"captchaVerification": null,
"clientUid": null,
"ts": null,
"browserInfo": null
},
"success": true
}

第2个其实是获取图片验证码得请求,是因为第一个我们滑动到了正确位置,所以界面刷新又获取了一次新得图片验证码

http://localhost:8080/captcha/get

实际上真正做滑动校验得是/check 这个接口

src/main/java/com/anji/captcha/service/impl/BlockPuzzleCaptchaServiceImpl.java

public ResponseModel check(CaptchaVO captchaVO) {
		ResponseModel r = super.check(captchaVO);
		if(!validatedReq(r)){
			return r;
		}
        //取坐标信息
        String codeKey = String.format(REDIS_CAPTCHA_KEY, captchaVO.getToken());
        if (!CaptchaServiceFactory.getCache(cacheType).exists(codeKey)) {
            return ResponseModel.errorMsg(RepCodeEnum.API_CAPTCHA_INVALID);
        }
        String s = CaptchaServiceFactory.getCache(cacheType).get(codeKey);
        //验证码只用一次,即刻失效
        CaptchaServiceFactory.getCache(cacheType).delete(codeKey);
        PointVO point = null;
        PointVO point1 = null;
        String pointJson = null;
        try {
            point = JsonUtil.parseObject(s, PointVO.class);
            //aes解密
            pointJson = decrypt(captchaVO.getPointJson(), point.getSecretKey());
            point1 = JsonUtil.parseObject(pointJson, PointVO.class);
        } catch (Exception e) {
            logger.error("验证码坐标解析失败", e);
            afterValidateFail(captchaVO);
            return ResponseModel.errorMsg(e.getMessage());
        }
        if (point.x - Integer.parseInt(slipOffset) > point1.x
                || point1.x > point.x + Integer.parseInt(slipOffset)
                || point.y != point1.y) {
            afterValidateFail(captchaVO);
            return ResponseModel.errorMsg(RepCodeEnum.API_CAPTCHA_COORDINATE_ERROR);
        }
        //校验成功,将信息存入缓存
        String secretKey = point.getSecretKey();
        String value = null;
        try {
            value = AESUtil.aesEncrypt(captchaVO.getToken().concat("---").concat(pointJson), secretKey);
        } catch (Exception e) {
            logger.error("AES加密失败", e);
            afterValidateFail(captchaVO);
            return ResponseModel.errorMsg(e.getMessage());
        }
        String secondKey = String.format(REDIS_SECOND_CAPTCHA_KEY, value);
        CaptchaServiceFactory.getCache(cacheType).set(secondKey, captchaVO.getToken(), EXPIRESIN_THREE);
        captchaVO.setResult(true);
        captchaVO.resetClientFlag();
        return ResponseModel.successData(captchaVO);
    }

分析一下接口的实现,经过了坐标解密(滑块图的坐标信息是通过加密后传输给前端),坐标对比,需要滑动的位置在允许的范围内才认为滑动位置正确

再来看看验证码生成的实现

src/main/java/com/anji/captcha/service/impl/BlockPuzzleCaptchaServiceImpl.java

public ResponseModel get(CaptchaVO captchaVO) {
		ResponseModel r = super.get(captchaVO);
		if(!validatedReq(r)){
			return r;
		}
        //原生图片
        BufferedImage originalImage = ImageUtils.getOriginal();
        if (null == originalImage) {
            logger.error("滑动底图未初始化成功,请检查路径");
            return ResponseModel.errorMsg(RepCodeEnum.API_CAPTCHA_BASEMAP_NULL);
        }
        //设置水印
        Graphics backgroundGraphics = originalImage.getGraphics();
        int width = originalImage.getWidth();
        int height = originalImage.getHeight();
        backgroundGraphics.setFont(waterMarkFont);
        backgroundGraphics.setColor(Color.white);
        backgroundGraphics.drawString(waterMark, width - getEnOrChLength(waterMark), height - (HAN_ZI_SIZE / 2) + 7);

        //抠图图片
        String jigsawImageBase64 = ImageUtils.getslidingBlock();
        BufferedImage jigsawImage = ImageUtils.getBase64StrToImage(jigsawImageBase64);
        if (null == jigsawImage) {
            logger.error("滑动底图未初始化成功,请检查路径");
            return ResponseModel.errorMsg(RepCodeEnum.API_CAPTCHA_BASEMAP_NULL);
        }
        CaptchaVO captcha = pictureTemplatesCut(originalImage, jigsawImage, jigsawImageBase64);
        if (captcha == null
                || StringUtils.isBlank(captcha.getJigsawImageBase64())
                || StringUtils.isBlank(captcha.getOriginalImageBase64())) {
            return ResponseModel.errorMsg(RepCodeEnum.API_CAPTCHA_ERROR);
        }
        return ResponseModel.successData(captcha);
    }

大致过程:加载验证码底图、随机从底图中扣取一个部分作为要滑动的小图,最后把两个图都以base64图片形式返回给前端

总结

1、本篇我们介绍了一个开源免费的行为验证码的项目

2、完成了项目的实际搭建和功能体验

3、我们初步分析了后端的核心代码,了解了滑动验证码的生成和校验逻辑

4、整体来说这个项目非常完整,并且提供了很多的终端实现(常见的基本上都支持了,html,vue,小程序终端,原生ios android等),并且后端也是非常容易集成到自己的项目,拿着demo版本修改一下即可