涛涛的若依学习笔记——验证码的一整套引入——下(验证码校验)

前言

上一章讲了验证码的获取,我们知道了前端如何实现验证码获取的调用,以及后端如何处理这个验证码的请求。最终结果是:后端传给前端一个这样的返回结果。
在这里插入图片描述
前端根据这个返回结果进行处理(代码解读参见 https://blog.csdn.net/niTaoTaoa/article/details/122463708)

    getCode() {
      getCodeImg().then(res => {
        this.captchaOnOff = res.captchaOnOff === undefined ? true : res.captchaOnOff;
        if (this.captchaOnOff) {
          this.codeUrl = "data:image/gif;base64," + res.img;
          this.loginForm.uuid = res.uuid;
        }
      });
    }

最终实现的就是这个效果
在这里插入图片描述
现在,来看一下输入验证码后会发生怎样精彩的联动吧。

1. 前端输入验证码

假设我已经输入了正确的账号和牧马,以及验证码的结果 3
并点击了登录按钮

1.看看人家的登录按钮怎么写的

        <el-button
          :loading="loading"
          size="medium"
          type="primary"
          style="width:100%;"
          @click.native.prevent="handleLogin"
        >
          <span v-if="!loading">登 录</span>
          <span v-else>登 录 中...</span>
        </el-button>

代码解读

        <el-button
          :loading="loading"
          size="medium"
          type="primary"
          style="width:100%;"
          @click.native.prevent="handleLogin"
        >
          <!-- 绑定一个 loading参数,默认是false,意思是  是不是加载中呗

          type	类型	string	primary / success / warning / danger / info / text

           @click.native.prevent
          1.给vue组件绑定事件时候,必须加上native ,否则会认为监听的是来自Item组件自定义的事件,
          2.prevent 是用来阻止默认的 ,相当于原生的event.preventDefault()
          注意:preventDefault() 方法阻止元素发生默认的行为(例如,当点击提交按钮时阻止对表单的提交)。

          显然,这个按钮会触发一个  handleLogin  的方法
           -->
          <span v-if="!loading">登 录</span>
          <span v-else>登 录 中...</span>
          <!-- 这里就是根据 loading 的值动态显示要显示的内容 -->
        </el-button>

2.看看 handleLogin()方法

    handleLogin() {
      this.$refs.loginForm.validate(valid => {
        if (valid) {
          this.loading = true;
          if (this.loginForm.rememberMe) {
            Cookies.set("username", this.loginForm.username, { expires: 30 });
            Cookies.set("password", encrypt(this.loginForm.password), { expires: 30 });
            Cookies.set('rememberMe', this.loginForm.rememberMe, { expires: 30 });
          } else {
            Cookies.remove("username");
            Cookies.remove("password");
            Cookies.remove('rememberMe');
          }
          this.$store.dispatch("Login", this.loginForm).then(() => {
            this.$router.push({ path: this.redirect || "/" }).catch(()=>{});
          }).catch(() => {
            this.loading = false;
            if (this.captchaOnOff) {
              this.getCode();
            }
          });
        }
      });
    }

解读一下

    handleLogin() {
      this.$refs.loginForm.validate(valid => {
        /**
         * 以前经常用 this.refs,只知道这样用,不知道是干啥的,今天刚好看看了
         * 参考  https://blog.csdn.net/qq_38128179/article/details/88876060
         *   在JavaScript中需要通过document.querySelector("#demo")来获取dom节点,然后再获取这个节点的值。
         * 在Vue中,我们不用获取dom节点,元素绑定ref之后,直接通过this.$refs即可调用,这样可以减少获取dom节点的消耗。
         *
         * 咦,在哪绑定的ref
         * 哦,在第三行
         * <template>
                 <div class="login">
                    <el-form ref="loginForm" :model="loginForm" :rules="loginRules" class="login-form">
                           <h3 class="title">若依后台管理系统</h3>

           通俗的讲,ref特性就是为元素或子组件赋予一个ID引用,通过this.$refs.refName来访问元素或子组件的实例
           this.$refs是一个对象,持有当前组件中注册过 ref特性的所有 DOM 元素和子组件实例
           且只有在组件渲染完成后才填充,在初始渲染的时候不能访问它们,并且它是非响应式的,因此不能用它在模板中做数据绑定

         validate 根据自定义的规则进行校验,符合规则返回true,否则false
         */
        if (valid) {
          this.loading = true; //表示现在开始登录
          if (this.loginForm.rememberMe) {
            Cookies.set("username", this.loginForm.username, { expires: 30 });
            Cookies.set("password", encrypt(this.loginForm.password), { expires: 30 });
            Cookies.set('rememberMe', this.loginForm.rememberMe, { expires: 30 });
          } else {
            Cookies.remove("username");
            Cookies.remove("password");
            Cookies.remove('rememberMe');
          }
          this.$store.dispatch("Login", this.loginForm).then(() => {
            //this.$store.dispatch(‘Login’, this.loginForm)来调取store里的user.js的Login方法,从而要更新。
            //参考 https://blog.csdn.net/longzhoufeng/article/details/103658726
            //dispatch:含有异步操作,数据提交至 actions ,可用于向后台提交数据
            // this.$store.dispatch('isLogin', true);
            // commit:同步操作,数据提交至 mutations ,可用于读取用户信息写到缓存里
            // this.$store.commit('loginStatus', 1);

            //如果登录成功,路由转发到首页
            this.$router.push({ path: this.redirect || "/" }).catch(()=>{});
          }).catch(() => {
            this.loading = false;
            if (this.captchaOnOff) {
              this.getCode();
            }
          });
        }
      });
    }

表单提交后,跳到了 store的user.js的login方法

3.看看store的user.js的Login方法

    // 登录
    Login({ commit }, userInfo) {
      const username = userInfo.username.trim()
      const password = userInfo.password
      const code = userInfo.code
      const uuid = userInfo.uuid
      return new Promise((resolve, reject) => {
        login(username, password, code, uuid).then(res => {
          let data = res.data
          setToken(data.access_token)
          commit('SET_TOKEN', data.access_token)
          setExpiresIn(data.expires_in)
          commit('SET_EXPIRES_IN', data.expires_in)
          resolve()
        }).catch(error => {
          reject(error)
        })
      })
    },

解读

 // 登录
    Login({ commit }, userInfo) {
      /**参考  https://vuex.vuejs.org/zh/guide/actions.html#%E5%88%86%E5%8F%91-action
       * Action 类似于 mutation,不同在于:
         Action 提交的是 mutation,而不是直接变更状态。
         Action 可以包含任意异步操作。
        Action 通过 store.dispatch 方法触发:
      store.dispatch('increment')
      乍一眼看上去感觉多此一举,我们直接分发 mutation 岂不更方便?实际上并非如此,
      还记得 mutation 必须同步执行这个限制么?Action 就不受约束!我们可以在 action 内部执行异步操作
       */
      // 获取表单里的数据,涛涛表示有手就行
      const username = userInfo.username.trim()  //两边去空格
      const password = userInfo.password
      const code = userInfo.code
      const uuid = userInfo.uuid
      //  ES6---new Promise()使用方法  参考 https://blog.csdn.net/qq_29483485/article/details/86605396
      /**
       *Promise 是一个对象,也是一个构造函数。是js对异步操作的一种解决方案,为异步操作提供了统一的接口
       Promise的构造函数接收一个参数,是函数
       并且传入两个参数:resolve,reject,
       分别表示 异步操作执行成功后的回调函数
       和      异步操作执行失败后的回调函数。
       其实这里用“成功”和“失败”来描述并不准确,按照标准来讲,
       resolve是将Promise的状态置为fullfiled,
       reject是将Promise的状态置为rejected。
参考  https://www.cnblogs.com/mg6666/p/14508322.html

       .then() 和 .catch():
                    Promise构造器接受一个函数作为参数,这个函数有两个参数:resolve,reject,分别代表这个Promise实例成功之后的回调函数和失败之后的回调函数。
                      Promise.all() 和 Promise.race():
                  all方法的效果实际就是 谁跑的慢,那么就以谁为准来执行回调,而race则相反,谁跑的快,就以谁为准来执行回调。
       */
      return new Promise((resolve, reject) => {
        login(username, password, code, uuid).then(res => {
          //这些就是后端返回来后的要看的了,暂且不提,先看这个code经历了什么
          //此处调用了login方法,传来四个参数  username, password, code, uuid
          // import { login, logout, getInfo, refreshToken } from '@/api/login'
          // 这个方法是从/api/login导入的,进去看看
          let data = res.data
          setToken(data.access_token)
          commit('SET_TOKEN', data.access_token)
          setExpiresIn(data.expires_in)
          commit('SET_EXPIRES_IN', data.expires_in)
          resolve()
        }).catch(error => {
          reject(error)
        })
      })
    },

4.看看login()方法

// 登录方法 这里没啥好说的,涛涛表示有手就行
export function login(username, password, code, uuid) {
  return request({
    url: '/auth/login',
    headers: {
      isToken: false
    },
    method: 'post',
    data: { username, password, code, uuid }
  })
}

这里访问了 /auth/login 接口,开始看后端!

2.后端校验验证码

1.ValidateCodeFilter类

先进的是网关的过滤器

    @Override
    public GatewayFilter apply(Object config)
    {
        return (exchange, chain) -> {
            ServerHttpRequest request = exchange.getRequest();

            // 非登录/注册请求或验证码关闭,不处理
            if (!StringUtils.containsAnyIgnoreCase(request.getURI().getPath(), VALIDATE_URL) || !captchaProperties.getEnabled())
            {
                return chain.filter(exchange);
            }

            try
            {
                String rspStr = resolveBodyFromRequest(request);
                JSONObject obj = JSONObject.parseObject(rspStr);
                validateCodeService.checkCapcha(obj.getString(CODE), obj.getString(UUID));
            }
            catch (Exception e)
            {
                return ServletUtils.webFluxResponseWriter(exchange.getResponse(), e.getMessage());
            }
            return chain.filter(exchange);
        };
    }

代码解读

    @Override
    public GatewayFilter apply(Object config) {
        return (exchange, chain) -> {
            // 获取接收的请求
            ServerHttpRequest request = exchange.getRequest();
            // 非登录/注册请求或验证码关闭,不处理
            if (!StringUtils.containsAnyIgnoreCase(request.getURI().getPath(), VALIDATE_URL) || !captchaProperties.getEnabled()) {
                return chain.filter(exchange);
            }
            //这次是登录请求,不被过滤
            try {
                //从请求中解析出请求体
                String rspStr = resolveBodyFromRequest(request);
                System.out.println(rspStr);
                // {"username":"admin","password":"admin123","code":"3","uuid":"9d4832c10ca94bedbd1559926afa21b2"}
                //请求体转为对象
                JSONObject obj = JSONObject.parseObject(rspStr);
                // 先根据UUID验证验证码
                validateCodeService.checkCapcha(obj.getString(CODE), obj.getString(UUID));
            } catch (Exception e) {
                //只要有异常出现,就说明有问题,不放行
                return ServletUtils.webFluxResponseWriter(exchange.getResponse(), e.getMessage());
            }
            // 如果验证码通过了,就放行
            return chain.filter(exchange);
        };
    }

这里我们可以看到中间有个
validateCodeService.checkCapcha(obj.getString(CODE), obj.getString(UUID));
这行就是验证码校验

2.checkCapcha()方法

源代码

    /**
     * 校验验证码
     */
    @Override
    public void checkCapcha(String code, String uuid) throws CaptchaException {
        if (StringUtils.isEmpty(code)) {
            throw new CaptchaException("验证码不能为空");
        }
        if (StringUtils.isEmpty(uuid)) {
            throw new CaptchaException("验证码已失效");
        }
        String verifyKey = Constants.CAPTCHA_CODE_KEY + uuid;
        String captcha = redisService.getCacheObject(verifyKey);
        redisService.deleteObject(verifyKey);

        if (!code.equalsIgnoreCase(captcha)) {
            
            throw new CaptchaException("验证码错误");
        }
    }

解读,xdm坚持住,马上结束了!!!

    /**
     * 校验验证码
     */
    @Override
    public void checkCapcha(String code, String uuid) throws CaptchaException {
        if (StringUtils.isEmpty(code)) {
            throw new CaptchaException("验证码不能为空");
        }
        if (StringUtils.isEmpty(uuid)) {
            throw new CaptchaException("验证码已失效");
        }
        //上述内容涛涛表示有手就行

        //根据传来的UUID,拼接上 验证码存储在Redis中的键的前缀,得到这个用户 在 redis 中 验证码 对应的键
        String verifyKey = Constants.CAPTCHA_CODE_KEY + uuid;
        // 根据这个键,在Redis中获取对应的存储的值
        String captcha = redisService.getCacheObject(verifyKey);
        //Redis 删除这个记录
        redisService.deleteObject(verifyKey);
        //如果传来的验证码,和后端根据UUID拼接,在Redis中存储的验证码,在忽略大小写后,不一样
        //说明验证码错误,抛出自定义异常 验证码错误
        //否则不做处理,上个方法没有捕捉到内部方法有异常抛出,正常执行,即放行。
        if (!code.equalsIgnoreCase(captcha)) {
            throw new CaptchaException("验证码错误");
        }
    }

不报错,验证码就没问题,验证码校验即通过。

至此,验证码这章基本结束了

持续更新ing

  • 4
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值