今天小编给大家分享一下Redis如何实现登录注册的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。
在传统的项目中,用户登录成功,将用户信息保存在session中,这种方式在微服务架构中会产生一系列问题。例如在购物车服务具有多台服务器,当一个请求落在购物车1号服务器后,其session保存了用户信息,另一个请求落在了购物车2号服务器,发现没有用户信息,则重新需要进行登录。服务器之间有session不共享的问题。为了解决这一问题,tomcat提出了内存拷贝,即只需要配置一些信息即可实现多台服务器之间的session拷贝,但是这种解决方案也有缺陷,例如:
浪费空间
拷贝有延时,如果在延时内有请求访问,则还会出现上述问题
为了解决此类问题,我们需要使用多个服务共享的信息平台,例如Redis
直接上流程图
流程图简洁明了,其中需要注意的是
Redis中存入验证码的key是手机号拼接的字符串,为什么保存用户到Redis的key要使用随机token,而不是手机号拼接的字符串呢?
因为在用户登录注册时,服务器会获取到手机号,所以可以使用手机号作为key,进行验证手机号和验证码时也方便进行匹对,那么在保存用户信息到Redis时为什么要使用随机token呢?因为在用户独立成功后,用户的每次请求都会携带cookie,如果将保存用户信息的key设置为含手机号的,那么用户的请求中的cookie也需要携带手机号,这样就会有一定的安全风险,所以在用户登录成功后,我们随机生成token,用token作为key,并且返回给前端token,这样前端请求时就会携带token,也避免了安全隐患。
@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(); }
@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.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属性即可配置先后顺序,值越小,优先级越高。
以上就是“Redis如何实现登录注册”这篇文章的所有内容,感谢各位的阅读!相信大家阅读完这篇文章都有很大的收获,小编每天都会为大家更新不同的知识,如果还想学习更多的知识,请关注亿速云行业资讯频道。
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。