redis|redis实现登录

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

文章目录
  • 基于session实现用户登录
    • 1、发送短信验证码
    • 2、短信验证登录
    • 3、使用拦截器解决登录验证
    • 4、 集群下,使用session解决用户登录的问题
  • redis实现用户登录
    • 1、基于redis实现用户登录的业务流程
    • 1、发送短信验证
    • 3、校验用户登录状态
    • 优化
      • 拦截器1——负责刷新token有效期
      • 拦截器2——负责校验用户是否登录

基于session实现用户登录 redis|redis实现登录
文章图片

1、发送短信验证码 redis|redis实现登录
文章图片

@Override public Result sendCode(String phone, HttpSession session) { //校验手机号 if (RegexUtils.isPhoneInvalid(phone)){ //不符合返回 return Result.fail("手机号格式错误"); } //符合,生成验证码 String code = RandomUtil.randomNumbers(6); //这个是hutool提供的随机生成6位数的API //保存验证码到session session.setAttribute("code",code); //发送验证码(真实情况下发送验证码,要调用第三方的平台,我们这里只是模拟一下) log.debug("发送短信验证码成功,验证码{}", code); return Result.ok(); }

2、短信验证登录 redis|redis实现登录
文章图片

@Override public Result login(LoginFormDTO loginForm, HttpSession session) { //校验手机号 String phone = loginForm.getPhone(); //手机号 if (RegexUtils.isPhoneInvalid(phone)){ //不符合返回 return Result.fail("手机号格式错误"); } //校验验证码String codeClient = loginForm.getCode(); //用户填写的验证码String codeServer = (String)session.getAttribute("code"); //服务器发送给客户端的验证码 if (StringUtils.isEmpty(codeClient)){ //不符合返回 return Result.fail("请填写验证码"); } if (!codeClient.equals(codeServer)){ //不符合返回 return Result.fail("验证码填写有误"); }//校验一致,判断用户是否存在 User user = query().eq("phone", phone).one(); if (user==null){ //创建新用户并保存user= createUserWithPhone(phone); } 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; }

3、使用拦截器解决登录验证 redis|redis实现登录
文章图片

redis|redis实现登录
文章图片

因为一个用户的请求就对应一个线程,为了获取user的线程安全问题,我们把user绑定在线程域ThreadLocal
public class UserHolder { private static final ThreadLocal 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(); } }public class LoginIntercepter implements HandlerInterceptor {@Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { //获取session HttpSession session = request.getSession(); UserDTO user = (UserDTO)session.getAttribute("user"); if (user==null){//判断用户是否存在 response.setStatus(401); return false; } UserHolder.saveUser(user); return true; }@Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { UserHolder.removeUser(); //一定要remove,避免内存泄漏 } }

4、 集群下,使用session解决用户登录的问题 redis|redis实现登录
文章图片

redis实现用户登录 1、基于redis实现用户登录的业务流程 redis|redis实现登录
文章图片

1、发送短信验证:不再将验证码保存在session中,而是保存在redis中,使用String类型即可,key为手机号,value为验证码
2、短信登录和注册:同样需要校验手机号和验证码、判断用户是否存在等。如果用户存在,服务器创建token并返回给客户端,用户保存在redis中,这里推荐Hash类型保存,key为token,如果用户不存在就注册用户,并保存在redis中,逻辑同上
token不推荐用手机号,不安全
redis|redis实现登录
文章图片

redis|redis实现登录
文章图片

3、校验用户登录状态的时候,请求会携带token过来,如果通过token校验用户是否存在
1、发送短信验证
@Override public Result sendCode(String phone, HttpSession session) { //校验手机号 if (RegexUtils.isPhoneInvalid(phone)) { //不符合返回 return Result.fail("手机号格式错误"); } //符合,生成验证码 String code = RandomUtil.randomNumbers(6); //这个是hutool提供的随机生成6位数的API /** * 保存验证码到redis中 * * key:login:code:手机号 * value:验证码 * 保存两分钟 */ stringRedisTemplate.opsForValue().set(LOGIN_CODE_KEY + phone, code, LOGIN_CODE_TTL, TimeUnit.MINUTES); //发送验证码(真实情况下发送验证码,要调用第三方的平台,我们这里只是模拟一下) log.debug("发送短信验证码成功,验证码{}", code); return Result.ok(); }

2、短信登录和注册
@Override public Result login(LoginFormDTO loginForm, HttpSession session) { //校验手机号 String phone = loginForm.getPhone(); //手机号 if (RegexUtils.isPhoneInvalid(phone)) { //不符合返回 return Result.fail("手机号格式错误"); } //校验验证码String codeClient = loginForm.getCode(); //用户填写的验证码 //todo 从redis获取验证码 String codeServer = stringRedisTemplate.opsForValue().get(LOGIN_CODE_KEY + phone); if (StringUtils.isEmpty(codeClient)) { //不符合返回 return Result.fail("请填写验证码"); } if (!codeClient.equals(codeServer)) { //不符合返回 return Result.fail("验证码填写有误"); }//校验一致,判断用户是否存在 User user = query().eq("phone", phone).one(); if (user == null) { //创建新用户并保存user = createUserWithPhone(phone); }//todo 保存用户到redis中 //随机生成token,作为登录令牌 String token = UUID.randomUUID().toString(true); UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class); //将user转化为hashMap存储 String tokenKey = token+LOGIN_USER_KEY; stringRedisTemplate.opsForHash().putAll(LOGIN_USER_KEY + token, BeanUtil.beanToMap(userDTO,new HashMap<>() , CopyOptions.create().setIgnoreNullValue(true).setFieldValueEditor((fieldName,fieldValue)->fieldValue.toString()) )); //设置LOGIN_USER_KEY有效期为半小时,注意的是在校验用户登录状态的时候,要更新这个时间, /** * session,是只要用户超过30分钟不操作,就会删除session中的数据,用户就得重新登录 * 我们这里就要模仿session的做法, 如果用户长时间不访问操作系统就让用户重新登录 */ stringRedisTemplate.expire(tokenKey,LOGIN_USER_TTL,TimeUnit.MINUTES); //返回token return Result.ok(token); }

3、校验用户登录状态
public class LoginIntercepter implements HandlerInterceptor {private StringRedisTemplate stringRedisTemplate; public LoginIntercepter(StringRedisTemplate stringRedisTemplate){ this.stringRedisTemplate = stringRedisTemplate; } @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // TODO 获取请求头中的token String token = request.getHeader("authorization"); if (StrUtil.isBlank(token)){ //不存在,拦截 response.setStatus(401); return false; } String key = RedisConstants.LOGIN_CODE_KEY + token; //TODO 基于token获取redis中的用户(hashMap) Map userMap = stringRedisTemplate.opsForHash().entries(key); if (userMap.isEmpty()){//判断用户是否存在 response.setStatus(401); return false; } //todo 将查询到的hashMap数据转化为UserDTO UserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false); UserHolder.saveUser(userDTO); //todo 刷新todo的有效期 stringRedisTemplate.expire(key,RedisConstants.LOGIN_USER_TTL, TimeUnit.MINUTES); //todo 放行 return true; }@Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { UserHolder.removeUser(); //一定要remove,避免内存泄漏 } }@Configuration public class MvcConfig implements WebMvcConfigurer {@Resource private StringRedisTemplate stringRedisTemplate; @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new LoginIntercepter(stringRedisTemplate)) .excludePathPatterns( "/shop/**", "/voucher/**", "/shop-type/**", "/upload/**", "/blog/hot", "/user/code", "/user/login" ); } }

优化 之前的拦截器,虽然也能起到刷新token有效期的作用,但是,那个token只有在访问需要登录验证的一些handler时候才会刷新
,这显然是不行的,为了解决这个问题,我们在定义一个拦截器,第一个拦截器先执行,拦截所有请求,只负责刷新token的时间(有就刷新,没有就不刷新,不负责拦截),第二个负责判断用户是不是存在(每次用户登录的时候,我们都把user保存在了ThreadLocal中,只需判单ThreadLocal中是否存在即可)
【redis|redis实现登录】redis|redis实现登录
文章图片

redis|redis实现登录
文章图片

拦截器1——负责刷新token有效期
package com.hmdp.config; import com.hmdp.utils.LoginIntercepter; import com.hmdp.utils.RefrshTokenInterceptor; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import javax.annotation.Resource; @Configuration public class MvcConfig implements WebMvcConfigurer {@Resource private StringRedisTemplate stringRedisTemplate; @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new LoginIntercepter()) .excludePathPatterns( "/shop/**", "/voucher/**", "/shop-type/**", "/upload/**", "/blog/hot", "/user/code", "/user/login" ).order(1); //order越小,拦截器优先级越高 registry.addInterceptor(new RefrshTokenInterceptor(stringRedisTemplate)).addPathPatterns("/**").order(0); } }

拦截器2——负责校验用户是否登录
package com.hmdp.utils; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.web.servlet.HandlerInterceptor; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public class LoginIntercepter implements HandlerInterceptor {@Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { //判断是否需要拦截(ThreadLocal中是否有用户) if (UserHolder.getUser()==null){ //没有,需要拦截 response.setStatus(401); return false; } //有用户放行 return true; }@Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { UserHolder.removeUser(); //一定要remove,避免内存泄漏 } }

    推荐阅读