Redis实现登录注册的示例代码

目录
  • 1. 引言
  • 2. 流程图及代码实现
    • 2.1 生成验证码保存到Redis
    • 2.2 登录验证
    • 2.3 请求拦截器
  • 3. 总结

1. 引言

在传统的项目中,用户登录成功,将用户信息保存在session中,这种方式在微服务架构中会产生一系列问题。例如在购物车服务具有多台服务器,当一个请求落在购物车1号服务器后,其session保存了用户信息,另一个请求落在了购物车2号服务器,发现没有用户信息,则重新需要进行登录。服务器之间有session不共享的问题。为了解决这一问题,tomcat提出了内存拷贝,即只需要配置一些信息即可实现多台服务器之间的session拷贝,但是这种解决方案也有缺陷,例如:

  • 浪费空间
  • 拷贝有延时,如果在延时内有请求访问,则还会出现上述问题

为了解决此类问题,我们需要使用多个服务共享的信息平台,例如Redis

2. 流程图及代码实现

直接上流程图

流程图简洁明了,其中需要注意的是

Redis中存入验证码的key是手机号拼接的字符串,为什么保存用户到Redis的key要使用随机token,而不是手机号拼接的字符串呢?

因为在用户登录注册时,服务器会获取到手机号,所以可以使用手机号作为key,进行验证手机号和验证码时也方便进行匹对,那么在保存用户信息到Redis时为什么要使用随机token呢?因为在用户独立成功后,用户的每次请求都会携带cookie,如果将保存用户信息的key设置为含手机号的,那么用户的请求中的cookie也需要携带手机号,这样就会有一定的安全风险,所以在用户登录成功后,我们随机生成token,用token作为key,并且返回给前端token,这样前端请求时就会携带token,也避免了安全隐患。

2.1 生成验证码保存到Redis

   @Override
    public Result sedCode(String phone, HttpSession session) {
        //1. 校验手机号
        if (RegexUtils.isPhoneInvalid(phone)) {
            //2.如果不符合,返回错误信息
            return Result.fail("手机号格式错误");
        }
        // 3.从redis里获取验证码是否存在
        if(null==stringRedisTemplate.opsForValue().get("loginCode" + phone)){
            log.info("请勿重复获取验证码");
            return Result.fail("请勿重复获取验证码");
        }
        //4. 符合,生成验证码
        String code = RandomUtil.randomNumbers(6);
        //5. 保存验证码到redis,并设置有效期1分钟,在设置key的时候,可以提前设置一个常量,然后在这里引用即可
        stringRedisTemplate.opsForValue().set("loginCode:"+phone,code,1, TimeUnit.MINUTES);

        //5. 发送验证码 模拟发送
        log.debug("发送短信验证码成功,验证码:{}",code);
        //返回ok
        return Result.ok();
    }

2.2 登录验证

@Override
    public Result login(LoginFormDTO loginForm, HttpSession session) {
        //1. 校验手机号
        String phone = loginForm.getPhone();
        if (RegexUtils.isPhoneInvalid(phone)) {
            return Result.fail("手机号格式错误");
        }
        //2. 获取Redis中的校验验证码
        String cacheCode = stringRedisTemplate.opsForValue().get("loginCode" + phone);

        // 3.获取表单中的验证码
        String code = loginForm.getCode();
        if (cacheCode == null || !cacheCode.toString().equals(code)){
            //3. 不一致,报错
            return Result.fail("验证码错误");
        }

        //4.一致,根据手机号查询用户
        User user = query().eq("phone", phone).one();

        //5. 判断用户是否存在
        if (user == null){
            //6. 不存在,创建新用户
            user = createUserWithPhone(phone);
        }

        //7.保存用户信息到session
        // 生成token
        String token = UUID.randomUUID().toString();
        // 将User转为Map
        UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class);
        Map<String, Object> userDtoMap = BeanUtil.beanToMap(userDTO);
        // 存储
        stringRedisTemplate.opsForHash().putAll("login:token:"+token,userDtoMap);
        // 设置有效期30分钟
        stringRedisTemplate.expire("login:token:"+token,30, TimeUnit.MINUTES);
        // 返回token给前端
        return Result.ok(token);
    }

2.3 请求拦截器

有些请求是需要用户登录才能进行访问的,所以我们设置一个登录拦截器先拦截请求,判断用户是否登录,如果登录了就进行放行即可。

2.3.1 实现HandlerInterceptor类

public class RefreshTokenInterceptor implements HandlerInterceptor {

    private StringRedisTemplate stringRedisTemplate;

    public RefreshTokenInterceptor(StringRedisTemplate stringRedisTemplate) {
        this.stringRedisTemplate = stringRedisTemplate;
    }

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 1.获取请求头中的token
        String token = request.getHeader("authorization");
        if (StrUtil.isBlank(token)) {
            return true;
        }
        // 2.基于TOKEN获取redis中的用户
        String key  = "login:token:" + token;
        Map<Object, Object> userMap = stringRedisTemplate.opsForHash().entries(key);
        // 3.判断用户是否存在
        if (userMap.isEmpty()) {
            return true;
        }
        // 5.将查询到的hash数据转为UserDTO
        UserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);
        // 6.存在,保存用户信息到 ThreadLocal
        UserHolder.saveUser(userDTO);
        // 7.刷新token有效期
        stringRedisTemplate.expire(key, LOGIN_USER_TTL, TimeUnit.MINUTES);
        // 8.放行
        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        // 移除用户
        UserHolder.removeUser();
    }
}

preHandle方法是在controller之前运行,在这个方法里面可以进行验证登录状态的操作。

  • 为什么要将用户保存到ThreadLocal?因为每一个线程都是独立的,如果将用户信息保存到公共变量中,会造成线程安全问题,每一个线程都具备一个ThreadLocal内存,我们将用户信息保存到ThreadLocal中即可实现线程独享一份用户信息
  • 为什么要刷新Redis中用户信息的有效时长?因为在session中,其机制是当用户不在使用session中的数据超过30分钟就会剔除session的数据,所以在拦截器的前置拦截中进行刷新即可
  • 上述代码的第三步骤,为什么userMap为空了还要放行呢?因为这个拦截器只是做Redis用户信息刷新存活时间的功能,真正拦截的是LoginInterceptor,LoginInterceptor代码在下面展示
public class LoginInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // ThreadLocal中获取用户信息
        if (UserHolder.getUser()==null){
            response.setStatus(401);
            return false;
        }
        // 放行
        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        //移除用户
        UserHolder.removeUser();
    }
}

两个拦截器配置了,但是没有生效,需要在配置类里进行配置

@Configuration
public class MvcConfig implements WebMvcConfigurer {
    @Autowired
    private StringRedisTemplate redisTemplate;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {

        // 登录拦截器
        registry.addInterceptor(new LoginInterceptor())
                .excludePathPatterns(
                        // 配置不需要被拦截的路径
                        "/shop/**",
                        "/voucher/**",
                        "/shop-type/**",
                        "/upload/**",
                        "/blog/hot",
                        "/user/code",
                        "/user/login"
                ).order(1);

        registry.addInterceptor(new RefreshTokenInterceptor(redisTemplate))
                .excludePathPatterns(
                        "/user/login",
                        "/user/code"
                ).order(0);
    }
}

这里配置两个拦截器,两个拦截器是有先后顺序的,上述已经说明,通过设置order属性即可配置先后顺序,值越小,优先级越高。

3. 总结

用户获取验证码存放到redis

登录请求拿着验证码和手机号去进行匹配,匹配成功后将用户信息存入redis

需要登录的请求会访问拦截器,两个拦截器,第一个拦截器RefreshTokenInterceptor负责刷新redis中用户信息的TTL,并且如果Redis中有用户信息,将存入ThreadLocal,第二个拦截器LoginInterceptor 用于检测ThreadLocal是否具有用户信息,如果没有,则前往登录界面,如果有就放行

到此这篇关于Redis实现登录注册的示例代码的文章就介绍到这了,更多相关Redis 登录注册内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • php+redis实现注册、删除、编辑、分页、登录、关注等功能示例

    本文实例讲述了php+redis实现注册.删除.编辑.分页.登录.关注等功能.分享给大家供大家参考,具体如下: 主要界面 连接redis redis.php <?php //实例化 $redis = new Redis(); //连接服务器 $a=$redis->connect("localhost",6379); //var_dump($a); //授权 $redis->auth("107lab"); 注册界面 add.php <form

  • redis+php实现微博(一)注册与登录功能详解

    本文实例讲述了redis+php实现微博注册与登录功能.分享给大家供大家参考,具体如下: (一).微博功能概况 微博用户账号注册 微博用户登录 微博发布 添加微博好友(粉丝) 微博推送 微博冷数据写入mysql数据库 (二).redis数据结构设计 这节分享微博用户注册与登录: 我们完全采用redis作为数据库来实现注册于登录 先来看一下redis数据结构的设计: 注册用户表:user set global:userid set user:userid:1:username zhangshan

  • Redis实现登录注册的示例代码

    目录 1. 引言 2. 流程图及代码实现 2.1 生成验证码保存到Redis 2.2 登录验证 2.3 请求拦截器 3. 总结 1. 引言 在传统的项目中,用户登录成功,将用户信息保存在session中,这种方式在微服务架构中会产生一系列问题.例如在购物车服务具有多台服务器,当一个请求落在购物车1号服务器后,其session保存了用户信息,另一个请求落在了购物车2号服务器,发现没有用户信息,则重新需要进行登录.服务器之间有session不共享的问题.为了解决这一问题,tomcat提出了内存拷贝,

  • SSM框架下实现登录注册的示例代码

    基本配置:jdk1.8   tomcat 8  MyEclipse 先打好地基: spring配置文件 application.xml: <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-

  • 使用JSP实现简单的用户登录注册页面示例代码解析

    实验要求: 将实验2中的系统用户登录和注册页面改为JSP页面,并部署自己的Web应用于Tomcat服务器中 具体要求: 完成登录JSP页面设计和注册页面设计 在登录页面表单中使用request对象获取用户信息,建立新的JSP页面完成登录验证(用户名和密码自己指定即可). 验证结果显示(如登录成功/用户名密码错误,可以使用JavaScript,也可以使用新的JSP页面). 在注册页面表单中使用request对象获取用户注册信息(注册项目由自己指定),在新的JSP页面显示用户注册信息,并提示注册成功

  • SpringBoot+Mybatis实现登录注册的示例代码

    学习SpringBoot+Mybatis实现的登录注册功能的Demo,实现这个Demo在网上也参考了资料和代码,本文是本人在实现Demo后的个人总结,以便理清思路. 1.环境 jdk8 : "1.8.0_281" Maven: 3.6.3 Idea:2020.2 Mysql:5.6.40 Navicat:10.1.7 2.步骤 2.1 创建一个SpringBoot项目 选择依赖如下: Web下的Spring Web. Template Engines下的Thymeleaf. SQL下的

  • 基于SSM框架实现简单的登录注册的示例代码

    一.环境配置工程目录 在pom.xml添加依赖 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd&

  • 浅析SpringCloud Alibaba-Nacos 作为注册中心示例代码

    文档地址 https://github.com/alibaba/spring-cloud-alibaba/blob/master/spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/readme-zh.md 1. 下载nacos-server https://github.com/alibaba/nacos/releases 2.启动nacos-server 默认使用nacos nacos 登录 Linux/Un

  • SpringBoot拦截器实现登录拦截的示例代码

    可以对URL路径进行拦截,可以用于权限验证.解决乱码.操作日志记录.性能监控.异常处理等 实现代码 新建 interceptor包 添加拦截器代码 package com.qcby.interceptor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.method.HandlerMethod; import org.springframework.web

  • SSM项目实现短信验证码登录功能的示例代码

    目录 1.登入网站 zz短信平台 2.导入工具类MessageUtil 3.ajax 模块 4. html页面 5.编写controller层 1.登入网站 zz短信平台 http://sms_developer.zhenzikj.com/zhenzisms_user/login.html 导入pom依赖 <dependency> <groupId>com.zhenzikj</groupId> <artifactId>zhenzisms</artifa

  • Python实现登录接口的示例代码

    之前写了Python实现登录接口的示例代码,最近需要回顾,就顺便发到随笔上了 要求: 1.输入用户名和密码 2.认证成功,显示欢迎信息 3.用户名3次输入错误后,退出程序 4.密码3次输入错误后,锁定用户名 Readme: 1.UserList.txt 是存放用户名和密码的文件,格式为:username: password,每行存放一条用户信息 2.LockList.txt 是存放已被锁定用户名的文件,默认为空 3.用户输入用户名,程序首先查询锁定名单 LockList.txt,如果用户名在里面

  • JavaWeb实现用户登录注册功能实例代码(基于Servlet+JSP+JavaBean模式)

    下面通过通过图文并茂的方式给大家介绍JavaWeb实现用户登录注册功能实例代码,一起看看吧. 一.Servlet+JSP+JavaBean开发模式(MVC)介绍 Servlet+JSP+JavaBean模式(MVC)适合开发复杂的web应用,在这种模式下,servlet负责处理用户请求,jsp负责数据显示,javabean负责封装数据. Servlet+JSP+JavaBean模式程序各个模块之间层次清晰,web开发推荐采用此种模式. 这里以一个最常用的用户登录注册程序来讲解Servlet+JS

随机推荐