全栈小白的gravatar头像
全栈小白 2023-11-06 14:26:51
项目中登录验证码怎么做才合理

唠嗑部分

今天我们来聊聊项目实战中登录验证码如何做比较合理,首先我们聊以下几个问题

1、登录时验证码校验是否必要?

答案当然是很有必要的,因为用户登录行为会直接影响数据库,如果没有某些防范措施,有恶意用户暴力破解密码,那就十分严重了,验证码校验是防止接口被刷的一种方式,可以大大的降低接口请求的频率

还有一些系统会加入密码次数校验,当密码错误次数超过n次时,会锁定该用户,在指定时间段内不允许登录

除此之外,对于用户接口限流也是一种方案,小伙伴可以自行尝试

2、验证码校验是前端行为还是服务端行为?

两种方式均可以,区别如下

前端行为:仅能保证前端页面操作时验证的效果,不能够保证接口安全,用户可跳过页面直接访问服务端接口,不建议

服务端行为:能够保证登陆接口安全,限制用户登录频率,可配合用户ip流量限制、密码错误次数防范等措施,建议

3、生成验证码后,服务器应该存在什么地方,会有哪些问题?

在学习jsp+servlet时,用户信息通常会存入到服务端的session当中,验证码也可存入session,但是之针对单实例可行,如多实例,需实现session共享或配合ip_hash的规则

可存入分布式缓存中,不会有多实例问题、也不需要解决session共享的问题

4、如何实现分布式验证码校验,保证登录安全?

首先,服务端提供获取验证码的接口,出参包含key、code(code为实际验证码,key为Redis存储的key),返回验证码之前将验证码存入redis中

前端收到验证码后,采用canvas绘制验证码,用户手动输入验证码后,将用户名、密码、验证码、key传入服务端进行登录

服务端收到登录请求后,查询redis,跟用户输入的验证码进行比较,比较通过后进行登录

言归正传

验证码校验是用户登录的第一道屏障,未验证通过则不进行登录,不接触数据库

1、导入验证码依赖,其他依赖自行添加,需redis、hutool-all

<dependency>
    <groupId>com.github.penggle</groupId>
    <artifactId>kaptcha</artifactId>
    <version>2.3.2</version>
</dependency>

2、配置kaptcha

/*
 * @Project:cxs-currency-sys-server
 * @Author:cxs
 * @Motto:放下杂念,只为迎接明天更好的自己
 * */
@Configuration
public class KaptchaConfig {
    
    private static final String CHAR_STR = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
    private static final String CHAR_LENGTH = "4";
​
    @Bean
    public Producer captcha() {
        // 配置图形验证码的基本参数
        Properties properties = new Properties();
        // 字符集
        properties.setProperty("kaptcha.textproducer.char.string", CHAR_STR);
        // 字符长度
        properties.setProperty("kaptcha.textproducer.char.length", CHAR_LENGTH);
        Config config = new Config(properties);
        DefaultKaptcha defaultKaptcha = new DefaultKaptcha();
        defaultKaptcha.setConfig(config);
        return defaultKaptcha;
    }
}

3、生成验证码核心代码

public void getValidateCode(BaseResult result) {
        long startTime = System.currentTimeMillis();
        try {
            // 创建验证码文本
            ValidateCodeVO vo = new ValidateCodeVO();
            String key = IdUtil.simpleUUID();
            String code = captchaProducer.createText();
            redisUtil.set(redisUtil.getCacheKey(CachePrefixContent.VALIDATE_CODE_PREFIX, key), code, commonConfig.getCodePeriod(), TimeUnit.SECONDS);
            vo.setKey(key);
            vo.setCode(code);
            result.setData(vo);
        } catch (Exception e) {
            log.error("生成验证码失败,{}", e);
            result.setCode(CurrencyErrorEnum.OPERA_ERROR.getCode());
            result.setMsg(CurrencyErrorEnum.OPERA_ERROR.getMsg());
        } finally {
            long endTime = System.currentTimeMillis();
            log.info("【{}】【生成验证码接口】【{}ms】 \n入参:{}\n出参:{}", "生成验证码", endTime - startTime, null, result);
        }
    }

4、接口出参预览

{
  "code": 200,
  "data": {
    "code": "2972",
    "key": "a23030e821384684a47912fac215cfb1"
  },
  "msg": "操作成功"
}

5、前端实现主要代码(注意:登录框的部分代码)

<el-form-item prop="code">
    <el-row :gutter="20">
        <el-col :span="12">
            <el-input ref="code" type="text" prefix-icon="el-icon-key" v-model="userInfo.code" placeholder="验证码" name="code" tabindex="2" autocomplete="on" @keyup.enter.native="loginHandle"/>
         </el-col>
        <el-col :span="6" :offset="3">
            <canvas ref="canvas" :width="codeInfo.codeWidth" :height="codeInfo.codeHeight" @click="reloadCode"></canvas>
        </el-col>
    </el-row>
</el-form-item>

6、需要到的函数

// 创建验证码
async createdCode() {
    // 调用上述接口生成
      const res = await getValidateCode()
      const {code, data, msg} = res
      this.userInfo.codeKey = data.key
      const identifyCode = data.code
      const codeList = identifyCode.split("");
      const canvas = this.$refs.canvas;// 获取 canvas 元素
      const ctx = canvas.getContext('2d');
      ctx.textBaseline = 'bottom';
      if (this.backgroundColor != '' && this.backgroundColor != null) {// 绘制画布背景颜色
        ctx.fillStyle = this.backgroundColor;
      } else {
        ctx.fillStyle = this.randomColor(255, 255);
      }
      ctx.fillRect(0, 0, this.contentWidth, this.contentHeight);
      codeList.forEach((code, i) => {// 绘制验证码字符
        this.drawText(ctx, code, i + 1, identifyCode.length);
      })
      this.drawLine(ctx);// 绘制干扰线
      this.drawDot(ctx);// 绘制干扰点
    },
    randomNum(min, max) {// 生成指定范围内的随机整数
      return Math.floor(Math.random() * (max - min) + min);
    },
    randomColor(min, max) {// 生成指定范围内的随机颜色
      const r = this.randomNum(min, max);
      const g = this.randomNum(min, max);
      const b = this.randomNum(min, max);
      return `rgb(${r},${g},${b})`;
    },
    drawText(ctx, txt, i, len) {// 绘制验证码字符
      ctx.fillStyle = this.randomColor(0, 160);
      ctx.font = `${this.randomNum(25, 30)}px SimHei`;
      const x = (i / (len + 1)) * 120;
      const y = this.randomNum(30, 35);
      const deg = this.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);
    },
    drawLine(ctx) {// 绘制干扰线
      for (let i = 0; i < 5; i++) {
        ctx.strokeStyle = this.randomColor(100, 255);
        ctx.beginPath();
        ctx.moveTo(this.randomNum(0, 120), this.randomNum(0, 40));
        ctx.lineTo(this.randomNum(0, 120), this.randomNum(0, 40));
        ctx.stroke();
      }
    },
    drawDot(ctx) {// 绘制干扰点
      for (let i = 0; i < 80; i++) {
        ctx.fillStyle = this.randomColor(0, 255);
        ctx.beginPath();
        ctx.arc(this.randomNum(0, 120), this.randomNum(0, 40), 1, 0, 2 * Math.PI);
        ctx.fill();
      }
    },
    reloadCode() {// 点击按钮时清除画布并重新生成验证码
      const canvas = this.$refs.canvas
      const ctx = canvas.getContext('2d')
      ctx.clearRect(0, 0, canvas.width, canvas.height)
      this.createdCode()
    },

7、效果展示

项目中登录验证码怎么做才合理

结语

1、验证码功能就实现了,快去给你的项目安排吧!

2、制作不易,一键四连再走吧,您的支持永远是我最大的动力!


打赏

已有1人打赏

最代码官方的gravatar头像
最近浏览
dyb1220 3月22日
暂无贡献等级
wumingming  LV1 3月19日
SZ2020920 3月14日
暂无贡献等级
meitan16 3月8日
暂无贡献等级
做你的景天  LV7 2月22日
周青松197  LV2 2月21日
youwuzuichen  LV10 2月18日
张张张飞  LV1 1月31日
liiiyou  LV1 1月26日
小学生波波  LV19 1月19日
顶部 客服 微信二维码 底部
>扫描二维码关注最代码为好友扫描二维码关注最代码为好友