Redis进行验证码登录的项目实践
1.基于Session进行对验证码等信息进行存储
我们如果要实现一个验证码的收发校验登录的模块,可以基于Session来进行对信息的存储,然后通过对这些信息的校验来完成对验证码的校验
首先要了解发送验证码的逻辑
注意:以下内容所有带Utill的类都是hutool包下的类,在使用时导入依赖即可使用
1.1发送验证码
流程如下:

在controller中定义方法,传参为前端的手机号和session,其中session是储存信息的容器,可以实现存储用户,手机号等信息
@PostMapping("code")
public Result sendCode(@RequestParam("phone") String phone, HttpSession session) {
//发送短信验证码并保存验证码
return userService.sendCode(phone,session);
}在service里定义方法后在实现类中实现
根据流程来分步实现,这里发送验证码的用到了Hutool包里的生成随机数来生成验证码。
苒后利用一个Utils包里的lambda表达式来判断手机号格式
/**
* 是否是无效手机格式
* @param phone 要校验的手机号
* @return true:符合,false:不符合
*/
public static boolean isPhoneInvalid(String phone){
return mismatch(phone, RegexPatterns.PHONE_REGEX);
}
/** /**
* 手机号正则
*/
public static final String PHONE_REGEX = "^1([38][0-9]|4[579]|5[0-3,5-9]|6[6]|7[0135678]|9[89])\\d{8}$";然后发送验证码可以利用阿里云来对手机号进行发送,但是我们主要讲逻辑,就不实现这个功能了,直接用日志将验证码打印过来
/**
* 发送短信验证码
* @return
*/
@Override
public Result sendCode(String phone, HttpSession session) {
//1.校验手机号
if(RegexUtils.isPhoneInvalid(phone)){
//不符合
return Result.fail("手机号格式错误");
}
//符合
//2.生成验证码
String code = RandomUtil.randomNumbers(6);
//3.保存验证码到seesion
session.setAttribute("code",code);
//4.发送验证码
log.debug("发送成功,验证码为:{}", code);
return Result.ok();
}苒后利用session来储存验证码
1.2校验验证码并登录
流程如下:

校验验证码逻辑较为复杂,首先要定义方法
其中loginFormDTO里传入的就是前端的参数,如手机号,验证码等信息
/**
* 登录功能
* @param loginForm 登录参数,包含手机号、验证码;或者手机号、密码
*/
@PostMapping("/login")
public Result login(@RequestBody LoginFormDTO loginForm, HttpSession session){
//实现登录功能
return userService.login(loginForm,session);
}然后在实现类里实现
其中我们先将手机号从loginForm里取出来再进行判断
@Override
public Result login(LoginFormDTO loginForm, HttpSession session) {
//1.校验手机号
String phone = loginForm.getPhone();
if(RegexUtils.isPhoneInvalid(phone)){
//不一致
return Result.fail("手机号格式错误");
}
//一致
//2.校验验证码
//先在session中取出验证码
Object cacheCode = session.getAttribute("code");
String code = loginForm.getCode();//前端提交的code
//不一致
if(cacheCode==null || !cacheCode.toString().equals(code)){
return Result.fail("验证码已过期或错误");
}
//重新提交验证码
//一致
//3.根据手机号查询用户
User user = query().eq("phone",phone).one();
//用户不存在
if(user==null){
//创建新用户并保存到数据库
user = creatUserWithPhone(phone);
//保存用户到session 在最后一步完成
}
//用户存在
//保存用户到session
//将user转为dto储存
session.setAttribute("user", BeanUtil.copyProperties(user, UserDTO.class));
return Result.ok();
}我们将验证码从session里取出来和前端得到的验证码进行对比:cacheCode对比code
再后利用mybatisplus进行查询用户存入到user中
然后判断用户是否存在,如果不存在,就证明是新用户
新用户创建我们利用一个方法creatUserWithPhone来根据手机号来进行创建
private User creatUserWithPhone(String phone) {
//1.创建用户
//由于新用户大部分创建时都有默认参数,因此我们只需要将手机号和昵称传入即可,昵称随机
//逻辑和大部分创建新用户的软件逻辑一样
User user = new User();
user.setPhone(phone);
user.setNickName(USER_NICK_NAME_PREFIX + RandomUtil.randomString(10));
//2.保存到数据库
save(user);
return null;
}创建以后用mp存入到数据库中
如果存在就将其转为dto存入到session中以便后续使用
1.3拦截校验
实现了基本逻辑业务后,就可以实现进行拦截校验了
先写一个拦截器,基本业务是:在每次请求处理之前获取session里的用户,如果用户不存在,也就是在之前的登录中没有登录,即没有存入用户信息,就进行拦截,返回401
然后在请求处理之后移除用户信息,节约内存开销
//校验登录状态
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//首先由于请求里面带有cookie,我们的request参数例就有session,我们只需要在里面取出即可
//1.获取session
HttpSession session = request.getSession();
//2.获取session中的用户
Object user = session.getAttribute("user");
//3.判断用户是否存在
if(user== null){
//不存在,拦截,返回false即可拦截,返回401状态码
response.setStatus(401);
return false;
}
//存在,将用户保存到ThreadLocal中
UserHolder.saveUser((UserDTO) user);
//放行
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
//移除用户
UserHolder.removeUser();
}
}然后在配置类里注册这个拦截器
@Configuration
public class MvcConfig implements WebMvcConfigurer {
//配置拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor())
.excludePathPatterns(
//排除一些路径,不需要拦截的地方
//如一些页面可以不需要登录校验
"/user/code",
"/user/login",
"/blog/hot",//查看热点,也与登录无关,可以放行
"/shop/**",
"/shop-type/**",
"/upload/**",
"/voucher/**"
);
}2.用Redis实现对验证码信息校验
我们前面使用Httpsession来进行储存用户信息和验证码,但是使用session也有很多问题
主要是session的共享问题:

如果我们使用了tomcat集群来部署项目,多台Tomcat并不共享session存储空间,当请求切换到不同的tomcat服务时导致数据丢失的问题;
而session的替代方案应该满足:
1.数据共享
2.内存存储
3.key,value架构
首先保存的基本思想是用手机号作为key来存储,因为要保证每个用户的key都不同
我们一般用String结构,以Json字符串来保存
或者使用hash结构来进行保存,利用hash结构将对象中的每个字段独立储存,可以针对单个字段进行CRUD,并且内存占用更少
我们以hash结构为例
我们只需要在原来的储存session步骤上替换为存储到Redis里即可
逻辑如下:
注意:
但是在校验验证码登录注册时,我们储存用户信息的key可以使用随机生成的token,值为json对象,为了防止泄漏风险,然后判断用户是否存在时如果存在,就报错用户到Redis里,然后返回token给前端,不存在也在创建新用户后返回token给前端
而且session仍然有用,就是在request请求头中的token里的session就存储的有用户信息,作为校验登录状态时判断用户是否存在的依据,就是在登录或者注册后得到的token获取用户信息
2.1发送验证码时存储到Redis逻辑
我们之前已经实现了发送验证码的功能,我们只需要修改存储验证码到session这个功能即可
在pom文档里导入Redis客户端依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>首先导入RedisTemplete实现API,这里用@Resource和@Autowired进行注入都可以
一个是针对类名装配,一个针对类型装配
@Resource
private StringRedisTemplate stringRedisTemplate;
然后修改业务逻辑
//3.保存验证码到seesion
// session.setAttribute("code",code);
//在key里加上一个前缀来和其他业务里的phone来区分
//然后设置一个有效期
stringRedisTemplate.opsForValue().set("login:code:"+phone,code,2, TimeUnit.MINUTES);2.2校验验证码实现登录注册逻辑

首先就是修改从session中获取验证码进行校验的逻辑
//从Redis获取验证码并校验
String cacheCode = stringRedisTemplate.opsForValue().get(LOGIN_CODE_KEY+phone);
String code = loginForm.getCode();//前端提交的code然后就是修改保存用户信息到redis中这些逻辑
我们把用户信息设置为哈希类型,把号码设置为string类型,因为哈希类型可以保证用户信息等隐私
//保存用户到Redis中
//3.1生成token作为登录令牌
String token = UUID.randomUUID().toString(true);
//3.2将USer对象转为HashMap存储
UserDTO userDTO= BeanUtil.copyProperties(user,UserDTO.class);
Map<String,Object> userMap= BeanUtil.beanToMap(userDTO);
//3.3存储,利用putALL,key可以是多个值,即hashMap,然后设置有效期
String tokenKey= LOGIN_CODE_KEY+token;
stringRedisTemplate.opsForHash().putAll(tokenKey,userMap);
//设置有效期
stringRedisTemplate.expire(tokenKey,LOGIN_USER_TTL,TimeUnit.MINUTES);
//但是还有一个问题,就是有效期的逻辑是,只要是在有效期内无论进行访问,只要有效期到了就失效,不会重置有效期
//因此我们要进行修改逻辑,修改为,只要三十分钟内访问过了就重置有效期,又可以过三十分钟过期
//我们就可以利用拦截器来实现,只要访问了请求,即触发了拦截器,就实现更新有效期的操作
//8.返回token给前端
return Result.ok();然后修改校验登录状态时的从redis获取用户逻辑

如图,然后在每次校验时也要刷新token有效期
//1.获取请求头中的token,根据前端返回的名字来获取
String token= request.getHeader("authorization");
if(StrUtil.isBlank(token)){
response.setStatus(401);
return false;
}
//2.基于token获取redis中的用户,利用entries来获取所有key里的值组成hashMap
Map<Object,Object> userMap= stringRedisTemplate.opsForHash().entries(RedisConstants.LOCK_SHOP_KEY+token);
//3.判断用户是否存在
if(userMap.isEmpty()){
//不存在,拦截,返回false即可拦截,返回401状态码
response.setStatus(401);
return false;
}
//将查询到的hash转为userDto对象
UserDTO userDTO= BeanUtil.fillBeanWithMap(userMap,new UserDTO(),false);
//存在,将用户保存到ThreadLocal中
UserHolder.saveUser(userDTO);
//刷新token有效期
stringRedisTemplate.expire(RedisConstants.LOCK_SHOP_KEY+token,30, TimeUnit.MINUTES);
//放行
return true;到此这篇关于Redis进行验证码登录的项目实践的文章就介绍到这了,更多相关Redis 验证码登录内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!


最新评论