zoukankan      html  css  js  c++  java
  • asp.net core配合vue实现后端验证码逻辑

    概述

    网上的前端验证码逻辑总感觉不安全,验证码建议还是使用后端配合验证。
    如果产品确定可以上网的话,就可以使用腾讯,百度等第三方验证,对接方便。但是产品可能内网部署,就必须自己写了。
    本文章就是基于这一点来实现的。
    前端验证码显示一个图片,后端生成图片。

    部分原理

    1.前端调用生端获取图片时,传入一个roomID,后端生成一个4位验征码,放入redis中。然后生成一个图片返回。
    2.前端显示图片,登录时将roomID和填写的验证码,一并提交,登录接口根据roomId从redis中取出验证码判断是否正确。

    这样就相当于后端验证了。

    大家觉得有问题什么,可以进行评论。谢谢。

    源码

    前端部分代码

    <template>
      <div class="login-container">
        <vue-particles
          color="#ffffff"
          :particleOpacity="0.7"
          :particlesNumber="50"
          shapeType="circle"
          :particleSize="4"
          linesColor="#dedede"
          :linesWidth="1"
          :lineLinked="true"
          :lineOpacity="0.4"
          :linesDistance="150"
          :moveSpeed="2"
          :hoverEffect="true"
          hoverMode="grab"
          :clickEffect="true"
          clickMode="push"
        ></vue-particles>
        <el-form ref="loginForm" :model="loginForm" :rules="loginRules" class="login-form" autocomplete="on" label-position="left">
          <div class="title-container">
            <h3 class="title">智能综合管理系统</h3>
          </div>
    
          <el-form-item prop="username">
            <span class="svg-container">
              <svg-icon icon-class="user" />
            </span>
            <el-input ref="username" v-model="loginForm.username" placeholder="用户名" name="username" type="text" tabindex="1" autocomplete="on" />
          </el-form-item>
    
          <el-tooltip v-model="capsTooltip" content="Caps lock is On" placement="right" manual>
            <el-form-item prop="password">
              <span class="svg-container">
                <svg-icon icon-class="password" />
              </span>
              <el-input
                :key="passwordType"
                ref="password"
                v-model="loginForm.password"
                :type="passwordType"
                placeholder="密码"
                name="password"
                tabindex="2"
                autocomplete="on"
                @keyup.native="checkCapslock"
                @blur="capsTooltip = false"
                @keyup.enter.native="handleLogin"
              />
              <span class="show-pwd" @click="showPwd">
                <svg-icon :icon-class="passwordType === 'password' ? 'eye' : 'eye-open'" />
              </span>
            </el-form-item>
          </el-tooltip>
          <el-form-item prop="yzm">
            <span class="svg-container">
              <svg-icon icon-class="password" />
            </span>
            <el-input type="text" v-model="loginForm.verifyCode" maxlength="4" placeholder="验证码" />
            <div class="identifyCode" @click="refreshCode">
              <el-image :src="verifyImageUrl"></el-image>
            </div>
          </el-form-item>
          <el-button :loading="loading" type="primary" style=" 100%; margin-bottom: 30px" @click.native.prevent="handleLogin">登录</el-button>
        </el-form>
      </div>
    </template>
    
    <script>
    import { validUsername } from '@/utils/validate'
    import Identify from './components/Identify'
    import { uuid } from 'vue-uuid'; // uuid object is also exported to things
    // outside Vue instance.
    
    
    export default {
      name: 'Login',
      components: { Identify },
      data() {
        const validateUsername = (rule, value, callback) => {
          if (!validUsername(value)) {
            callback(new Error('请输入正确的用户名'))
          } else {
            callback()
          }
        }
        const validatePassword = (rule, value, callback) => {
          if (value.length < 6) {
            callback(new Error('密码最少6位'))
          } else {
            callback()
          }
        }
        return {
          loginForm: {
            username: 'admin',
            password: '111111',
            roomId: '',
            verifyCode: ''
          },
          loginRules: {
            username: [{ required: true, trigger: 'blur', validator: validateUsername }],
            password: [{ required: true, trigger: 'blur', validator: validatePassword }]
          },
          passwordType: 'password',
          capsTooltip: false,
          loading: false,
          showDialog: false,
          redirect: undefined,
          otherQuery: {},
          identifyCodes: '1234567890',
          identifyCode: '',
          // verifyImageRoomId: '',
          verifyImageUrl: '',
        }
      },
      watch: {
        $route: {
          handler: function (route) {
            const query = route.query
            if (query) {
              this.redirect = query.redirect
              this.otherQuery = this.getOtherQuery(query)
            }
          },
          immediate: true
        }
      },
      created() {
        // window.addEventListener('storage', this.afterQRScan)
        // this.identifyCode = ''
        // this.makeCode(this.identifyCodes, 4)
        this.refreshCode()
      },
      mounted() {
        if (this.loginForm.username === '') {
          this.$refs.username.focus()
        } else if (this.loginForm.password === '') {
          this.$refs.password.focus()
        }
      },
      destroyed() {
        // window.removeEventListener('storage', this.afterQRScan)
      },
      methods: {
        checkCapslock(e) {
          const { key } = e
          this.capsTooltip = key && key.length === 1 && (key >= 'A' && key <= 'Z')
        },
        showPwd() {
          if (this.passwordType === 'password') {
            this.passwordType = ''
          } else {
            this.passwordType = 'password'
          }
          this.$nextTick(() => {
            this.$refs.password.focus()
          })
        },
        createUuid() {
          let uuidV4 = uuid.v4().replace('-', '').replace('-', '').replace('-', '').replace('-', '')
    
          this.verifyImageRoomId = uuidV4
          this.verifyImageUrl = '/api/Operator/getCaptchaImage/120/40/' + this.verifyImageRoomId
          console.log(uuidV4)
        },
        handleLogin() {
          this.$refs.loginForm.validate(valid => {
            if (valid) {
              this.loading = true
              this.$store.dispatch('user/login', this.loginForm)
                .then(() => {
                  this.$router.push({ path: this.redirect || '/', query: this.otherQuery })
                  this.loading = false
                })
                .catch(() => {
                  this.loading = false
                })
            } else {
              console.log('error submit!!')
              return false
            }
          })
        },
        getOtherQuery(query) {
          return Object.keys(query).reduce((acc, cur) => {
            if (cur !== 'redirect') {
              acc[cur] = query[cur]
            }
            return acc
          }, {})
        },
        // 生成随机数
        randomNum(min, max) {
          return Math.floor(Math.random() * (max - min) + min)
        },
        // 切换验证码
        refreshCode() {
          let uuidV4 = uuid.v4().replace('-', '').replace('-', '').replace('-', '').replace('-', '')
    
          this.loginForm.roomId = uuidV4
          this.verifyImageUrl = '/api/Operator/getCaptchaImage/120/40/' + this.loginForm.roomId
          console.log(uuidV4)
        },
        // 生成四位随机验证码
        makeCode(o, l) {
          for (let i = 0; i < l; i++) {
            this.identifyCode += this.identifyCodes[
              this.randomNum(0, this.identifyCodes.length)
            ]
          }
          console.log(this.identifyCode)
        }
      }
    }
    </script>
    
    <style lang="scss">
    /* 修复input 背景不协调 和光标变色 */
    /* Detail see https://github.com/PanJiaChen/vue-element-admin/pull/927 */
    
    $bg: #283443;
    $light_gray: #fff;
    $cursor: #fff;
    
    @supports (-webkit-mask: none) and (not (cater-color: $cursor)) {
      .login-container .el-input input {
        color: $cursor;
      }
    }
    
    /* reset element-ui css */
    .login-container {
      background: url("~@/assets/background.jpg") no-repeat;
      min-height: 100vh;
    
      .el-input {
        display: inline-block;
        height: 47px;
         85%;
    
        input {
          background: transparent;
          border: 0px;
          -webkit-appearance: none;
          border-radius: 0px;
          padding: 12px 5px 12px 15px;
          color: $light_gray;
          height: 47px;
          caret-color: $cursor;
    
          &:-webkit-autofill {
            box-shadow: 0 0 0px 1000px $bg inset !important;
            -webkit-text-fill-color: $cursor !important;
          }
        }
      }
    
      .el-form-item {
        border: 1px solid rgba(255, 255, 255, 0.1);
        background: rgba(0, 0, 0, 0.1);
        border-radius: 5px;
        color: #454545;
    
        .el-form-item__error {
          color: rgb(223, 223, 176);
        }
      }
    }
    </style>
    
    <style lang="scss" scoped>
    $bg: #2d3a4b;
    $dark_gray: #889aa4;
    $light_gray: #eee;
    
    .login-container {
      min-height: 100%;
       100%;
      background-color: $bg;
      overflow: hidden;
    
      .login-form {
        position: absolute;
        left: 0;
        right: 0;
        top: 0;
        bottom: 0;
        margin: auto;
         520px;
        max- 100%;
        padding: 160px 35px 0;
        margin: 0 auto;
        overflow: hidden;
      }
    
      .tips {
        font-size: 14px;
        color: #fff;
        margin-bottom: 10px;
    
        span {
          &:first-of-type {
            margin-right: 16px;
          }
        }
      }
    
      .svg-container {
        padding: 6px 5px 6px 15px;
        color: $dark_gray;
        vertical-align: middle;
         30px;
        display: inline-block;
      }
    
      .title-container {
        position: relative;
    
        .title {
          font-size: 42px;
          color: $light_gray;
          margin: 0px auto 40px auto;
          text-align: center;
          font-weight: bold;
        }
      }
    
      .show-pwd {
        position: absolute;
        right: 10px;
        top: 7px;
        font-size: 16px;
        color: $dark_gray;
        cursor: pointer;
        user-select: none;
      }
      .identifyCode {
        position: absolute;
        right: 10px;
        top: 5px;
      }
    
      .thirdparty-button {
        position: absolute;
        right: 0;
        bottom: 6px;
      }
    
      @media only screen and (max- 470px) {
        .thirdparty-button {
          display: none;
        }
      }
    }
    </style>
    
    

    后端接口

    
            /// <summary>
            /// 获取验证码
            /// </summary>
            /// <returns></returns>
            [HttpGet("getCaptchaImage/{int}/{height:int}/{roomId}")]
            public IActionResult GetCaptchaImage(int width, int height, string roomId)
            {
                Console.WriteLine(roomId);
                //int width = 100;
                //int height = 36;
                var captchaCode = Captcha.GenerateCaptchaCode();
                var result = Captcha.GenerateCaptchaImage(width, height, captchaCode);
                string redisKey = "VerifyCode_" + roomId;
                Startup.redisDb.StringSet(redisKey, captchaCode, new TimeSpan(0, 5, 0));
                Stream s = new MemoryStream(result.CaptchaByteData);
                return new FileStreamResult(s, "image/png");
            }
    
            /// <summary>
            /// 登录
            /// </summary>
            /// <returns></returns>
            [HttpPost("login")]
            public ApiResponseData Login(LoginDto loginInfo)
            {
                if (string.IsNullOrWhiteSpace(loginInfo.username))
                    return ApiResponse.Error("用户名不能为空");
                if (string.IsNullOrWhiteSpace(loginInfo.password))
                    return ApiResponse.Error("密码不能为空");
    
                Entity.BaseOperator operInfo = Db
                    .Select<BaseOperator>()
                    .Where(a => a.OperatorCode == loginInfo.username && a.Password == loginInfo.password.ToLower() && a.Status == 1 && a.IsDel == false).ToOne();
                if (operInfo == null)
                    return ApiResponse.Error("用户名或者密码不正确");
    
                bool verifyResult = Captcha.ValidateCaptchaCode(loginInfo.RoomId, loginInfo.VerifyCode);
                if(verifyResult == false)
                    return ApiResponse.Error("验证码不正确");
    
                //登录时重新生成token,一个用户只能在一个地方登录
                string token = System.Guid.NewGuid().ToString().Replace("-", "");
                Db.Update<BaseOperator>(operInfo.OperatorId)
                    .Set(a => a.Token, token)
                    .ExecuteAffrows();
    
                dynamic outJson = new ExpandoObject();//初始化一个不包含任何成员的ExpandoObject
                outJson.token = token;
    
                //存入redis
                string redisKey = "UserInfo_" + token;
                Startup.redisDb.StringSet(redisKey, operInfo.OperatorId, new TimeSpan(24, 0, 0));
    
                return ApiResponse.Succ(outJson);
            }
    

    作者:朱鹏飞 加我个人微信(zhupengfeivip)进入郑州IT微信交流群
    个人博客:https://zhupengfeivip.github.io/
    image.png
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

  • 相关阅读:
    appium 元素定位方法
    Mac 使用MuMu模拟器调试
    渗透测试工具Drozer安装使用(Mac)
    渗透测试工具Drozer安装使用(Windows)
    python虚拟环境搭建
    HDU 6900 Residual Polynomial【分治 NTT】
    CF 1405E Fixed Point Removal【线段树上二分】
    Educational Codeforces Round 41
    Educational Codeforces Round 39
    Educational Codeforces Round 36
  • 原文地址:https://www.cnblogs.com/zhupengfei/p/14915166.html
Copyright © 2011-2022 走看看