验证码防刷校验

为了避免验证码重复发送,可以引入redis将验证码缓存起来;发送验证码,先去缓存中查,如果有,判断时间,确保60s之后才能再次发送验证码;如果没有,就可以发送;

验证码的再次校验,使用redis将验证码缓存起来,并设置过期时间,如果验证码匹配成功,就立即删除缓存;

  • key->sms:code:15825826017
  • value->45678_当前时间(System.currentTimeMillis())

设置认证服务的常量

public class AuthServerConstant {
   //验证码在redis中存储的前缀
	public static final String SMS_CODE_CACHE_PREFIX = "sms:code:";
	public static final String LOGIN_USER = "loginUser";
	public static final String NOT_LOGIN = "请先进行登录";
	public static final String SESSION = "GULISESSION";
}

发送验证码,先去缓存中查,如果有,判断时间,确保60s之后才能再次发送验证码;如果没有,就可以发送;

/** 接收到一个手机号,在此处生成验证码和缓存,然后转给第三方服务让他给手机发验证按
 * */
@ResponseBody
@GetMapping("/sms/sendcode")
public R sendCode(@RequestParam("phone") String phone){
    //  TODO 接口防刷(冷却时长递增),redis缓存 sms:code:电话号
    String redisCode = stringRedisTemplate.opsForValue().get(AuthServerConstant.SMS_CODE_CACHE_PREFIX + phone);
    // 如果不为空,返回错误信息
    if(null != redisCode && redisCode.length() > 0){
        long CuuTime = Long.parseLong(redisCode.split("_")[1]);
        //确保60s之后才能再次发送验证码
        if(System.currentTimeMillis() - CuuTime < 60 * 1000){ // 60s
            return R.error(BizCodeEnum.SMS_CODE_EXCEPTION.getCode(), BizCodeEnum.SMS_CODE_EXCEPTION.getMsg());
        }
    }
    // 生成验证码
    String code = UUID.randomUUID().toString().substring(0, 6);
    // redis缓存验证码,防止同一个phone在60秒内再次发送验证码,加一个当前时间戳
    String redis_code = code + "_" + System.currentTimeMillis();
    // 缓存验证码
   stringRedisTemplate.opsForValue().set(AuthServerConstant.SMS_CODE_CACHE_PREFIX + phone, redis_code , 10, TimeUnit.MINUTES);
    try {// 调用第三方短信服务
        return thirdPartFeignService.sendCode(phone, code);
    } catch (Exception e) {
        log.warn("远程调用不知名错误 [无需解决]");
    }
    return R.ok();
}

密码加密

MD5&MD5盐值加密:Message Digest algorithm 5,信息摘要算法

  • 压缩性:任意长度的数据,算出的MD5值长度都是固定的。
  • 容易计算:从原数据计算出MD5值很容易。
  • 抗修改性:对原数据进行任何改动,哪怕只修改1个字节,所得到的MD5值都有很大区别。
  • 强抗碰撞:想找到两个不同的数据,使它们具有相同的MD5值,是非常困难的。
  • 不可逆

但是MD5不能直接进行密码的加密存储,容易被暴力破解,需要盐值加密;

加盐:

  • 通过生成随机数与MD5生成字符串进行组合
  • 数据库同时存储MD5值与salt值。验证正确性时使用salt进行MD5即可

代码中存储密码:使用spring提供的BCryptPasswordEncoder类,不需要额外添加salt字段

// 密码要加密存储
BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
entity.setPassword(bCryptPasswordEncoder.encode(userRegisterVo.getPassword()));

用户登录,使用BCryptPasswordEncoder类验证密码,在gulimall-member服务进行验证

package com.atguigu.gulimall.member.service.impl;

public MemberEntity login(MemberLoginVo vo) {
    String loginacct = vo.getLoginacct();
    BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
    // 去数据库查询
    MemberEntity entity = this.baseMapper.selectOne(new QueryWrapper<MemberEntity>().eq("username", loginacct).or().eq("mobile", loginacct));
    if(entity == null){
        // 登录失败
        return null;
    }else{
        // 前面传一个明文密码 后面传一个编码后的密码
        boolean matches = bCryptPasswordEncoder.matches(vo.getPassword(), entity.getPassword());
        if (matches){
            entity.setPassword(null);
            return entity;
        }else {
            return null;
        }
    }
}