Redis实现短信登录的企业实例分析(redis,开发技术)

时间:2024-04-28 21:10:10 作者 : 石家庄SEO 分类 : 开发技术
  • TAG :

");
}
//3.符合,生成验证码
Stringcode=RandomUtil.randomNumbers(6);

//4.保存验证码到session
session.setAttribute("code",code);

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

2. 短信验证码登录、注册

Redis实现短信登录的企业实例分析

主要代码:

UserController

/*
登录功能
@paramloginForm登录参数,包含手机号、验证码;或者手机号、密码
/
@PostMapping("/login")
publicResultlogin(@RequestBodyLoginFormDTOloginForm,HttpSessionsession){
//实现登录功能
returnuserService.login(loginForm,session);
}

UserServiceImpl

@Override
publicResultlogin(LoginFormDTOloginForm,HttpSessionsession){
//1.校验手机号
Stringphone=loginForm.getPhone();
if(RegexUtils.isPhoneInvalid(phone)){
//如果不符合,返回错误信息
returnResult.fail("手机号格式错误!");
}

//2.校验验证码
ObjectcacheCode=session.getAttribute("code");
Stringcode=loginForm.getCode();
if(cacheCode==null||!cacheCode.toString().equals(code)){
//3.验证码不一致,则报错
returnResult.fail("验证码错误");
}

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

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

//7.保存用户信息到session中,UserDTO只包含简单的用户信息,
//而不是完整的User,这样可以隐藏用户的敏感信息(例如:密码等),还能减少内存使用
session.setAttribute("user",BeanUtil.copyProperties(user,UserDTO.class));

//8.返回ok
returnResult.ok();
}

privateUsercreateUserWithPhone(Stringphone){
//1.创建用户
Useruser=newUser();
user.setPhone(phone);
//随机设置昵称user_mrkuw05lok
user.setNickName(SystemConstants.USER_NICK_NAME_PREFIX+RandomUtil.randomString(10));
//2.保存用户
save(user);
returnuser;
}

3. 登录验证功能

用户请求登录时,会携带cookie,cookie中包含JSEESIONID

Redis实现短信登录的企业实例分析

为了避免用户请求每个controller时,每次都去校验用户信息,所以可以加拦截器

拦截器只需在用户请求访问时,校验一次后将用户信息保存到ThreadLocal中,供后续线程使用

Redis实现短信登录的企业实例分析

主要代码:

在工具类中编写ThreadLocal

publicclassUserHolder{
privatestaticfinalThreadLocal<UserDTO>tl=newThreadLocal<>();

publicstaticvoidsaveUser(UserDTOuser){
tl.set(user);
}

publicstaticUserDTOgetUser(){
returntl.get();
}

publicstaticvoidremoveUser(){
tl.remove();
}
}

在工具类中编写登录拦截器

publicclassLoginInterceptorimplementsHandlerInterceptor{

/*
前置拦截
@paramrequest
@paramresponse
@paramhandler
@return
@throwsException
/
@Override
publicbooleanpreHandle(HttpServletRequestrequest,HttpServletResponseresponse,Objecthandler)throwsException{
//1.获取session
HttpSessionsession=request.getSession();
//2.获取session中的用户
Objectuser=session.getAttribute("user");
//3.判断用户是否存在
if(user==null){
//4.不存在,拦截,返回401状态码
response.setStatus(401);
returnfalse;
}
//5.存在,保存用户信息到ThreadLocal
UserHolder.saveUser((User)user);
//6.放行
returntrue;
}

/*
后置拦截器
@paramrequest
@paramresponse
@paramhandler
@paramex
@throwsException
/
@Override
publicvoidafterCompletion(HttpServletRequestrequest,HttpServletResponseresponse,
Objecthandler,Exceptionex)throwsException{
//请求结束后移除用户,防止ThreadLocal造成内存泄漏
UserHolder.removeUser();
}
}

在配置类中添加拦截器配置类

@Configuration
publicclassMvcConfigimplementsWebMvcConfigurer{

/*
添加拦截器
@paramregistry
/
@Override
publicvoidaddInterceptors(InterceptorRegistryregistry){
//登录拦截器
registry.addInterceptor(newLoginInterceptor())
//排除不需要拦截的路径
.excludePathPatterns(
"/shop/",
"/voucher/
",
"/shop-type/",
"/upload/
",
"/blog/hot",
"/user/code",
"/user/login"
);
}
}

UserController

@GetMapping("/me")
publicResultme(){
//获取当前登录的用户并返回
UserDTOuser=UserHolder.getUser();
returnResult.ok(user);
}

三、集群的session共享问题

Redis实现短信登录的企业实例分析

四、基于Redis实现共享session的登录功能

1. 选择合适的数据结构存入Redis

  • 手机号作为key,String类型的验证码作为value

  • 用户登录时正好会提交手机号,方便通过Redis进行校验验证码

Redis实现短信登录的企业实例分析

token作为key,Hash类型的用户信息作为value

后端校验成功后,会返回token给前端,前端会将token保存到sessionStorage中(这是浏览器的存储方式),以后前端每次请求都会携带token,方便后端通过Redis校验用户信息

Redis实现短信登录的企业实例分析

前端代码:将后端返回的token保存到sessionStorage中

Redis实现短信登录的企业实例分析

前端每次请求时,都会通过拦截器将token设置到请求头中,赋值给变量authorization,后端通过authorization获取前端携带的token进行校验

Redis实现短信登录的企业实例分析

2. 发送短信验证码

修改之前代码,将验证码存入Redis

@Service
@Slf4j
publicclassUserServiceImplextendsServiceImpl<UserMapper,User>implementsIUserService{

@Resource
privateStringRedisTemplatestringRedisTemplate;

@Override
publicResultsendCode(Stringphone,HttpSessionsession){
//1.使用工具类校验手机号
if(RegexUtils.isPhoneInvalid(phone)){
//2.如果不符合,返回错误信息
returnResult.fail("手机号格式错误!");
}
//3.符合,生成验证码
Stringcode=RandomUtil.randomNumbers(6);

//4.保存验证码到session
//session.setAttribute("code",code);
//4.保存验证码到redis
//"login:code:"是业务前缀,以"login:code:"+手机号为key,过期时间2分钟
stringRedisTemplate.opsForValue().set(RedisConstants.LOGIN_CODE_KEY+phone,code,RedisConstants.LOGIN_CODE_TTL,TimeUnit.MINUTES);

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

3. 短信验证码登录、注册

  • 修改之前代码,从Redis获取验证码并校验

  • 随机生成token,保存用户信息到redis中,返回token

@Override
publicResultlogin(LoginFormDTOloginForm,HttpSessionsession){
//1.校验手机号
Stringphone=loginForm.getPhone();
if(RegexUtils.isPhoneInvalid(phone)){
//如果不符合,返回错误信息
returnResult.fail("手机号格式错误!");
}

////2.校验验证码
//ObjectcacheCode=session.getAttribute("code");
//Stringcode=loginForm.getCode();
//if(cacheCode==null||!cacheCode.toString().equals(code)){
////3.验证码不一致,则报错
//returnResult.fail("验证码错误");
//}

//2.从Redis获取验证码并校验
StringcacheCode=stringRedisTemplate.opsForValue().get(RedisConstants.LOGIN_CODE_KEY+phone);
Stringcode=loginForm.getCode();
if(cacheCode==null||!cacheCode.equals(code)){
//3.验证码不一致,则报错
returnResult.fail("验证码错误");
}

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

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

////7.保存用户信息到session中,UserDTO只包含简单的用户信息,而不是完整的User,这样可以隐藏用户的敏感信息(例如:密码等),还能减少内存使用
//session.setAttribute("user",BeanUtil.copyProperties(user,UserDTO.class));

//7.保存用户信息到redis中
//7.1随机生成token,作为登录令牌
//使用hutool工具中的UUID,true表示不带“-”符号的UUID
Stringtoken=UUID.randomUUID().toString(true);

//7.2将User对象转为Hash类型进行存储
UserDTOuserDTO=BeanUtil.copyProperties(user,UserDTO.class);
//由于使用的是stringRedisTemplate,所以存入的value中的值必须都是String类型的
//但是UserDTO中的id是Long类型的,所以进行对象属性拷贝时,需要自定义实现转换规则
Map<String,Object>userMap=BeanUtil.beanToMap(userDTO,newHashMap<>(),CopyOptions.create().setIgnoreNullValue(true).setIgnoreNullValue(true).setFieldValueEditor((fieldName,fieldValue)->fieldValue.toString()));

//7.3存入redis,"login:token:"是业务前缀,以"login:token:"+token作为key
stringRedisTemplate.opsForHash().putAll(RedisConstants.LOGIN_USER_KEY+token,userMap);
//7.4设置token有效期,有效期为30分钟
stringRedisTemplate.expire(RedisConstants.LOGIN_USER_KEY+token,RedisConstants.LOGIN_USER_TTL,TimeUnit.MINUTES);

//8.返回token
returnResult.ok(token);
}

privateUsercreateUserWithPhone(Stringphone){
//1.创建用户
Useruser=newUser();
user.setPhone(phone);
//随机设置昵称user_mrkuw05lok
user.setNickName(SystemConstants.USER_NICK_NAME_PREFIX+RandomUtil.randomString(10));
//2.保存用户
save(user);
returnuser;
}

4. 解决token刷新问题

  • token刷新问题是指,用户长时间不进行界面操作时,到了过期时间,token自动失效;但是,用户一旦进行操作,就需要给token续期,即更新token过期时间

  • 为了解决token刷新问题,需要加2个拦截器

  • 第一个拦截器可以拦截所有请求,只要用户有请求就刷新token,并保存用户信息到ThreadLocal中

  • 第二个拦截器只对登录请求进行拦截,从ThreadLocal中获取用户信息进行校验

Redis实现短信登录的企业实例分析

刷新token的拦截器代码:

publicclassRefreshTokenInterceptorimplementsHandlerInterceptor{

//因为LoginInterceptor不是通过Spring进行管理的Bean,所以不能再LoginInterceptor中进行注入StringRedisTemplate
//可以通过构造方法传入StringRedisTemplate
privateStringRedisTemplatestringRedisTemplate;

publicRefreshTokenInterceptor(StringRedisTemplatestringRedisTemplate){
this.stringRedisTemplate=stringRedisTemplate;
}

/*
前置拦截
@paramrequest
@paramresponse
@paramhandler
@return
@throwsException
/
@Override
publicbooleanpreHandle(HttpServletRequestrequest,HttpServletResponseresponse,Objecthandler)throwsException{
////1.获取session
//HttpSessionsession=request.getSession();
////2.获取session中的用户
//Objectuser=session.getAttribute("user");
////3.判断用户是否存在
//if(user==null){
////4.不存在,拦截,返回401状态码
//response.setStatus(401);
//returnfalse;
//}
////5.存在,保存用户信息到ThreadLocal
//UserHolder.saveUser((UserDTO)user);
////6.放行
//returntrue;

//1.获取请求头中的token
Stringtoken=request.getHeader("authorization");
if(StrUtil.isBlank(token)){
//不存在,则拦截,返回401状态码
response.setStatus(401);
returnfalse;
}

//2.通过token获取redis中的用户
Map<Object,Object>userMap=stringRedisTemplate.opsForHash()
.entries(RedisConstants.LOGIN_USER_KEY+token);

//3.判断用户是否存在
if(userMap.isEmpty()){
//4.用户不存在,则拦截,返回401状态码
response.setStatus(401);
returnfalse;
}

//5.将redis中Hash类型数据转换成UserDTO对象
UserDTOuserDTO=BeanUtil.fillBeanWithMap(userMap,newUserDTO(),false);

//6.用户存在,保存用户信息到ThreadLocal
UserHolder.saveUser(userDTO);

//7.刷新token有效期
stringRedisTemplate.expire(RedisConstants.LOGIN_USER_KEY+token,RedisConstants.LOGIN_USER_TTL,TimeUnit.MINUTES);

//8.放行
returntrue;
}

/*
后置拦截器
@paramrequest
@paramresponse
@paramhandler
@paramex
@throwsException
/
@Override
publicvoidafterCompletion(HttpServletRequestrequest,HttpServletResponseresponse,
Objecthandler,Exceptionex)throwsException{
//请求结束后移除用户,防止ThreadLocal造成内存泄漏
UserHolder.removeUser();
}
}

登录拦截器的代码:

publicclassLoginInterceptorimplementsHandlerInterceptor{

@Override
publicbooleanpreHandle(HttpServletRequestrequest,HttpServletResponseresponse,Objecthandler)throwsException{
//1.判断是否需要拦截(ThreadLocal中是否有用户)
if(UserHolder.getUser()==null){
//没有,需要拦截,设置状态码
response.setStatus(401);
//拦截
returnfalse;
}
//有用户,则放行
returntrue;
}

}

@Configuration
publicclassMvcConfigimplementsWebMvcConfigurer{

@Resource
privateStringRedisTemplatestringRedisTemplate;

/*
添加拦截器
@paramregistry
/
@Override
publicvoidaddInterceptors(InterceptorRegistryregistry){
//登录拦截器
registry.addInterceptor(newLoginInterceptor())
//排除不需要拦截的路径
.excludePathPatterns(
"/shop/",
"/voucher/
",
"/shop-type/",
"/upload/
",
"/blog/hot",
"/user/code",
"/user/login"
).order(1);

//token刷新的拦截器,order越小,执行优先级越高,所以token刷新的拦截器先执行
registry.addInterceptor(newRefreshTokenInterceptor(stringRedisTemplate)).addPathPatterns("/**")
.excludePathPatterns(
//RefreshTokenInterceptor拦截器也需要放行"/user/code","/user/login",不然token过期后再重新登录就会一直被拦截
"/user/code",
"/user/login")
.order(0);
}
}

以上就是关于“Redis实现短信登录的企业实例分析”这篇文章的内容,相信大家都有了一定的了解,希望小编分享的内容对大家有帮助,若想了解更多相关的知识内容,请关注亿速云行业资讯频道。

本文:Redis实现短信登录的企业实例分析的详细内容,希望对您有所帮助,信息来源于网络。
上一篇:Java Mybatis框架应用怎么配置下一篇:

8 人围观 / 0 条评论 ↓快速评论↓

(必须)

(必须,保密)

阿狸1 阿狸2 阿狸3 阿狸4 阿狸5 阿狸6 阿狸7 阿狸8 阿狸9 阿狸10 阿狸11 阿狸12 阿狸13 阿狸14 阿狸15 阿狸16 阿狸17 阿狸18