基于Redis实现短信验证码登录(笔记)

发布于:2025-06-22 ⋅ 阅读:(14) ⋅ 点赞:(0)

一.基于session的短信校验(简单看看就行,重点第二部分的基于redis)

(1)发送短信验证码

(2)短信验证码登录 注册

登录和注册是放在一起实现的.
登录的时候根据验证码先判断是否存在,存在之后.再判断手机号是否在数据库中存在? 不存在立马让他快捷注册!存在则放行!!

对与user表来说,其实phone字段是最重要的.在登录时发现未注册的话,直接提供快捷注册方式(只提供一个手机号..)
其他属性都是默认的...

(3)校验登录状态

每一个session都有对应的sessionID保存到浏览器的cookies当中,
每一个独立的线程都有独立的存储空间,相互之间没有干扰。

session流程:

做用户身份的校验时,拦截器配合threadlocal,来获取当前用户的信息..
如果信息存在,放到threadlocal中,这样拦截器放行之后,用户信息也可以被共享
到处理器方法中.且针对不同的cookie是不同的用户信息!

思路:

1.先根据service层将获取到的手机号发送到手机运营商的服务端

2.用户收到你的验证码之后,会带上手机号码,和code登录,登录成功也需要存到session

登录包含注册


4.还需要进行登录校验。查询用户登录信息。

5.不需要在session存那么多信息,返回登录信息的时候,不必返回电话号码那些

二、基于redis的短信校验(重点)

1.redis解决session共享的思路

每一个tomcat都有自己session空间,当负载到第一个tomcat的时候,用户把信息存储到第一台tomcat,那么当第二次的时候负载到第二台的时候该用户信息为空查不到为空,此时就会发生用户在浏览网站的时候,莫名其妙地被拦截了

如何基于redis???

1.发送短信验证码,并且存入到redis,然后设置有效期(关键)

考虑短信登录的验证码,redis要保存的是哪种数据结构,首先考虑String
然后我们的redis是一个共享空间,我们还得确保key是不一致的(建议以手机号

而且这个key和值是存在一定的时效性,不可能验证码过了几个小时几个月还能用

代码:

      1.校验手机号(利用正则表达式) 不符合就返回错误信息 符合就发送验证码(得先生成),然后保存起来,后续需要做校验
          if(RegexUtils.isPhoneInvalid(phone))
          {
              return Result.fail("手机号格式不正确");
          }
//          2.利用随机生成数生成验证码
        String code= RandomUtil.randomNumbers(6);
//TODO   保存到redis,记得加上前缀,以及有效期(如果不加以限制,每当有人一直狂点,就不好了)
        redisTemplate.opsForValue().set(RedisConstants.LOGIN_CODE_KEY +phone,code, 5,TimeUnit.MINUTES);

//TODO    发送验证码,需要调用第三方平台
        log.debug("发送短信验证码成功:{}",code);

//      4.返回ok
        return Result.ok();

2.登录 注册功能

记住虽然上一步已经校验过手机号码是否有误,但是不同的接口还是得再次重新校验一次。以防万一,如果这时用户故意输错,没有做好判断也不好

接着再从redis中取,然后做一个是否为空或者输入不正确判断

代码:

现在以token为登录凭证,但是不会自动写到浏览器,我们需要手动返回给前端

    //虽然我们上面的接口已经校验了手机号 但是两次不同的请求,手机号必须还要再次进行校验
        // 1.先校验手机号
        if(RegexUtils.isPhoneInvalid(loginFormDTO.getPhone()))
        {
            return Result.fail("手机号格式不正确");
        }

        //TODO 以前是session获取,现在redis
        String cacheCode = (String) redisTemplate.opsForValue().get(RedisConstants.LOGIN_CODE_KEY+loginFormDTO.getPhone());
        String code=loginFormDTO.getCode();

      //   3.看是否验证码是否一致,不一致直接报错
        if(cacheCode==null ||! cacheCode.toString().equals(code))
            return Result.fail("验证码错误");
     //    4.根据手机号查用户
        User user = query().eq("phone", loginFormDTO.getPhone()).one();
//        判断并保存
        System.out.println("the user info"+user);
        if(user==null)
        {
              user=createUserByPhone(loginFormDTO.getPhone());
        }

//        保存用户信息到redis
//        先模拟生产token
        UserDTO userDTO = new UserDTO();
        userDTO.setId(user.getId());
        userDTO.setIcon(user.getIcon());
        userDTO.setNickName(user.getNickName());
        String token = UUID.randomUUID().toString();
//        转为hash 这里对象用hash存储 String也行看需求啦
        Map<String, Object> map = BeanUtil.beanToMap(userDTO);
//        存储
        redisTemplate.opsForHash().putAll("login:token:"+token,map);
        redisTemplate.expire("login:token:"+token,30,TimeUnit.MINUTES);
//        那么直接扔到redis也不行,同样设置有效期,一段时间就去除

//        优化:只要用户在不断访问我们,我们就不断地更新token有效期

//        一般这里是需要返回登录凭证
        return Result.ok(token);

3..校验登录状态

代码:


//        1,获取请求头中的token
        String token = request.getHeader("authorization");
//        判断是否为空再取
        if (StrUtil.isBlank(token))
        {
            // 未授权
            response.setStatus(401);
            return false;
        }
//        记住这里是通过entry拿到 键值对的形式
        Map userMap = template.opsForHash().entries("login:token:" + token);



//        2.拿到用户信息
        System.out.println(userMap+"拦截用户信息");
        if(userMap == null)
        {
//            未授权
            response.setStatus(401);
            return false;
        }
//        UserDTO userDTO = new UserDTO();
//                userDTO.setId(user.getId());
//                userDTO.setIcon(user.getIcon());
//                userDTO.setNickName(user.getNickName());
//        将查询到的userMap形式转为对象
        UserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);
//        存在则就保存到ThreadLocal
        UserHolder.saveUser(userDTO);
//        刷新token
        template.expire("login:token:" + token,30, TimeUnit.MINUTES);

前端(小知识):前端会去帮我们存储这个token

总结:
对redis的数据的读取,需要关注:
    (1)键的唯一性要求
    (2)方便将来携带的需求..

前端的逻辑:
(1)前端的登录是一个ajax请求,当success时获取到的是后端返回的token
(2)存储到浏览器的sessionStorage
(3)以后每次请求都要携带,通过在前端设置拦截器来保证每次发送请求时都会携带该token
   用户用户登录状态的校验.每次都将token放到请求头中

4.解决登录状态刷新的问题

注意:
之前的如果只设置一个拦截需要登录权限才能访问的url,那么如果此时已经登录的用户一直在
访问一些不需要登录权限就能看的url(比如首页),那这个拦截器就拦截不到哇,就没法更新此时redis中对应的该用户的token
.这样用户的登录状态也就消失了.所以我们需要两个拦截器,新增一个拦截器拦截所有(刷新和保存用户信息),只要有用户信息就更新token
且放入threadlocal中,第二个拦截器只需要判断threadlocal中有没有用户信息就行!

(1)token刷新拦截器,拦截所有请求
(2)登录状态校验拦截器,拦截所有需要登录后才能访问的url

总结:
token刷新拦截器:
第一个拦截器只关心当前是否有用户信息,有则刷新过期时间+存threadlocal.没有也要刷新TTL且放行,后续的如果是需要登录的路径会被第二个拦截器拦截,由于在第一个拦截器中并没有放入threadlocal所以第二个拦截器会拦截!

登录拦截器:
第二个拦截器只关心threadlocal中是否存在数据,有则放行,没有则拦截!!

测试:不管现在我是需要登录还是不需要登录的,只要访问了,都会去刷新token    


网站公告

今日签到

点亮在社区的每一天
去签到