当前位置: 首页 > 后端技术 > PHP

API交互中如何做好图片验证码?

时间:2023-03-29 14:50:45 PHP

前言在传统的web开发过程中,图形验证码的处理是非常简单的。只需要在后台生成一张带有随机字符串的图片,将验证码的内容放到Session中即可。把判断拿出来就行了。但是现在越来越推崇API交互和无状态。在session中,虽然不支持默认配置,但是还是有很多曲线救国的方法。在基于Session实现的API开发中,我们也可以向前端下发SessionID,通过PHP内置的方法来实现这一切。比如我们同意上一段,当请求中包含X-Session-Id且不为空时,说明本次session注册了SessionID,否则下发一个SessionID,X-Session-Id在将返回响应标头。上一节记录了SessionID,下面简单实现一下。//code_session.phpsession_start();//这里假设已经通过Header获取到SessionID,保存在$sessionId变量中。//当SessionID不存在或为空时,创建一个新的SessionID。如果(!isset($sessionId)||empty($sessionId)){$sessionId=session_create_id();//因为前端还没有SessionID,发一个通知前端保存。header('X-Session-Id:'.$sessionId);}//设置当前会话的SessionID。session_id($sessionId);//这里我们可以自由读写Session。//生成验证码$code=mt_rand(1e3,1e4-1);//create_image请自行实现或使用已有的图形验证码库生成。$image=create_image($code);//存入Session$_SESSION['code']=$code;//输出一张图片$image->output();以上基本实现了图片的生成,前端只需要根据需要再次提交表单时,在headers中包含X-Session-ID即可。//code_session_validate.phpsession_start();//这里假设已经通过Header获取到SessionID,保存在$sessionId变量中。//当SessionID不存在或为空时,创建一个新的SessionID。if(!isset($sessionId)||empty($sessionId)||!isset($_POST['code'])||empty($_POST['code'])){//因为没有提交SessionID,到这里肯定是站不住脚的,直接终止就可以了。exit;}//设置当前会话的SessionID。session_id($sessionId);if($_POST['code']!=$_SESSION['code']){//验证码错误exit;}//验证通过删除code,unset($_SESSION['代码']);使用上面的Session,我们基本实现了一个简单的验证,而且是基于API交互,不依赖浏览器cookie。当我们需要共享Session等一些复杂的东西时,这些就超出了本文的范围(其实现在已经超出范围了)。以下基于客户端主动签名的方法是无状态的,但是需要使用Redis。这是使用PHPRedis扩展完成的。在大多数情况下,我们不需要像上面那样使用Session创建太多的Session,这样会造成一些资源浪费。当然,Session能做的远不止这些。接下来,我们将使用Redis让客户端主动签署Image验证码。理论原理:客户端在本地生成一个随机字符串,然后拼接在验证码地址后面。后端截取客户端生成的随机字符串,作为验证凭证放入Redis。客户端提交的时候,需要把之前生成的随机字符串带上Validate。//code_client.php$salt='wertyujkdbaskndasda';if(!isset($_GET['sign'])){//客户端不提供签名,停止执行exit;}//用户发送的所有数据是Unreliable,需要加盐执行md5$sign=md5($_GET['sign'].$salt);//拼接签名为Rediskey$key='code:'.$sign;//连接Redis$cache=new\Redis();//生成验证码$code=mt_rand(1e3,1e4-1);//将验证码保存到Redis,并设置有效期为2分钟。if($cache->exists($key)){//这个Key已经被占用了,先停在这里。exit;}$cache->set($key,$code,60*2);//创建图片并返回$image=create_image($code);$image->output();好的,我们来验证一下。//code_client_validate.php$salt='wertyujkdbaskndasda';if(!isset($_POST['sign'])||!isset($_POST['code'])//没有提交验证码。||!empty($_POST['code'])){//客户端没有提供签名,停止执行exit;}//用户发送的所有数据都是不可靠的,我们需要对其加盐并执行md5$sign=md5($_POST['sign'].$salt);//将签名拼接为Rediskey$key='code:'.$sign;//连接Redis$cache=new\Redis();if(!$cache->exists($key)){//根本没有这个keyeixt;}if($cache->get($key)!=$_POST['code']){//验证码error}//校验通过则删除$cache->del($key);看是不是再复杂点,甚至用Redis,虽然不好看,但是也实现了我们想要的,不过这也不是很好的解决方案。此外,我们还需要考虑客户端字符串不够随机的事实。接下来我们换一个方向,切换到服务端发行。基于服务器端签名的实现是基于客户端签名的。这是另一种思路,但总的来说,这几乎是一样的。理论上的原理也是发出Sign,不过这次是由服务端发出,然后Sign通过Header发送给客户端。客户端需要先获取图片资源。注意这里返回的应该是合法的二进制流,然后从header中取出Sign,同时展示给用户。//code_server.php$cache=new\Redis();$salt='wertyujkdbaskndasda';functiongenerateSign(){global$cache,$salt;$sign=md5(mt_rand().$salt);//拼接签名作为Redis的key$key='code:'.$sign;if($cache->exists($key)){//是的,你没看错,就是如果生成的Sign已经存在,就会递归,直到生成一个不存在的。返回generateSign();}return$key;}//连接Redis$key=generateSign();//生成验证码$code=mt_rand(1e3,1e4-1);//保存验证码到Redis并设置2分钟有效期。$cache->set($key,$code,60*2);//创建图片并返回$image=create_image($code);//哈哈,需要把前缀header剃掉('X-Captcha-Sign:'.str_replace('code:','',$key));$image->output();看起来几乎没有什么变化,只是生成Sign的方式变了,但是如果你这样做,前端同学可能会不高兴是的,他们需要获取这个资源和Sign中的X-Captcha-Sign首先显示标题,然后在界面上显示它。当然也可以将结果直接base64或者直接用二进制流生成位图显示。我们只需要能够验证即可,验证方式可以直接使用上面的。使用ajax获取这个资源的时候要特别注意,如果你的业务涉及到跨域,还需要设置Access-Control-Expose-Headers-HTTP|响应头中的MDN,否则ajax获取不到自定义响应头。.header('Access-Control-Expose-Headers:X-Captcha-Sign');综上所述,这三种方案基本可以满足我们的需求,可能有人想到了另一种方案。提供一个json接口名,后台生成图片并保存,返回url和sign给前端。考虑这个选项。文中提到的一些知识是一些基础知识的应用。文章中的代码直接在文章中敲出来。如有印刷错误或逻辑错误,请不吝赐教。本文使用的Redis是PHPRedis的扩展。至于验证码图片的生成,可以使用gregwar/captcha-Packagist来完成。以上只是我个人的一些理解。如果大家有更好的解决方案,不妨一起分享。参考PHP:Sessions-Manual[注1]本文所说的Session是一种技术标准,与我们常说的通过浏览器自动传递cookie的交互中的Session有一定的概念。在这里,它只是手动实现的。SessionID的传递,却始终保持Session的直译语义“会话”。