短信登录-redis实现共享session

流程

前面只是基础功能的实现,后面才是redis共享session应用

生成验证码

实现

 public Result sendCode(String phone, HttpSession session) {
        // 校验手机号
        // 使用的是自己编写的一个工具包进行的正则校验
        if (RegexUtils.isPhoneInvalid(phone)) {
            return Result.fail("手机格式错误");
        }
        // 生成验证码 使用的是hutool包里的工具生成随机数
        String code = RandomUtil.randomNumbers(6);
        // 保存验证码到session
        session.setAttribute("code",code);
        // 发送验证码
        return Result.ok();
    }

手机号登录,注册

实现

 public Result login(LoginFormDTO loginForm, HttpSession session) {
        // 1.校验手机号
        // 使用的是自己编写的一个工具包进行的正则校验
        String phone = loginForm.getPhone();
        if (RegexUtils.isPhoneInvalid(phone)) {
            return Result.fail("手机格式错误");
        }
        // 2.校验验证码
        String code = loginForm.getCode();
        Object cacheCode = session.getAttribute("code");
        if(code == null || !code.equals(cacheCode)){
            return Result.fail("验证码有误");
        }
        // 3.根据手机号查询用户
        User user = lambdaQuery().eq(User::getPhone, phone).one();
        // 4.用户是否存在
        if(user == null) {
            // 4.1不存在就创建
            user = createUser(phone,session);
        }
        // 5.存在就将用户保存到session
        session.setAttribute("user",user);
        return Result.ok();
    }

    private User createUser(String phone, HttpSession session) {
        User user = new User();
        user.setPhone(phone);
        user.setNickName(USER_NICK_NAME_PREFIX + phone);
        save(user);
        // 将user放到session
        session.setAttribute("user",user);
        return user;
    }

校验登录状态

实现

拦截器实现

public class LoginInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 1.从session中获取用户
        HttpSession session = request.getSession();
        User user = (User) session.getAttribute("user");
        // 2.用户是否存在
        if (user == null) {
            // 2.1不存在 就拦截
            response.setStatus(401);
            Result notLogin = Result.fail("未授权");
            response.setContentType("text/json");
            response.setCharacterEncoding("utf-8");
            response.getWriter().write(new ObjectMapper().writeValueAsString(notLogin));
            return false;
        }
        // 2.2存在
        // 保存用户到ThreadLocal,使用的是自己编写的工具
        /*UserDTO userDTO = new UserDTO();
        userDTO.setId(user.getId());
        userDTO.setIcon(user.getIcon());
        userDTO.setNickName(user.getNickName());*/
        UserHolder.saveUser(BeanUtil.copyProperties(user,UserDTO.class));//太麻烦,使用hutool工具
        // 3.放行
        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        // 清理数据
        UserHolder.removeUser();
    }
}
@Configuration
public class WebmvcConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoginInterceptor()).
                excludePathPatterns(
                        "/shop/**",
                        "/voucher/**",
                        "/shop-type/**",
                        "/upload/**",
                        "/blog/hot",
                        "/user/code",
                        "/user/login"
                ).order(1);
    }
}

使用redis优化

登录生成验证码优化

 public Result sendCode(String phone, HttpSession session) {
        // 校验手机号
        // 使用的是自己编写的一个工具包进行的正则校验
        if (RegexUtils.isPhoneInvalid(phone)) {
            return Result.fail("手机格式错误");
        }
        // 生成验证码 使用的是hutool包里的工具生成随机数
        String code = RandomUtil.randomNumbers(6);
        // *modify: 将验证码保存到redis中
        stringRedisTemplate.opsForValue()
                .set(RedisConstants.LOGIN_CODE_KEY + phone,code,
                        RedisConstants.LOGIN_CODE_TTL,TimeUnit.MINUTES);// 设置过期时间
        // 发送验证码
        log.info("生成的验证码是:{}",code);
        return Result.ok();
    }

登录优化

public Result login(LoginFormDTO loginForm, HttpSession session) {
        // 1.校验手机号
        // 使用的是自己编写的一个工具包进行的正则校验
        String phone = loginForm.getPhone();
        if (RegexUtils.isPhoneInvalid(phone)) {
            return Result.fail("手机格式错误");
        }
        // 2.校验验证码
        String code = loginForm.getCode();
        // *modify
        // *1.从redis中获取验证

        String codeKey = RedisConstants.LOGIN_CODE_KEY + phone;
        String cacheCode = stringRedisTemplate.opsForValue()
                .get(codeKey);
        if(code == null || !code.equals(cacheCode)){
            return Result.fail("验证码有误");
        }
        // *验证码正确则删除redis中缓存的验证码
        stringRedisTemplate.delete(codeKey);
        // 3.根据手机号查询用户
        User user = lambdaQuery().eq(User::getPhone, phone).one();
        // 4.用户是否存在
        if(user == null) {
            // 4.1不存在就创建
            user = createUser(phone,session);
        }
        // 5.存在就将用户保存到session
        // session.setAttribute("user",user);
        // *modify: 将用户保存到redis中
        // *1.将user转换为map
        // *1.1 先将user转换为userDto
        UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class);
        Map<String, Object> map = BeanUtil.beanToMap(userDTO,new HashMap<>(), new CopyOptions()
                .setIgnoreNullValue(true) //忽略null值
                .setFieldValueEditor((field,value) -> value.toString())//将值变为String类型
        );
        // *2.将map保存到redis
        String tokenKey = RedisConstants.LOGIN_USER_KEY + UUID.randomUUID().toString(true);
        stringRedisTemplate.opsForHash().putAll(tokenKey,map);
        // *3.设置过期时间
        stringRedisTemplate.expire(tokenKey,RedisConstants.LOGIN_USER_TTL, TimeUnit.SECONDS);
        return Result.ok(tokenKey);
    }

拦截器优化

拦截器配置

@Configuration
public class WebmvcConfig implements WebMvcConfigurer {
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoginInterceptor(stringRedisTemplate)).
                excludePathPatterns(
                        "/shop/**",
                        "/voucher/**",
                        "/shop-type/**",
                        "/upload/**",
                        "/blog/hot",
                        "/user/code",
                        "/user/login"
                ).order(1);
    }
}
public class LoginInterceptor implements HandlerInterceptor {

    private StringRedisTemplate stringRedisTemplate;

    public LoginInterceptor(StringRedisTemplate stringRedisTemplate) {
        this.stringRedisTemplate = stringRedisTemplate;
    }

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // *modify
        // *1.从redis中获取用户
        // *1.1.先获取request请求头中的authorization
        String token = request.getHeader("authorization");
        if (StrUtil.isBlank(token)) {
            // 1.2token不存在 就拦截
            response.setStatus(401);
            Result notLogin = Result.fail("未授权");
            response.setContentType("text/json");
            response.setCharacterEncoding("utf-8");
            response.getWriter().write(new ObjectMapper().writeValueAsString(notLogin));
            return false;
        }
        // *从redis获取用户
        Map<Object, Object> entries = stringRedisTemplate.opsForHash().entries(token);
        if (entries.isEmpty()) {
            return false;
        }
        // *2将map转换为userDto
        UserDTO userDTO = BeanUtil.fillBeanWithMap(entries, new UserDTO(), false);
       // * 将用户保存到ThreadLocal
        UserHolder.saveUser(userDTO);
        // *3.设置redis中userDto的过期时间
        stringRedisTemplate.expire(token,RedisConstants.LOGIN_USER_TTL, TimeUnit.SECONDS);
        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        // 清理数据
        UserHolder.removeUser();
    }
}

问题

当一个用户登录之后,访问的资源都是不需要过滤的,这样的话,拦截器就不能对token进行刷新了。

解决方案的话,就是需要在配置一个拦截器,用来拦截所有请求,对这些请求进行token刷新后,直接放行,交给第二个过滤器去判断是否放行。

优化如下

拦截器1

public class RefreshTokenInterceptor implements HandlerInterceptor {

    private StringRedisTemplate stringRedisTemplate;

    public RefreshTokenInterceptor(StringRedisTemplate stringRedisTemplate) {
        this.stringRedisTemplate = stringRedisTemplate;
    }

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // *modify
        // *1.从redis中获取用户
        // *1.1.先获取request请求头中的authorization
        String token = request.getHeader("authorization");
        if (StrUtil.isBlank(token)) {
            return true;
        }
        // *从redis获取用户
        Map<Object, Object> entries = stringRedisTemplate.opsForHash().entries(token);
        if (entries.isEmpty()) {
            return true;
        }
        // *2将map转换为userDto
        UserDTO userDTO = BeanUtil.fillBeanWithMap(entries, new UserDTO(), false);
       // * 将用户保存到ThreadLocal
        UserHolder.saveUser(userDTO);
        // *3.刷新redis中userDto的过期时间
        stringRedisTemplate.expire(token,RedisConstants.LOGIN_USER_TTL, TimeUnit.SECONDS);
        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        // 清理数据
        UserHolder.removeUser();
    }
}

拦截器2

public class LoginInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 1.从Thread Local中获取用户
        UserDTO user = UserHolder.getUser();
        // 2.判断用户是否存在
        if (user == null) {
            //2.1不存在
            return false;
        }
        //2.2用户存在
        return true;
    }
}

配置

@Configuration
public class WebmvcConfig implements WebMvcConfigurer {
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoginInterceptor()).
                excludePathPatterns(
                        "/shop/**",
                        "/voucher/**",
                        "/shop-type/**",
                        "/upload/**",
                        "/blog/hot",
                        "/user/code",
                        "/user/login"
                ).order(1);
        registry.addInterceptor(new RefreshTokenInterceptor(stringRedisTemplate))
                .addPathPatterns("/**").order(0);
    }
}

总结

短信登录使用redis实现需要注意以下几点

  • 生成验证码之后需要将生成的验证码存入到redis,并且设置一个ttl

 

  • 登录时,需要从redis中获取验证码,当登录成功的时候需要将redis中的 验证码删除。
  • 验证码输入正确后,当从数据库查询到用户之后,需要将用户信息保存到redis中,使用hash保存,需要注意的是序列化时value容易出错,并且设置ttl

  • 登录检验时,从redis中获取用户,如果存在,就刷新token时间

  • 需要设置两个拦截器,第一个用于拦截所有请求,之后刷新token时间,第二个用来判断这些请求是否需要过滤

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值