短信登录session-redis

server/2024/12/22 19:45:03/

1.流程

在这里插入图片描述

1.1 发送验证码

  1. 模拟路径
http://127.0.0.1:8080/api/user/code?phone=1335566
Request Method:POST
  1. controller层
	/*** 发送手机验证码*/@PostMapping("code")public Result sendCode(@RequestParam("phone") String phone, HttpSession session) {// TODO 发送短信验证码并保存验证码return userService.sendCode(phone,session);}

2.调用service层Impl

@Overridepublic Result sendCode(String phone, HttpSession session) {// 1.校验手机号if (RegexUtils.isPhoneInvalid(phone)) {// 2.如果不符合,返回错误信息return Result.fail("手机号格式错误!");}// 3.符合,生成验证码String code = RandomUtil.randomNumbers(6);// 4.保存验证码到 sessionsession.setAttribute("code",code);// 5.(模拟)发送验证码log.debug("发送短信验证码成功,验证码:{}", code);// 返回okreturn Result.ok();}

1.2 验证码登录

1.controller层

 	 /*** 登录功能* @param loginForm 登录参数,包含手机号、验证码;或者手机号、密码*/@PostMapping("/login")public Result login(@RequestBody LoginFormDTO loginForm, HttpSession session){return userService.login(loginForm, session);}

2.service层Impl

 @Overridepublic Result login(LoginFormDTO loginForm, HttpSession session) {// 1.校验手机号String phone = loginForm.getPhone();if (RegexUtils.isPhoneInvalid(phone)) {// 2.如果不符合,返回错误信息return Result.fail("手机号格式错误!");}// 3.校验验证码Object cacheCode = session.getAttribute("code");String code = loginForm.getCode();if(cacheCode == null || !cacheCode.toString().equals(code)){//3.不一致,报错return Result.fail("验证码错误");}//一致,根据手机号查询用户  mybatis-plusUser user = query().eq("phone", phone).one();//5.判断用户是否存在if(user == null){//不存在,则创建user =  createUserWithPhone(phone);}//7.保存用户信息到session中,返回的数据为了安全性进行Dto封装,使用hutool中的BeanUtil.copyProperties()进行复制session.setAttribute("user", BeanUtil.copyProperties(user, UserDTO.class));return Result.ok();}private User createUserWithPhone(String phone) {//创建用户配置默认信息User user = new User();user.setPhone(phone);user.setNickName(USER_NICK_NAME_PREFIX + RandomUtil.randomString(10));//向数据库中添加用户save(user);return user;}

1.3 登录校验

  1. 在utils层创建拦截器并且实现HandlerInterceptor接口
  2. 快捷键Ctrl + i 重写相关方法
public class LoginInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {//1.获取sessionHttpSession session = request.getSession();//2.获取session中的用户Object user = session.getAttribute("user");//3.判断用户是否存在if(user == null){//4.不存在,拦截,返回401状态码response.setStatus(401);return false;}//5.存在,保存用户信息到ThreadlocalUserHolder.saveUser((UserDto) user);//6.放行return true;}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {//为了防止内存溢出要对ThreadLocal中的数据进行清除UserHolder.removeUser();}
}
  1. 让拦截器生效(配置拦截器)
    在config层创建配置类并且实现WebMvcConfigurer接口,添加@Configuration注解,重写addInterceptors方法
@Configuration
public class MvcConfig implements WebMvcConfigurer {@Overridepublic void addInterceptors(InterceptorRegistry registry) {// 登录拦截器registry.addInterceptor(new LoginInterceptor()).excludePathPatterns("/shop/**","/voucher/**","/shop-type/**","/upload/**","/blog/hot","/user/code","/user/login");}
}

2. ThreadLocal如何书写

public class UserHolder {private static final ThreadLocal<UserDTO> tl = new ThreadLocal<>();public static void saveUser(UserDTO user){tl.set(user);}public static UserDTO getUser(){return tl.get();}public static void removeUser(){tl.remove();}
}

3. session共享问题:多台Tomcat服务器不共享session存储空间,请求到不同服务器是可能出现数据丢失

4.基于Redis实现共享session登录

在这里插入图片描述

4.1 发送验证码

  1. 模拟路径
http://127.0.0.1:8080/api/user/code?phone=1335566
Request Method:POST
  1. controller层
	/*** 发送手机验证码*/@PostMapping("code")public Result sendCode(@RequestParam("phone") String phone, HttpSession session) {// TODO 发送短信验证码并保存验证码return userService.sendCode(phone,session);}

2.调用service层Impl
【和之前不同的是:要引入redis相关依赖,不写入session,而是写入redis数据库

@Resourceprivate StringRedisTemplate stringRedisTemplate;@Overridepublic Result sendCode(String phone, HttpSession session) {// 1.校验手机号if (RegexUtils.isPhoneInvalid(phone)) {// 2.如果不符合,返回错误信息return Result.fail("手机号格式错误!");}// 3.符合,生成验证码String code = RandomUtil.randomNumbers(6);/*4.保存验证码到 sessionsession.setAttribute("code",code);*///4.保存验证码到redis并设置过期时间stringRedisTemplate.opsForValue().set(LOGIN_CODE_KEY + phone,code,2, TimeUnit.MINUTES);// 5.(模拟)发送验证码log.debug("发送短信验证码成功,验证码:{}", code);// 返回okreturn Result.ok();}

4.2 验证码登录

 @Overridepublic Result login(LoginFormDTO loginForm, HttpSession session) {// 1.校验手机号String phone = loginForm.getPhone();if (RegexUtils.isPhoneInvalid(phone)) {// 2.如果不符合,返回错误信息return Result.fail("手机号格式错误!");}// 3.从redis获取验证码并校验String cacheCode = stringRedisTemplate.opsForValue().get(LOGIN_CODE_KEY + phone);String code = loginForm.getCode();if (cacheCode == null || !cacheCode.equals(code)) {// 不一致,报错return Result.fail("验证码错误");}//4一致,根据手机号查询用户User user = query().eq("phone", phone).one();//5.判断用户是否存在if(user == null){//不存在,则创建user =  createUserWithPhone(phone);}
/*        7.保存用户信息到session中session.setAttribute("user", BeanUtil.copyProperties(user, UserDTO.class));*/// 7.保存用户信息到 redis// 7.1.随机生成token,作为登录令牌 UUID hutool中的String token = UUID.randomUUID().toString(true);// 7.2.将User对象转为DTO和HashMap存储UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class);Map<String, Object> userMap = BeanUtil.beanToMap(userDTO, new HashMap<>(),CopyOptions.create().setIgnoreNullValue(true).setFieldValueEditor((fieldName, fieldValue) -> fieldValue.toString()));// 7.3.存储String tokenKey = LOGIN_USER_KEY + token;stringRedisTemplate.opsForHash().putAll(tokenKey, userMap);// 7.4.设置token有效期stringRedisTemplate.expire(tokenKey, LOGIN_USER_TTL, TimeUnit.MINUTES);return Result.ok(token);}private User createUserWithPhone(String phone) {User user = new User();user.setPhone(phone);user.setNickName(USER_NICK_NAME_PREFIX + RandomUtil.randomString(10));save(user);return user;}

4.3 登录校验

  1. 在utils层创建拦截器并且实现HandlerInterceptor接口
  2. 快捷键Ctrl + i 重写相关方法
  3. 需要注入Redis相关依赖
public class LoginInterceptor implements HandlerInterceptor {private StringRedisTemplate stringRedisTemplate;public LoginInterceptor(StringRedisTemplate stringRedisTemplate) {this.stringRedisTemplate = stringRedisTemplate;}@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {// 1.获取请求头中的tokenString token = request.getHeader("authorization");if (StrUtil.isBlank(token)) {response.setStatus(401);return false;}// 2.基于TOKEN获取redis中的用户String key  = LOGIN_USER_KEY + token;Map<Object, Object> userMap = stringRedisTemplate.opsForHash().entries(key);// 3.判断用户是否存在if (userMap.isEmpty()) {response.setStatus(401);return false;}// 5.将查询到的hash数据转为UserDTOUserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);// 6.存在,保存用户信息到 ThreadLocalUserHolder.saveUser(userDTO);// 7.刷新token有效期stringRedisTemplate.expire(key, LOGIN_USER_TTL, TimeUnit.MINUTES);//6.放行return true;}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {UserHolder.removeUser();}
}
  1. 让拦截器生效(配置拦截器)
    在config层创建配置类并且实现WebMvcConfigurer接口,添加@Configuration注解,重写addInterceptors方法
@Configuration
public class MvcConfig implements WebMvcConfigurer {@Resourceprivate StringRedisTemplate stringRedisTemplate;@Overridepublic void addInterceptors(InterceptorRegistry registry) {// 登录拦截器registry.addInterceptor(new LoginInterceptor(stringRedisTemplate)).excludePathPatterns("/shop/**","/voucher/**","/shop-type/**","/upload/**","/blog/hot","/user/code","/user/login");}
}

5. 拦截器优化

问题:
在这个方案中,他确实可以使用对应路径的拦截,同时刷新登录token令牌的存活时间,但是现在这个拦截器他只是拦截需要被拦截的路径,假设当前用户访问了一些不需要拦截的路径,那么这个拦截器就不会生效,所以此时令牌刷新的动作实际上就不会执行,所以这个方案他是存在问题的
优化:
既然之前的拦截器无法对不需要拦截的路径生效,那么我们可以添加一个拦截器,在第一个拦截器中拦截所有的路径,把第二个拦截器做的事情放入到第一个拦截器中,同时刷新令牌,因为第一个拦截器有了threadLocal的数据,所以此时第二个拦截器只需要判断拦截器中的user对象是否存在即可,完成整体刷新功能。

redis_339">5.1 定义一个拦截器用来拦截所有请求,因此每个请求都可以对redis中的数据进行刷新,并且优先使用。

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

5.2 在定义一个拦截器,判断是否拦截

public class LoginInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {// 1.判断是否需要拦截(ThreadLocal中是否有用户)if (UserHolder.getUser() == null) {// 没有,需要拦截,设置状态码response.setStatus(401);// 拦截return false;}// 有用户,则放行return true;}}

http://www.ppmy.cn/server/3788.html

相关文章

Spring Boot 多环境配置:YML 文件的三种高效方法

&#x1f31f; 前言 欢迎来到我的技术小宇宙&#xff01;&#x1f30c; 这里不仅是我记录技术点滴的后花园&#xff0c;也是我分享学习心得和项目经验的乐园。&#x1f4da; 无论你是技术小白还是资深大牛&#xff0c;这里总有一些内容能触动你的好奇心。&#x1f50d; &#x…

【C++】日期类的实现

&#x1f49e;&#x1f49e; 前言 hello hello~ &#xff0c;这里是大耳朵土土垚~&#x1f496;&#x1f496; &#xff0c;欢迎大家点赞&#x1f973;&#x1f973;关注&#x1f4a5;&#x1f4a5;收藏&#x1f339;&#x1f339;&#x1f339; &#x1f4a5;个人主页&#x…

github上传代码

偷一下懒&#xff0c;把链接贴一下&#xff0c;后续再补充。 1.下载Git 【学习笔记】上传代码到GitHub&#xff08;保姆级教程&#xff09; 2.如何创建GitHub仓库 手把手教你在github上传文件 3.如何删掉GitHub仓库 github如何删除仓库或项目&#xff1f; 4.遇到的错误 …

java通过maven导入本地jar包的三种方式

一、引入lib下加载&#xff08;加载过后打包&#xff0c;以后再次使用不用再次导入&#xff09; 首先创建一个用于创建jar包的项目&#xff0c;并测试能否成功运行 讲项目打包 在需要引入的项目中创建lib目录 并把刚才打包的jar复制进去 通过dependency引入jar包 groupId、art…

基础算法:一次性理解二分算法

二分查找 1.理解2.三种模板2.朴素模板注意事项 3.左端点模板注意事项 4.右端点模板注意事项 3.典型例题3.1题目要求3.2题目解析3.3实现思想寻找左边界思路寻找右边界思路&#xff1a; 3.4代码实现3.5注意事项 1.理解 本质&#xff1a;二段性 首先我们需要打破我们的固有思维&…

Blender生成COLMAP数据集

最近在做3DGS方向&#xff0c;整理了一下Blender生成自己的数据集。 1 Introduction 在Blender中构建场景&#xff08;light, object, camera&#xff09;&#xff0c;利用Blender的python脚本对其渲染&#xff0c;导出多视角下渲染出的RGB图和depth map&#xff0c;并将trans…

美国硅谷站群服务器如何提高网站性能

美国硅谷站群服务器可以通过多种方式提高网站性能&#xff0c;那么美国硅谷站群服务器如何提高网站性能。Rak部落小编为您整理发布美国硅谷站群服务器如何提高网站性能。 美国硅谷站群服务器提高网站性能主要包括以下几点&#xff1a; 1. 使用内容分发网络(CDN)&#xff1a;通过…

从零开始写一个RTSP服务器(二)RTSP协议的实现

目录 写在前面一、创建套接字二、解析请求三、OPTIONS响应四、DESCRIBE响应五、SETUP响应六、PLAY响应七、源码八、测试 写在前面 此系列只追求精简&#xff0c;旨在学习RTSP协议的实现过程&#xff0c;不追求复杂完美&#xff0c;所以这里要实现的RTSP服务器为了简单&#xf…