前后端分离项目中实现业务中常用图形验证码验证功能(详细)

这篇具有很好参考价值的文章主要介绍了前后端分离项目中实现业务中常用图形验证码验证功能(详细)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

图形验证码的必要性

图形验证码是验证码的一种,有防止黑客对某一特定注册用户用程序暴力破解私人信息、恶意破解密码、刷票、论坛灌水的作用。

图形验证码是一种区分用户是计算机还是人的公共全自动程序。验证码是现在很多网站通行的方式,由计算机生成并评判,但是只有人类才能解答。

在常用的网站业务中我们不难看出很多登录注册业务上都采用了图形验证码的方式。今天记录一个图形验证码以后端的方式实现的逻辑。

实现效果

在登录注册业务上集成图片中验证码后的是效果如下图所示:

前后端分离验证码实现,SpringBoot,Java,javascript,java,spring boot,Powered by 金山文档

工具说明

这里主要推荐Hutool工具中的captcha包中的图形验证码来实现。

验证码功能位于cn.hutool.captcha包中,核心接口为ICaptcha,此接口定义了以下方法:

  • createCode 创建验证码,实现类需同时生成随机验证码字符串和验证码图片

  • getCode 获取验证码的文字内容

  • verify 验证验证码是否正确,建议忽略大小写

  • write 将验证码写出到目标流中

其中write方法只有一个OutputStreamICaptcha实现类可以根据这个方法封装写出到文件等方法。

AbstractCaptcha为一个ICaptcha抽象实现类,此类实现了验证码文本生成、非大小写敏感的验证、写出到流和文件等方法,通过继承此抽象类只需实现createImage方法定义图形生成规则即可。

生成方式可参考Hutool官网给定的方式:

LineCaptcha 线段干扰的验证码

生成效果大致如下:

前后端分离验证码实现,SpringBoot,Java,javascript,java,spring boot,Powered by 金山文档
//定义图形验证码的长和宽LineCaptcha lineCaptcha =CaptchaUtil.createLineCaptcha(200,100);//图形验证码写出,可以写出到文件,也可以写出到流
lineCaptcha.write("d:/line.png");//输出codeConsole.log(lineCaptcha.getCode());//验证图形验证码的有效性,返回boolean值
lineCaptcha.verify("1234");//重新生成验证码
lineCaptcha.createCode();
lineCaptcha.write("d:/line.png");//新的验证码Console.log(lineCaptcha.getCode());//验证图形验证码的有效性,返回boolean值
lineCaptcha.verify("1234");

可以看出,这种方式特别简便,大部分实现逻辑已经被封装在工具类内部,并不需要我们了解具体的实现。并且可以验证图形中的验证码信息,非常方便该功能在登陆注册等业务上的验证码校验。

但是需要注意的是,这种方式一般用于输出一个png图片,如果后端处理生成图片后怎么在前端界面上展示也是我们需要考虑的问题。

思路说明

不过Hutool也给出了另外的输入输出流的方式,因此我们可以在后端利用前端的请求,调用图形验证码生成逻辑相应的图形输出流,然后在前端通过指定的流解析,将图形验证码展示在前端界面上。另外在处理逻辑上,在生成图片输出流的同时去获取图心中的验证码并将验证码给信息存入Redis缓存中,当用户在登陆或注册模块中输入相应信息时,将前端界面的数据代入后端一起校验,如果某个值有错,将对应的错误信息反馈给用户。

实现方法

1,导入依赖

首先需要导入Hutool的工具包,至于项目构建的SpringBoot依赖并不在这里一一列举。这里主要是需要使用captcha的依赖,怕麻烦的也可以将Hutool所有的项目依赖hutool-all直接导入:

<!--        hutool工具依赖-->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.7.19</version>
        </dependency>

依赖加载完毕后,就可以使用CaptchaUtil这个工具类了。

2,构建业务接口

然后就通过工具类构建图形验证码构建接口

 /**
     * 获取验证码
     *
     * @return
     */
    @GetMapping("getCode")
    public Result<Map<String, String>> getCode() {
        LineCaptcha lineCaptcha = CaptchaUtil.createLineCaptcha(200, 100);
        String verify = IdUtil.simpleUUID();
        //图形验证码写出,可以写出到文件,也可以写出到流
        FastByteArrayOutputStream os = new FastByteArrayOutputStream();
        lineCaptcha.write(os);
        String code = lineCaptcha.getCode();
//        缓存一分钟的验证码
        redisTemplate.opsForValue().set(verify, code, Duration.ofMinutes(1));
        ConcurrentHashMap<String, String> map = new ConcurrentHashMap<>(5);
        //验证码对应的redis上的uuid
        map.put("uuid", verify);
        //图片上的验证码
        map.put("code", code);
        //将图片转换成输出流传到前端上
        map.put("img",Base64.encode(os.toByteArray()));
        return Result.ok(map);
    }

这里我们将图形验证码写入到输出流,并通过Base64进行编码转换,当前端获取后端请求后的数据后,将“img”对应的输出流以"data:image/gif;base64,"的方式解析出来,这样图片就能成功显示在界面上了。

为了方便校验,这里也为每个请求生成的图形验证码指定一个与之对应的随机id,并为其设置一定的缓存时间,以便在业务中校验。这样在前端界面我们每次点击以及验证码图片就相当于向后端请求一次。(所以这只是一种实现方式,但是并不太可取,毕竟大量请求会消耗服务器)。

3,后端登录功能上集成图形验证码

@ApiOperation(value = "管理员登录接口")
    @PostMapping("/login")
    @PassToken
    public Result<? extends Object> login(@RequestBody @Validated LoginDto loginDto, BindingResult result) {
        if (result.hasErrors()) {
            return Result.fail(null).message("信息输入不正确");
        }
        //获取uuid
        String uuid = loginDto.getUuid();
        //获取验证码
        String code = loginDto.getCode();
//        //验证验证码是否正确
        if (!Objects.equals(redisTemplate.opsForValue().get(uuid), code)) {
            log.info("判断结果{}",!Objects.equals(redisTemplate.opsForValue().get(uuid), code));
            //错误则返回前端并提示
            return Result.fail(null).message("验证码错误,请重新验证");
        }
            //验证成功则清除redis内的验证码缓存
            redisTemplate.delete(uuid);
            Admin admin = adminService.login(loginDto.getUsername(), loginDto.getPassword());
            if (!StringUtils.isEmpty(admin)) {
                return Result.ok(admin).message("欢迎管理员~!");
            }

            return Result.fail(null).message("用户名或密码错误,请重新登录!");
    }

当用户登录时,需要在前端输入指定的登录信息,请求登录业务接口后,此时后端首先获取图形验证码的随机id和用户填写的验证码信息,并通过uuid去redis中找到存储缓存的后端生成的验证码信息,并与之对比。如果验证码信息不对,返回给前端提示信息-》"验证码错误,请重新验证",只有通过后即可验证用户的账号密码的准确性并反馈对应的提示信息。

4,部分前端参考

这里也给出前端登录页面的具体信息可供大家参考。

<template>
  <div class="login-wrap">
    <div class="ms-login">
      <div class="title">FP&Net后台管理系统</div>
      <el-form
          ref="ruleForm"
          class="demo-ruleForm"
          :model="loginForm"
          :rules="rules"
      >
        <!--        账号-->
        <div style="display: flex">
          <el-icon class="icon-mine" style="margin-top: 10px" size="large">
            <user-filled/>
          </el-icon>
          <el-form-item prop="username">

            <el-input
                size="large"
                class="input-wid"
                v-model="loginForm.username"
                placeholder="请输入用户名"
            ></el-input>
          </el-form-item>
        </div>
        <!--        密码-->
        <div style="display: flex">
          <el-icon class="icon-mine" style="margin-top: 10px">
            <Lock/>
          </el-icon>
          <el-form-item prop="password">
            <el-input
                size="large"
                class="input-wid"
                type="password"
                placeholder="请输入密码"
                v-model="loginForm.password"
                @keyup.enter="submitForm('loginForm')"
            ></el-input>
          </el-form-item>
        </div>
        <!--        验证码-->
        <div style="display: flex">
          <el-icon class="icon-mine" style="margin-top: 10px">
            <Stamp/>
          </el-icon>
          <el-form-item prop="code" style="width: 160px">
            <el-input
                size="large"
                class="input-wid"
                v-model="loginForm.code"
                placeholder="点击图片刷新"
            ></el-input>
          </el-form-item>
          <div class="w-18px"/>
          <div class="login-code">
            <img :src="codeUrl" @click="getCode" class="login-code-img"/>
          </div>

        </div>
        <div style="font-size: 18px;margin: 0 10px">
          <el-checkbox size="large" v-model="remMe">记住我</el-checkbox>
          <el-link type="warning" style="font-size: 14px; line-height: 20px;margin-left: 150px" @click="forget()">
            忘记密码?
          </el-link>
        </div>
        <div class="login-btn">
          <el-button type="primary" size="large" style="margin: 0 10px" @click="submitForm" @keyup="submitForm">登录</el-button>
          <el-button type="warning" size="large" style="margin-left: 100px" @click="handleSignUp()">注册</el-button>
        </div>
        <p style="font-size: 12px; line-height: 30px; color: #999">
          Tips : 请保护好个人信息哦~
        </p>
      </el-form>
    </div>
  </div>
</template>

<script>
import Cookies from "js-cookie";
import request from "@/util/postreq";
import {mixin} from "../../mixins/index";
import {UserFilled, Lock, Stamp} from '@element-plus/icons-vue'


export default {
  name: "LoginPage",
  mixins: [mixin],
  components: {
    UserFilled,
    Lock,
    Stamp,
  },
  data() {
    //用户名校验
    const validateName = (rule, value, callback) => {
      if (!value) {
        return callback(new Error("用户名不能为空"));
      } else {
        callback();
      }
    };
    //验证码校验
    const codeRule = (rule, value, callback) => {
          if (value === "") {
            callback(new Error("请输入验证码"));
          } else {
            callback();
          }
    }
    //密码校验
    const validatePassword = (rule, value, callback) => {
      if (value === "") {
        callback(new Error("请输入密码"));
      } else {
        callback();
      }

    };
    return {
      wrap: {
        flexWrap: "nowrap",
      },
      codeUrl: "",
      remMe: false,
      loginForm: {
        // 登录用户名密码
        username: "",
        password: "",
        code: "",
        uuid: "",
        avatar: "",
        cid:"",
        roleId:""
      },

      rules: {
        username: [
          {validator: validateName, message: "用户名不能为空", trigger: "blur"},
        ],
        code: [
          {
            validator: codeRule,
            message: "验证码不能为空",
            trigger: "blur",
          }
        ],
        password: [
          {
            validator: validatePassword,
            message: "密码不能为空",
            trigger: "blur",
          },
        ],
      },
    };
  },
  created() {
    this.getCode()
    this.rememberMes()
  },
  methods: {
    getCode() {
      request.get('/getCode').then(res => {
        if (res.code === 200) {
          this.codeUrl = "data:image/gif;base64," + res.data.img;
          this.loginForm.uuid = res.data.uuid;
          this.code = res.data.code
        }
      });
    },
    rememberMes() {
      const username = Cookies.get("username");
      const password = Cookies.get("password");
      const rememberMe = Cookies.get('rememberMe');
      const cid = Cookies.get("cid");
      const roleId = Cookies.get("roleId")
      this.loginForm = {
        username: username === undefined ? this.loginForm.username : username,
        password: password === undefined ? this.loginForm.password : password,
        cid: cid === undefined ? this.loginForm.cid : cid,
        roleId: roleId === undefined ? this.loginForm.roleId : roleId,
      };
      this.remMe = {
        remMe: rememberMe === undefined ? false : Boolean(rememberMe)
      }
    },
    submitForm() {
      request.post('/login', this.loginForm)
          .then((res) => {
            console.log(res.data);
            if (res.code === 200) {
              this.$message({
                message: res.message,
                type: res.type,
              });
              this.setUserInfo(res.data)
              //cookie存储个人信息
              
              if (this.loginForm.remMe) {
                Cookies.set("username", this.loginForm.username, {expires: 30});
                Cookies.set("password", this.loginForm.password, {expires: 30});
                Cookies.set('rememberMe', this.loginForm.remMe, {expires: 30});
                Cookies.set('roleId', this.loginForm.roleId, {expires: 30});
                Cookies.set('cid', this.loginForm.cid, {expires: 30});
              } else {
                Cookies.remove("username");
                Cookies.remove("password");
                Cookies.remove('rememberMe');
                Cookies.remove('cid');
                Cookies.remove('roleId');
              }
              //将个人信息存入vuex
              this.setUserInfo(res.data);
              setTimeout(() => {
                if (res.data != null) {
                  localStorage.setItem("user", JSON.stringify(res.data)); //存储用户信息到浏览器
                  this.$router.push("homePage/FlowerEcharts");
                }
              }, 500);
            } else {
              this.$notify({
                title: res.message,
                type: "error",
              });
            }
          })
          .catch((error) => {
            console.error(error);
          });
    },
    setUserInfo(item) {
      this.$store.commit("setUserId", item.id);
      this.$store.commit("setUserName", item.username);
      this.$store.commit("setUserPic", item.avatar);
      this.$store.commit("setNickName", item.nickname);
      this.$store.commit("setCid", item.cid);
      this.$store.commit("setRoleId", item.roleId);
    },
    handleSignUp() {
      this.$router.push("/register");
    },
    // 后面添加找回密码功能
    forget() {
      this.$router.push("/forget");
    },
  },


};
</script>

<style scoped>
.login-code-img {
  height: 38px;
}

.login-code {
  width: 33%;
  height: 38px;
  float: right;
}

img {
  cursor: pointer;
  vertical-align: middle;
}

/*#captcha {*/
/*  width: 200px;*/
/*  height: 70px;*/
/*  margin: 10px auto;*/
/*}*/
.w-18px {
  width: 18px;
}

.title {
  margin-bottom: 50px;
  text-align: center;
  font-size: 30px;
  font-weight: 600;
  color: rgb(3, 19, 11);
}

.login-wrap {
  position: relative;
  background: url("../../assets/images/plant.png") fixed center;
  background-size: cover;
  width: 100%;
  height: 100%;
}

.input-wid {
  width: 250px;
}

.icon-mine {
  margin-right: 5px;
}

/*.ms-title {*/
/*  position: absolute;*/
/*  top: 50%;*/
/*  width: 100%;*/
/*  margin-top: -230px;*/
/*  text-align: center;*/
/*  font-size: 30px;*/
/*  font-weight: 600;*/
/*  color: rgb(240, 241, 231);*/
/*}*/

.ms-login {
  position: absolute;
  left: 50%;
  top: 30%;
  width: 320px;
  height: 380px;
  margin: -150px 0 0 -190px;
  padding: 40px;
  border-radius: 5px;
  background: #fff;
}

.login-btn {
  text-align: center;
  display: flex;
}

.login-btn button {
  height: 36px;
  width: 100%;
}
</style>

前端主要是采用vue框架搭配Axios使用实现前后端请求数据交互,界面利用ElementPlus搭建,这里不做详述。重点是处理请求后,将验证码的图形输出流在前端以字符拼接的形式进行处理。

request.get('/getCode').then(res => {
        if (res.code === 200) {
         //处理Base64编码后的图片输出流
          this.codeUrl = "data:image/gif;base64," + res.data.img;
          this.loginForm.uuid = res.data.uuid;
          this.code = res.data.code
        }
      });

至此,图形验证码功能就实现了。因为主要是通过后端实现的,因此可能存在一定的性能消耗,仅作为一个学习参考。文章来源地址https://www.toymoban.com/news/detail-764362.html

到了这里,关于前后端分离项目中实现业务中常用图形验证码验证功能(详细)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Java企业工程管理系统源码+项目说明+功能描述+前后端分离 + 二次开发

        工程项目各模块及其功能点清单 一、系统管理     1、数据字典:实现对数据字典标签的增删改查操作     2、编码管理:实现对系统编码的增删改查操作     3、用户管理:管理和查看用户角色     4、菜单管理:实现对系统菜单的增删改查操作     5、角色管理:管理

    2023年04月09日
    浏览(51)
  • thinkphp6前后端验证码分离以及验证

    1.验证码接口生成验证码: 也可以自己写方法 2.验证方法和普通模式session验证有区别,需要改原文件:          修改后的代码:

    2024年02月12日
    浏览(42)
  • java版企业工程项目管理系统 Spring Cloud+Spring Boot+Mybatis+Vue+ElementUI+前后端分离 功能清单

         Java版工程项目管理系统 Spring Cloud+Spring Boot+Mybatis+Vue+ElementUI+前后端分离 功能清单如下: 首页 工作台:待办工作、消息通知、预警信息,点击可进入相应的列表 项目进度图表:选择(总体或单个)项目显示1、项目进度图表  2、项目信息 施工地图:1、展示当前角色权

    2024年02月08日
    浏览(69)
  • 实现前后端分离的登陆验证token思路

    在前后端完全分离的情况下,Vue项目中实现token验证大致思路如下: 1、第一次登录的时候,前端调后端的登陆接口,发送用户名和密码 2、后端收到请求,验证用户名和密码,验证成功,就给前端返回一个token 3、前端拿到token,将token存储到localStorage和vuex中,并跳转路由页面

    2024年02月03日
    浏览(41)
  • 前后端分离------后端创建笔记(07)表单验证

    4.1 定义一个方法  4.2 这里表单的数据在哪里,就是这个 4.3  this.userForm,这里能够让数据清空 6.1 有两种常规的验证,第一种是非空验证,另一种长度验证,这一种非常普遍,因此在ELEMTUI中做了一些封装,因此我们直接拿过来用就行,还有一种情况,像电子邮件,这种情况就

    2024年02月13日
    浏览(43)
  • 基于若依前后端分离框架的小程序的token验证

    后端和管理端都用的若依框架。 但是前段的小程序需要微信授权登录。这时候就需要在若依框架上重新再起一套token验证。 首先创建两个类(只要放在你能够引用得到的位置就可以): 第一个:实体 第二个service: 下一步: 找到 com.ruoyi.framework.security.filter; 这个文件 添加你

    2024年02月11日
    浏览(47)
  • 瑞吉外卖项目——前后端分离

    前后端分离开发,就是在项目开发过程中,对于前端代码的开发由专门的 前端开发人员 负责,后端代码则由 后端开发人员 负责,这样可以做到分工明确、各司其职,提高开发效率,前后端代码并行开发,可以加快项目开发进度。 目前,前后端分离开发方式已经被越来越多

    2023年04月20日
    浏览(48)
  • 项目实战-前后端分离博客系统

    纯后端讲解 完整的前台后台代码编写 主流技术栈(SpringBoot,MybatisPlus,SpringSecurity,EasyExcel,Swagger2,Redis,Echarts,Vue,ElementUI....) 完善细致的需求分析 由易到难循序渐进 ​我们有前台和后台两套系统。两套系统的前端工程都已经提供好了。所以我们只需要写两套系统的后端。 ​但是

    2024年02月14日
    浏览(42)
  • 3.若依前后端分离版开发用户自定义配置表格功能

    一、背景 在项目上线测试的时候,关于同一个界面的表格,不同的用户会出现不同的字段排列需求,有些用户希望把A字段排在最前面,有些用户则希望A字段不显示。针对这种情况,开发一个表格自定义配置的功能,每个用户根据自己的需求自己去设定表单字段的显示、隐藏

    2024年02月12日
    浏览(41)
  • 虚拟机构建单体项目及前后端分离项目

    在现代化办公环境中,会议是组织沟通、决策和合作的重要方式之一。为了提高会议的效率和质量,许多企业选择部署会议OA系统来实现会议管理的自动化和数字化。本博客将介绍如何部署和优化会议OA系统,并探讨前后端分离的SPA项目在此过程中的应用  步骤 要构建虚拟机

    2024年02月08日
    浏览(47)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包