手机免密登陆

wynnyo 2020年07月20日 1,347次浏览

功能描述

在前后端分离实现手机免密, vue + .net core

整体思路

  • 输入手机号
  • 验证手机号的合法性
  • 点击获取验证码, 开启 60s 的定时器, 60s 后可以重新获取
  • 在后端生成验证码, 并以手机号为 key, 验证码为 value 存入 redis, 时效为 10 分钟
  • 通过短信把验证码发送到手机上
  • 输入验证码
  • 前端验证验证码的合法性(6位数字)
  • 登陆操作
  • 后端在 redis 验证手机号和验证码是否匹配
  • 匹配登陆并返回 token
  • 不匹配返回错误

具体实现

前端

  • 手机号合法验证

    phoneRules: {
      type: Array,
      default: () => {
        return [
          { required: true, message: "手机号不能为空", trigger: "blur" },
          {
            validator: (rule, value, callback) => {
              let regExp = /^1[345789]\d{9}$/;
              if (regExp.test(value) === false) {
                callback(new Error("手机号不合法, 请从新输入"));
              } else {
                callback();
              }
            },
            trigger: "blur"
          }
        ];
      }
    },
    
  • 验证码合法验证

    validCodeRules: {
      type: Array,
      default: () => {
        return [
          { required: true, message: "验证码不能为空", trigger: "blur" },
          {
            validator: (rule, value, callback) => {
              let regExp = /^\d{6}$/;
              if (regExp.test(value) === false) {
                callback(new Error("验证码不合法, 请从新输入"));
              } else {
                callback();
              }
            },
            trigger: "blur"
          }
        ];
      }
    },
    
  • 获取验证码

    getValidCode() {
      // 先验证手机号是否合法
      this.$refs.loginPhoneForm.validateField("phone", valid => {
        if (!valid) {
          this.isShowText = false;
          this.time = 60;
          let self = this;
    
          let myTimer = () => {
            self.time -= 1;
            if (self.time === 0) {
              self.isShowText = true;
              window.clearTimeout(timer);
            }
          };
    
          let timer = window.setInterval(myTimer, 1000);
          axios({
            method: "post",
            url: "/api/platform/phonelogin/valid",
            data: {
              Phone: this.form.phone
            }
          }).catch(error => {
          });
        }
      });
    }
    
  • 登陆操作

    axios({
      method: "post",
      url: "/api/platform/phonelogin/login",
      data: {
        Phone: phone,
        Code: validCode
      }
    })
    .then(res => {
      if (res) {
        // 设置 token, 跳转
    	  ...
      }
    })
    .catch(error => {
      // 错误处理
      ...
    });
    

后端

  • 生成验证码, 为了方便记忆, 生成的验证码有以下特点

    • 随机拿到 3 个不重复的数字: a, b, c
    • 对 3 个数字进行包装, 得到: a1 => ab, a2 => a, a3 => ccc
    • 对 a1, a2, a3 随机排序, 得到一个 6 位的验证码
    /// <summary>
    /// 生成验证码
    /// </summary>
    /// <returns></returns>
    public static string GenerateValidCode()
    {
        var arr = Enumerable.Range(0, 10).OrderBy(t => Guid.NewGuid()).Take(3).ToArray();
    
        var a1 = arr[0].ToString() + arr[1];
        var a2 = arr[0].ToString();
        var a3 = arr[2].ToString() + arr[2] + arr[2];
    
        var arr1 = new List<string> { a1, a2, a3 };
    
        return string.Join("", arr1.OrderBy(t => Guid.NewGuid()));
    }
    
  • 获取验证码

    public class PhoneLoginQo
    {
        [MobileValid("手机不合法")]
        public string Phone { get; set; }
    }
    
    [HttpPost]
    [Route("valid")]
    [AllowAnonymous]
    public async Task Valid(PhoneLoginQo qo)
    {
        var code = SmsHelper.GenerateValidCode();
    
        await PhoneValidCodeService.SetPhoneCodeAsync(qo.Phone, code);
    
        var content = $"您的验证码是:{code}。为了保障信息安全,请勿告诉他人。";
    
        SmsHelper.SendSms(qo.Phone, content);
    }
    
  • 登陆

    [HttpPost]
    [Route("login")]
    [AllowAnonymous]
    public async Task<PhoneLoginVo> Login(PhoneValidLoginQo qo)
    {
        try
        {
            // 去 redis 验证 手机号 和 验证码
            if (await PhoneValidCodeService.CheckPhoneCodeAsync(qo.Phone, qo.Code))
            {
                var db = userService.Context.Db;
                var user = await db.Queryable<UserEntity>()
                    .FirstAsync(e => e.LoginId == qo.Phone);
                if (user == null)
                {
                  throw ExceptionHelper.NewMessageErrorException("PHONE_USER_NOT_EXIST", "该手机用户不存在");
                }
    
                // 存在, 使用该用户登陆, 返回 对应 token
                ...
                //var token = ...
    
                return new PhoneLoginVo() { IsSuccess = true, TokenInfo = token };
            }
            else
            {
                return new PhoneLoginVo() { IsSuccess = false };
            }
        }
        catch (Exception e)
        {
            throw ExceptionHelper.NewMessageErrorException("", e.Message);
        }
    }
    

效果展示

image-20200720141054171

image-20200720141217352

image-20200720141251650