当前位置: 首页 > news >正文

Node中实现一个简易的图片验证码流程

前言

最近在实现一个登录中常见的功能:图片验证码。这个功能非常有意思,但是你说难又不会太难,简单又不会太简单。不会太难之处在于你最方便可以客户端本地存储验证码图片(不排除被打的可能),不会太简单的是要做到非常完整的流程还是要点精力,比如过期时间,加密验证,签发私钥…。本文从一个不是非常主后端的角度去实现一个标准图片验证码的流程。

已用技术:

  • koa
  • koa-session(express 的原理一样)
  • svg-captcha
  • redis

原理

我们都知道正常的登录验证码都是存在一定过期时间的,那这个过期怎么算是过期?那我们可以通过 cookie,session,redis 中去判断过期与否。因为这三者在 web 和 app 都可以设定过期时间限制登录凭证。考虑用 session 和 redis 来实现这个功能,因为 cooile 太容易被篡改了。

基本思路如下:

1.用户进入登录页面后请求接口从后端获取图片验证码标签
2.后端生成验证码的同时,设置一个 session,同时拿到响应的 session 值作为 key 值,图片验证码的正确字符串作为 value,映射到 redis 中存储。
3.用户确定登录后,携带客户端的 cooile 和验证码一起发送给后台
4.后台拿到请求头中 cooile 中的 session 值和验证码,用 session 值作为 key 获取 redis 中的对应的 value 值,进行验证比对正确。

实现

后端服务器下载koa-session,redis,svg-captcha插件依赖。

后端: 在app.js中引入koa-session

const Koa = require("koa");
const session = require("koa-session");

const session_signed_key = ["appletSystem"];
const app = new Koa();
const CONFIG = {name: "sessionId",maxAge: 180000, // session 过期时间,以毫秒ms为单位计算 overwrite: true,//是否允许重写 。(默认是 true) httpOnly: true,//是否设置HttpOnly,如果在Cookie中设置了"HttpOnly"属性,那么通过程序(JS脚本、Applet等)将无法读取到Cookie信息,这样能有效的防止XSS攻击。signed: true,//是否签名。(默认是 true) rolling: false,//是否每次响应时刷新Session的有效期renew: false,//是否在Session快过期时刷新Session的有效期
};
app.keys = session_signed_key;
app.use(session(CONFIG, app));

app.listen(8080, () => {console.log(`服务器8080启动成功`);
}); 

在这里,session_signed_key为开启sign签发的公钥,CONFIG为session的配置文件,signed为了防止客户端的cookie被篡改,必须为true;maxAge为过期时间,最后设置app.keys,use(session(CONFIG, app))就ok了。

使用redis:

确保本地安装了redis并且启动服务

const { createClient } = require("redis");
const client = createClient({host: "localhost", //	redis地址port: 6379, // 端口号
});
client.on("connect", (error) => {if (!error) {console.log("connect to redis");}
});
function setString(key, value, expire = 180) { //存储key和value键值对return new Promise((resolve, reject) => {client.set(key, value, (error, replay) => {if (error) {reject("设置失败");}if (expire) {//默认expire过期时间为180sclient.expire(key, expire); //给当前的key设置过期时间}resolve("设置成功");});});
}
function getString (key) => {return new Promise(async (resolve, reject) => {client.get(key, function (err, result) {resolve(result);//直接获取value值,如果没有key,直接返回null});});
}; 

引入redis并且创建client代理,通过封装两个方法get和set去获取redis的key-value。方法可直接复制使用。

在验证码接口:

const svgCaptcha = require("svg-captcha");

class svg {async getsvg(ctx, next) {const { time } = ctx.request.query;const c = svgCaptcha.create({size: 4, // 验证码长度ignoreChars: "0o1iLl", // 验证码字符中排除 0o1icolor: true, // 验证码是否有彩色noise: 0, //干扰线background: "#666", // 背景颜色height: 50,fontSize: 60,width: 100,});ctx.session.sessionId= { time: "svgIsMjc" + time};await ctx.session.manuallyCommit();
    const cookie = ctx.response.headers["set-cookie"];
    if (cookie) {
      let cookies = cookie[0]; //获取第一个值
      const newSessionKey = getCookie(cookies); //传字符串
      try {
        const result = await setString(newSessionKey, c.text.toLowerCase());
      } catch (error) {
        throw new Error(error);
      }
    }ctx.body = c.data;}
} 

在这里遇到问题比较多,既然返回图片的时候设置一个过期的session,那么该怎么设置?起初我设置了ctx.session.sessionId= { time: “svgIsMjc” + time +ip },time为当前时间毫秒和获取的ip地址来确保session的值各不相同。

如图,确实能在图片返回的同时客户端存储了cookie值,并且为session。

并且每次请求客户端的sessionId会不同

如图

在验证图片码过期中间件接口:

const verifySvgCode = async (ctx, next) => {const cookie = ctx.request.headers.cookie;//获取cookieconst { vaildcode } = ctx.request.body;//获取客户端的验证码if (!ctx.session.sessionId) {//验证是否过期或者被篡改const error = new Error(erroType.SVG_COOKIE_IS_TIMEOUT);return ctx.app.emit("error", error, ctx);}if (cookie) {//存在就是cookie没有过期,验证cookie的redis存储的值const newSessionKey = getCookie(cookie);//转化session字符串截取sessionid值const oldvaildcode = await getString(newSessionKey);//获取value值if (oldvaildcode && vaildcode.toLowerCase() == oldvaildcode) {//校验两者验证码的相等与否,否则抛出错误await next();return;} else {const error = new Error(erroType.SVG_COOKIE_IS_FALSE);return ctx.app.emit("error", error, ctx);}} else {const error = new Error(erroType.SVG_COOKIE_IS_TIMEOUT);return ctx.app.emit("error", error, ctx);}
}; 

通过ctx.headers获取cookie,如果过期当前肯定为空值。在这里,我一开始是犯错误。ctx.session.sessionId是一个undefined。因为在CONFIG中我少配置了一个key属性,所以拿不到,搞得排查了大半天!。

const CONFIG = {...key: "sessionId", //cookie的key。 (默认是 koa:sess) */,必须加key值,不然无法获取session!
}; 

而且,还有一个npm仓库中redis工具的问题。目前redis最高为4.0版本,第二个版本就是3.1.2。在node里面使用最高4.0版本是连接不上去redis的,所以我采用3.1.2版本。

而3.1.2版本的redis工具get方法我是出现问题。 直接可以set但是get的时候总是null 我在redis-cli下却可以get到值。排查了半天,我发现是get的时候key值长度问题。

封装的截取sessionId的方法:

const getCookie = (cookie) => {//接收字符串const sessionId = cookie;const start = sessionId.indexOf("=") + 1;const end = sessionId.indexOf(";") - 2;const newSessionKey = sessionId.substring(start, 110);console.log(newSessionKey);return JSON.stringify(newSessionKey);
}; 

我多次调试完,发现client.get的时候传入的key值也就是sessionId的值如果超过110长度,那么redis预期这个key值不存在就给我返回null了。

所以我妥协了,截取了110长度作为key存储,再用get的时候能得到存储的value值。 如图成功拿到了。问题都在百度搜查过,存在但是没有解决办法。

总结

1.redis的创建以及使用
2.koa-session的使用方法以及注意事项
3.npm中的redis工具的get问题
4.图片验证码的流程以及校验

最后

整理了75个JS高频面试题,并给出了答案和解析,基本上可以保证你能应付面试官关于JS的提问。



有需要的小伙伴,可以点击下方卡片领取,无偿分享

相关文章:

  • java-Lambda表达式
  • Robotics System Toolbox中的机器人运动(7)--RRT规划避障路径
  • 和一个海归的博士聊人生
  • 移动端布局介绍——css像素/物理像素/设备像素比
  • redis简介及八种数据类型
  • GAN Step By Step -- Step1 GAN介绍
  • vue纯前端结合css动画实现模拟导航效果
  • 【数据增强】90°、180°和270°翻转图片(*4)
  • 【Hadoop---07】HDFS 读 / 写 数据流程(面试重点)
  • 【笔记】C#得到真正的屏幕大小
  • SSH远程端口转发
  • 微信支付配置信息如何获取
  • nginx反向代理实例
  • webpack与vite对比
  • Linux中的权限机制
  • 0基础学习移动端适配
  • canvas 绘制双线技巧
  • ERLANG 网工修炼笔记 ---- UDP
  • HTTP那些事
  • iOS | NSProxy
  • JavaScript HTML DOM
  • js
  • js面向对象
  • Laravel 中的一个后期静态绑定
  • Spring框架之我见(三)——IOC、AOP
  • vue的全局变量和全局拦截请求器
  • 从0实现一个tiny react(三)生命周期
  • 从重复到重用
  • 观察者模式实现非直接耦合
  • 记录:CentOS7.2配置LNMP环境记录
  • 记一次删除Git记录中的大文件的过程
  • 经典排序算法及其 Java 实现
  • 前端路由实现-history
  • 设计模式 开闭原则
  • 深度解析利用ES6进行Promise封装总结
  • 使用iElevator.js模拟segmentfault的文章标题导航
  • 我是如何设计 Upload 上传组件的
  • 以太坊客户端Geth命令参数详解
  • 阿里云IoT边缘计算助力企业零改造实现远程运维 ...
  • 长三角G60科创走廊智能驾驶产业联盟揭牌成立,近80家企业助力智能驾驶行业发展 ...
  • 好程序员大数据教程Hadoop全分布安装(非HA)
  • #《AI中文版》V3 第 1 章 概述
  • $Django python中使用redis, django中使用(封装了),redis开启事务(管道)
  • (2021|NIPS,扩散,无条件分数估计,条件分数估计)无分类器引导扩散
  • (7)STL算法之交换赋值
  • (c语言版)滑动窗口 给定一个字符串,只包含字母和数字,按要求找出字符串中的最长(连续)子串的长度
  • (delphi11最新学习资料) Object Pascal 学习笔记---第8章第2节(共同的基类)
  • (DFS + 剪枝)【洛谷P1731】 [NOI1999] 生日蛋糕
  • (SpringBoot)第七章:SpringBoot日志文件
  • (二)linux使用docker容器运行mysql
  • (二)Pytorch快速搭建神经网络模型实现气温预测回归(代码+详细注解)
  • (附表设计)不是我吹!超级全面的权限系统设计方案面世了
  • (算法设计与分析)第一章算法概述-习题
  • (已更新)关于Visual Studio 2019安装时VS installer无法下载文件,进度条为0,显示网络有问题的解决办法
  • (转载)Linux 多线程条件变量同步