SpringSecurity下,使用Redis实现验证码验证,用户错误登陆次数限制,锁定/释放用户

2022-09-19 11:09:14

写在前面

本篇涉及两个场景

  • 验证码验证逻辑
  • 错误登录控制(锁定/释放用户)

本篇只是对这两种场景的一种实现,可供参考,还有别的实现方式,可自行学习探索、使用

一、接口设计

1.1、验证码接口

在这里插入图片描述

1.2、登陆接口

在这里插入图片描述

二、验证码验证逻辑

2.1、验证码生成,几种生成方式可供参考,参考链接

参考中都是写到文件,实际使用时,是请求验证码生成接口,接口响应写到输出流到页面

/**
 * 生成验证码
 */@RestControllerpublicclassCaptchaImageController{@Autowiredprivate StringRedisTemplate stringRedisTemplate;@GetMapping("/code/image")public ResultBeancreateCode(String captchaId, HttpServletRequest request, HttpServletResponse response)throws IOException{if(StringUtil.isNullStr(captchaId)){return ResultBean.error(CodeEnum.CUSTON_ERROR,"缺少参数");}// 设置大小,以及位数
        SpecCaptcha specCaptcha=newSpecCaptcha(129,48,4);// 设置字体
        specCaptcha.setFont(newFont("Times New Roman", Font.ITALIC,34));// 设置类型
        specCaptcha.setCharType(Captcha.TYPE_NUM_AND_UPPER);

        stringRedisTemplate.opsForValue().set(
                RedisKeyGen.getCaptcha(captchaId),
                specCaptcha.text(),60,
                TimeUnit.SECONDS);
        specCaptcha.out(response.getOutputStream());return null;}}

2.2、验证码文本,临时存储,基于Redis,有效期 1 分钟

参考生成代码中,存储

 stringRedisTemplate.opsForValue().set(
                RedisKeyGen.getCaptcha(captchaId),
                specCaptcha.text(),60,
                TimeUnit.SECONDS);

除了Redis临时存储之外,还有以下方式作为存储

  • 使用关系型数据库表作为临时存储,校验功能
  • 使用 Session 临时存储,校验时从 Request 中获取已生成的验证码文本与登录接口传参验证码文本比较

2.3、初次登陆不需要验证码

只是当前业务场景

2.4、验证码错误不计入错误登录次数

验证码可无限刷新登录,验证码错误不计入错误登录控制 / 锁定

三、错误登录控制(5次)(锁定/释放用户)

3.1、使用 Redis 临时存储错误次数(10分钟内,记录连续错误次数,最多五次)

3.2、10分钟内,连续登陆错误(用户名/密码错误)5次后,锁定用户 4 小时

3.3、锁定用户,Redis 临时存储 锁定用户记录 4 小时,4h 后自动释放,可重新登陆

四、详细代码如下

@PostMapping("/login")public ResultBeanlogin(
            HttpServletRequest request,
            HttpServletResponse response,
            String username,
            String password,
            String captchaId,
            String captchaCode){// 验证用户是否被锁定
        String lockUser= stringRedisTemplate.opsForValue().get(RedisKeyGen.getLockUser(username));if(!StringUtil.isNullStr(lockUser)){return ResultBean.error(CodeEnum.USER_LOCKED);}//在去redis获取登录次数的一个key,有效期10分钟,如果没获取这个key,但是验证码不为空的时候,// 直接返回提示,验证码已过期,请刷新浏览器,
        String loginErrTimes= stringRedisTemplate.opsForValue().get(RedisKeyGen.getLoginErr(username));if(StringUtil.isNullStr(loginErrTimes)&&!StringUtil.isNullStr(captchaCode)){return ResultBean.error(CodeEnum.CAPTCHA_EXPIRED_ERROR);}

        Integer loginErrorTime=0;if(!StringUtil.isNullStr(loginErrTimes)){
            loginErrorTime= Integer.valueOf(loginErrTimes);}// 如果验证吗为空(缓存刷新,首次登陆),那不需判断验证吗,否则如果有,必须判断验证吗是否正确if(!StringUtil.isNullStr(captchaCode)){
            String code= stringRedisTemplate.opsForValue().get(RedisKeyGen.getCaptcha(captchaId));if(StringUtil.isNullStr(code)){return ResultBean.error(CodeEnum.CAPTCHA_EXPIRED_ERROR);}if(!captchaCode.equalsIgnoreCase(code)){// 忽略大小写return ResultBean.error(CodeEnum.CAPTCHA_ERROR);}}// 10分钟内,不可连续用户/密码错误 5 次
        Authentication authentication= null;try{
            UsernamePasswordAuthenticationToken upToken=newUsernamePasswordAuthenticationToken(username, password);
            authentication= authenticationManager.authenticate(upToken);
            SecurityContextHolder.getContext().setAuthentication(authentication);
            String token= jwtTokenProvider.generateToken(username);
            Cookie cookie=newCookie(HEADER, token);
            cookie.setHttpOnly(true);
            cookie.setPath("/");//设置过期时间4小时
            cookie.setMaxAge(4*60*1000);
            cookie.setSecure(request.isSecure());
            cookie.setDomain(request.getServerName().toLowerCase());
            response.addCookie(cookie);}catch(AuthenticationException e){int i= loginErrorTime+1;
            stringRedisTemplate.opsForValue().set(RedisKeyGen.getLoginErr(username), String.valueOf(i),10, TimeUnit.MINUTES);if(i==4){return ResultBean.error(CodeEnum.ERROR_FOUR);}if(i>=5){
                stringRedisTemplate.opsForValue().set(RedisKeyGen.getLockUser(username),"1",4, TimeUnit.HOURS);return ResultBean.error(CodeEnum.USER_LOCKED);}//            stringRedisTemplate.delete(Lists.newArrayList(RedisKeyGen.getUserInfo(username), RedisKeyGen.getUserResource(username)));return ResultBean.error(CodeEnum.PSAA_ERROR);}// 登陆成功,删除缓存的锁定用户和错误登陆次数
        stringRedisTemplate.delete(Lists.newArrayList(RedisKeyGen.getLockUser(username), RedisKeyGen.getLoginErr(username)));return ResultBean.ok(getLoginVO(username));}
  • 作者:Tonels
  • 原文链接:https://tonels.blog.csdn.net/article/details/104657127
    更新时间:2022-09-19 11:09:14