当前位置: 首页 > news >正文

短信登录的实现-redis和session的比较

目录

  • 短信登录功能的实现
    • 一:基于session进行短信登录
    • 1:发送验证码
    • 2:登录
    • 3:登录验证拦截器
    • 4:隐藏用户敏感信息
    • 二:session的集群共享问题
    • 三:基于redis实现短信登录
      • 登录的刷新问题

短信登录功能的实现

一:基于session进行短信登录

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

1:发送验证码

@Override
public Result sendCode(String phone, HttpSession session) {//使用工具类判断手机号是否有效if (RegexUtils.isPhoneInvalid(phone)) {//无效返回错误信息return Result.fail("手机号格式错误");}//使用hutool包中的生成随机数的方法生成一个6位的验证码String code = RandomUtil.randomNumbers(6);//向返回的session中添加验证码信息session.setAttribute("code",code);//模拟发送验证码log.debug("验证码发送成功,{}",code);//发送成功返回return Result.ok();}

2:登录

@Override
public Result login(LoginFormDTO loginForm, HttpSession session) {//检查手机号是否有效,因为可能发送验证码时是正确的,后面又更改了if (RegexUtils.isPhoneInvalid(loginForm.getPhone())) {return Result.fail("手机号格式错误");}//进行验证码的校验String code = (String) session.getAttribute("code");//获取session中的验证码String code1 = loginForm.getCode();//获取用户输入的验证码if (code1==null||!code1.equals(code)){//比较是否相同return Result.fail("验证码输入错误");}//查找用户,看是否存在,从而判断是注册还是登录String phone = loginForm.getPhone();//获取手机号User user = lambdaQuery().eq(User::getPhone, phone).one();//通过mp进行单表查询,条件是手机号相同if (user==null){user= createUserWithPhone(phone);//用户为空,说明是注册,我们调用一个自定义方法来保存返回这个用户}//将用户保存到session中session.setAttribute("user",user);return Result.ok();
}private User createUserWithPhone(String phone) {User user = new User();//创建用户user.setPhone(phone);user.setNickName("user_"+RandomUtil.randomString(6));//获取一个随机用户名save(user);//mp方法直接保存return user;
}

校验验证码-》查询用户-》注册/登录-》保存到session

3:登录验证拦截器

1:自定义拦截器

//自定义的拦截器要实现HandlerInterceptor接口
public class LoginInterceptor implements HandlerInterceptor {//HandlerInterceptor中有三个可以重写的方法:拦截前处理,中,后@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {//获取sessionHttpSession session = request.getSession();//获取session中的用户User user = (User) session.getAttribute("user");//判断用户是否存在if (user==null){//用户不存在不放行,返回状态码401:权限不足response.setStatus(401);return false;}//属性拷贝user->dtoUserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class);//将用户信息放置到线程中;UserHolder.saveUser(userDTO);return true;}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {//放止内存泄露,拦截结束后将用户从线程中删除UserHolder.removeUser();}
}

2:注册拦截器:

//配置类加上注解Configuration,然后注册拦截器就要使用WebMvcConfigurer中的方法,所以我们先实现了
@Configuration
public class MVCConfig implements WebMvcConfigurer {@Override//添加拦截器的方法public void addInterceptors(InterceptorRegistry registry) {//添加拦截器registry.addInterceptor(new LoginInterceptor()).excludePathPatterns("/shop/**","/voucher/**","/shop-type/**","/upload/**","/blog/hot","/user/code","/user/login");//指定不许拦截的路径}
}

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

4:隐藏用户敏感信息

//转成dto隐藏用户信息
UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class);

二:session的集群共享问题

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

因为session是存储在tomcat服务器中的,我们将来部署集群进行负载均衡,那么我们存储在一台tomcat服务器的数据无法实现共享;

我们考虑采用redis:1:共享数据;2:内存存储;3:键值结构

三:基于redis实现短信登录

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

我们以手机号为key,验证码为value保存到redis,然后后面将用户保存到redis使用hash来保存;

用户信息的key使用一个token来保存,一个是保证key的唯一性,一个是保证数据的安全性,因为token也就是key是要存在浏览器中的;要保证安全性;

我们做的修改:

在发送验证码时:

@Override
public Result sendCode(String phone, HttpSession session) {//使用工具类判断手机号是否有效if (RegexUtils.isPhoneInvalid(phone)) {//无效返回错误信息return Result.fail("手机号格式错误");}//使用hutool包中的生成随机数的方法生成一个6位的验证码String code = RandomUtil.randomNumbers(6);//session.setAttribute("code",code);//将验证码保存到redis中,手机号为key,验证码为value,并且设置有效期为2分钟redisTemplate.opsForValue().set(RedisConstants.LOGIN_CODE_KEY+phone,code,2, TimeUnit.MINUTES);//模拟发送验证码log.debug("验证码发送成功,{}",code);//发送成功返回return Result.ok();}

在登录验证时:

 @Overridepublic Result login(LoginFormDTO loginForm, HttpSession session) {//检查手机号是否有效,因为可能发送验证码时是正确的,后面又更改了if (RegexUtils.isPhoneInvalid(loginForm.getPhone())) {return Result.fail("手机号格式错误");}//进行验证码的校验
//        String code = (String) session.getAttribute("code");String code1 = loginForm.getCode();//获取用户输入的验证码String phone = loginForm.getPhone();//获取手机号String code = redisTemplate.opsForValue().get(phone);if (code1==null||!code1.equals(code)){//比较是否相同return Result.fail("验证码输入错误");}//查找用户,看是否存在,从而判断是注册还是登录User user = lambdaQuery().eq(User::getPhone, phone).one();//通过mp进行单表查询,条件是手机号相同if (user==null){user= createUserWithPhone(phone);//用户为空,说明是注册,我们调用一个自定义方法来保存返回这个用户}//转成dto隐藏用户信息UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class);
//        session.setAttribute("user",userDTO);//随机生成token,使用uuid生成,然后转成字符串String token = UUID.randomUUID().toString(true);//将对象转成map,使用beanutil的方法,用于后面给hash赋值Map<String, Object> map = BeanUtil.beanToMap(userDTO);//将用户存储在hash中,key为token,然后putall加入多个字段是map,我们前面创建过了;redisTemplate.opsForHash().putAll(RedisConstants.LOGIN_USER_KEY+token,map);//设置过期时间redisTemplate.expire(RedisConstants.LOGIN_USER_KEY+token,30,TimeUnit.MINUTES);return Result.ok(token);}

拦截器中的操作:

@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//        HttpSession session = request.getSession();//从请求头中获取tokenString token = request.getHeader("authorization");//从redis中根据token取出map;Map<Object, Object> usermap = redisTemplate.opsForHash().entries(RedisConstants.LOGIN_USER_KEY + token);
//        UserDTO userDTO = (UserDTO) session.getAttribute("user");//判断map是否为空,不用判断是否为null,因为上面的方法entries会帮我们判断if (usermap.isEmpty()){//用户不存在不放行,返回状态码401:权限不足response.setStatus(401);return false;}//将map通过beanutil中的方法转成对象,fillBeanWithMap(usermap, new UserDTO(), false),第二个参数是//对象类型,第三个是是否抛出异常;UserDTO userDTO = BeanUtil.fillBeanWithMap(usermap, new UserDTO(), false);//将用户信息放置到线程中;UserHolder.saveUser(userDTO);//刷新token的有效值:只要用户一直访问token一直存在就不需要重新登录获取token,超过刷新时间就需要重新登录redisTemplate.expire(RedisConstants.LOGIN_USER_KEY+token,RedisConstants.CACHE_SHOP_TTL, TimeUnit.MINUTES);return true;}

登录的刷新问题

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

我们之前只有一个拦截器,但是拦截的路径不是所有,如果用户一直访问的是不需要拦截的路径,那么他的token就不会刷新,就会失去登录状态,我们可以再加一个拦截器,第一个拦截一切路径,并且刷新有效期,第二个做登录校验;

我们重新定义一个拦截器,无论与否都放行:

将拦截校验的操作交给第二个拦截器,

 @Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//        HttpSession session = request.getSession();//从请求头中获取tokenString token = request.getHeader("authorization");//从redis中根据token取出map;Map<Object, Object> usermap = stringRedisTemplate.opsForHash().entries(RedisConstants.LOGIN_USER_KEY + token);//判断map是否为空,不用判断是否为null,因为上面的方法entries会帮我们判断if (usermap.isEmpty()){//这个拦截器不做登录校验,直接放行;return true;}//将map通过beanutil中的方法转成对象,fillBeanWithMap(usermap, new UserDTO(), false),第二个参数是//对象类型,第三个是是否抛出异常;UserDTO userDTO = BeanUtil.fillBeanWithMap(usermap, new UserDTO(), false);//将用户信息放置到线程中;UserHolder.saveUser(userDTO);//刷新token的有效值:只要用户一直访问token一直存在就不需要重新登录获取token,超过刷新时间就需要重新登录stringRedisTemplate.expire(RedisConstants.LOGIN_USER_KEY+token,RedisConstants.CACHE_SHOP_TTL, TimeUnit.MINUTES);return true;}

第二个拦截器:他判断是否登录的依据就是treadlocal中有没有用户:

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {UserDTO user = UserHolder.getUser();if (StrUtil.isBlankIfStr(user)) {response.setStatus(401);return false;}return true;
}

别忘记注册拦截器:

public void addInterceptors(InterceptorRegistry registry) {//添加拦截器registry.addInterceptor(new LoginInterceptor()).excludePathPatterns("/shop/**","/voucher/**","/shop-type/**","/upload/**","/blog/hot","/user/code","/user/login").order(1);//指定不许拦截的路径//order设置拦截器的先后顺序,order的值越小,拦截器越先执行;registry.addInterceptor(new ReFlashTokenINterceptor(stringRedisTemplate)).addPathPatterns("/**").order(0);}

http://www.mrgr.cn/news/60961.html

相关文章:

  • JVM—类加载器、双亲委派机制
  • Mysql管理(常用工具与系统数据库)
  • 2024年诺贝尔物理学奖颁发给机器学习与神经网络领域的研究者的意义
  • IDA 修改完汇编代码后,为什么还是调试时还是原来的代码?因为Apply patches to … 另一个exe文件了
  • css之loading旋转加载
  • 论文阅读:三星-TinyClick
  • 分布式数据库技术金融应用规范技术架构
  • SMARTFORMS 条形码CODE39有校验位
  • uniapp的IOS证书申请(测试和正式环境)及UDID配置流程
  • 智能听诊器:宠物健康的守护者
  • iptables限制docker端口禁止某台主机访问(使用DOCKER链和raw表的PREROUTING链)
  • 企业网站cms 企业网站源码模板
  • 高职院校教学一体化护理实训室建设方案
  • kafka消费者组分区分配实战
  • 029_Common_Plots_Matlab常见二维绘图
  • HTML5 应用程序缓存
  • 金蝶三十载逐梦之旅:用友之巅,何以难攀?
  • Flutter动画渐变
  • 【skywalking】仪表盘介绍
  • 沪深A股上市公司数据报告分析