安全漏洞之多次登录失败账户锁定及验证码

2019/11/14 应用安全

前一篇文章阐述了安全漏洞中最简单的密码明文传输的问题,密码传输的问题解决了,那么如果出现多次登录账户失败之后我们应该怎么处理呢,以及出现暴力破解的情况应该怎样解决呢?阅读本文之后你将了解到以下几个处理安全漏洞的方法。

  • 账户登录多次失败之后锁定账户
  • 登录提供验证码,防止暴力破解

IBM安全检测多次登录失败锁定账户

MNsMVA.md.png

思考

如何通过实现多次登录账户锁定一段时间在进行二次登录。

  • 在登录用户表中添加字段记录登录密码错误次数,达到相应错误次数进行账户锁定,数据库中添加锁定时间,登录时对时间进行判断
  • 使用验证码防止暴力破解登录

封锁方式

一般封锁账户可以选择在数据库中添加字段记录用户输入密码错误次数、记录用户上次错误时间、以及账户封锁时间;也可以选择使用缓存系统redis来记录用户错误登录次数,以及失败时间等来进行登录账号封锁。

写代码前几个步骤

  • 定义RedisKey用来记录密码错误次数
  • 登录失败一次记录该用户失败次数+1,设置这个Redis这个key生命周一1hours
  • 每次登录进来获取该用户的失败次数 大于5 即对账号进行锁定
  • 防止账户暴力破解,使用Kaptcha开源框架实现验证码功能

账户封锁程序时序图

Mra0EQ.jpg

1. 登录时判断账户是否输入错误

登录时判断账户是否输入错误,若输入错误redis记录次数加1

  // 校验密码正确性
    if (!Encrypt.validPassword(dto.getPassword(), user.getPassword())) {
      redisService.set(RedisKeyEnums.USER_DISABLED_TOKEN_KEY, user.getUserId() + "",
          null != passwordNum ? passwordNum + 1 : 1, 1, TimeUnit.HOURS);
      throw new AppException(ErrorCode.PASSWORD_IS_WRONG, ErrorMessages.PASSWORD_IS_WRONG);
    }
  if (null != passwordNum && passwordNum >= 5) {
      redisService
          .set(RedisKeyEnums.USER_DISABLED_TOKEN_KEY, user.getUserId() + "", 5, 2, TimeUnit.HOURS);
      throw new AppException(ErrorCode.ACCESS_DENIED, ErrorMessages.PASSWORD_ERROR_DISBLED);
    }

2. 使用图片验证码防止暴力破解

使用captcha框架实现验证码

2.1 引入maven文件

    <dependency>
      <groupId>com.github.penggle</groupId>
      <artifactId>kaptcha</artifactId>
      <version>2.3.2</version>
    </dependency>

2.2 spring boot 中配置kaptcha bean

@Configuration
public class KaptchaConfig {

  @Bean
  public DefaultKaptcha producer() {
    Properties properties = new Properties();
    properties.put("kaptcha.border", "no");
    properties.put("kaptcha.textproducer.font.color", "black");
    properties.put("kaptcha.textproducer.char.space", "10");
    properties.put("kaptcha.textproducer.char.length", "4");
    properties.put("kaptcha.image.height", "34");
    properties.put("kaptcha.image.width", "150");
    properties.put("kaptcha.textproducer.font.size", "25");

    properties.put("kaptcha.noise.impl", "com.google.code.kaptcha.impl.NoNoise");
    Config config = new Config(properties);
    DefaultKaptcha defaultKaptcha = new DefaultKaptcha();
    defaultKaptcha.setConfig(config);
    return defaultKaptcha;
  }
}

kaptcha 常见配置 链接 kaptcha 常见配置

2.3 提供方法给客户端获取验证码

此方法返回验证码图片的base64编码给客户端,将生成的验证码和UUID 保存在redis中,并且将随机数返回给客户端,以便在登录时将随机数返回进行验证码的校验。

   /**
   * 获取验证码
   */
  @ResponseBody
  @GetMapping(value = "/captcha")
  public APIResult captcha(HttpServletResponse response) throws ServletException, IOException {
    // 生成文字验证码
    String text = producer.createText();
    // 生成图片验证码
    ByteArrayOutputStream outputStream = null;
    BufferedImage image = producer.createImage(text);
    outputStream = new ByteArrayOutputStream();
    ImageIO.write(image, "jpg", outputStream);
    // 对字节数组Base64编码
    BASE64Encoder encoder = new BASE64Encoder();
    // 生成captcha的token
    String token = CommonUtils.getUUID();
    Map<String, Object> map = Maps.newHashMap();
    map.put("imgToken", token);
    map.put("img", encoder.encode(outputStream.toByteArray()));
    redisService.set(RedisKeyEnums.KAPTCHA_CODE, token, text, 5L, TimeUnit.MINUTES);
    return APIResult.success(map);
  }

2.4. 验证码校验

登录时根据客户端返回的随机数获取到存储在本地的验证码与用于输入的校验码进行对比

    String oldKaptchaCode = this.redisService.get(RedisKeyEnums.KAPTCHA_CODE, dto.getImgToken());
    if (!dto.getKaptchaCode().equalsIgnoreCase(oldKaptchaCode)) {
      return APIResult.failed(APIResultCodeEnums.ILLEGAL_PARAMETER, "验证码错误");
    }

结束语

  • 前面两篇文章主要讲述了在登录操作时可能会出现的安全漏洞以及处理方式,后续的文章准备讲下,如果在服务端处理xss攻击。

Search

    Table of Contents