Redis实现短信登录的企业实例分析
导读:本文共8807字符,通常情况下阅读需要29分钟。同时您也可以点击右侧朗读,来听本文内容。按键盘←(左) →(右) 方向键可以翻页。
摘要:");}//3.符合,生成验证码Stringcode=RandomUtil.randomNumbers(6);//4.保存验证码到sessionsession.setAttribute("code",code);//5.模拟发送验证码log.debug("发送短信验证码成功,验证码:{}",code);//返回okreturnRe... ...
目录
(为您整理了一些要点),点击可以直达。");
}
//3.符合,生成验证码
Stringcode=RandomUtil.randomNumbers(6);
//4.保存验证码到session
session.setAttribute("code",code);
//5.模拟发送验证码
log.debug("发送短信验证码成功,验证码:{}",code);
//返回ok
returnResult.ok();
}
}
2. 短信验证码登录、注册
主要代码:
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
为了避免用户请求每个controller时,每次都去校验用户信息,所以可以加拦截器
拦截器只需在用户请求访问时,校验一次后将用户信息保存到ThreadLocal中,供后续线程使用
主要代码:
在工具类中编写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实现共享session的登录功能
1. 选择合适的数据结构存入Redis
手机号作为key,String类型的验证码作为value
用户登录时正好会提交手机号,方便通过Redis进行校验验证码
token作为key,Hash类型的用户信息作为value
后端校验成功后,会返回token给前端,前端会将token保存到sessionStorage中(这是浏览器的存储方式),以后前端每次请求都会携带token,方便后端通过Redis校验用户信息
前端代码:将后端返回的token保存到sessionStorage中
前端每次请求时,都会通过拦截器将token设置到请求头中,赋值给变量authorization,后端通过authorization获取前端携带的token进行校验
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中获取用户信息进行校验
刷新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实现短信登录的企业实例分析的详细内容,希望对您有所帮助,信息来源于网络。