使用redis代替session的登录校验

发布于:2025-05-29 ⋅ 阅读:(40) ⋅ 点赞:(0)

1.代码逻辑流程图:

这里存储用户信息使用hash结构进行存储。 使用String和json存储不是那么方便。

因为原本使用session的方案会自动帮我们进行校验,这里我们使用redis进行代替有很多东西都需要我们自己去存储。前端会把token存放在请求头中,我们每次校验的使用从请求头中获取即可。

2.发送验证码的代码:

public Result sendCode(String phone, HttpSession session) {
    // 校验手机号
    if(RegexUtils.isPhoneInvalid(phone)){
        // 如果不符合,返回错误信息
        return Result.fail("手机号格式错误");
    }
    // 符合生成验证码
    String code = RandomUtil.randomNumbers(6);
    // 保存验证码到redis中
    stringRedisTemplate.opsForValue().set(RedisConstants.LOGIN_CODE_KEY + phone, code
            ,RedisConstants.LOGIN_CODE_TTL
            ,TimeUnit.MINUTES);
    // 发送验证码
    log.debug("发送验证码成功,验证码:{}",code);
    return Result.ok();
}

3.重新实现登录的代码:

public Result login(LoginFormDTO loginForm, HttpSession session) {
    // 校验手机号和验证码
    String phone = loginForm.getPhone();
    if(RegexUtils.isPhoneInvalid(phone)){
        // 如果不符合,返回错误信息
        return Result.fail("手机号格式错误");
    }
    // 从redis中获取验证码并校验验证码
    String cacheCode = stringRedisTemplate.opsForValue()
            .get(RedisConstants.LOGIN_CODE_KEY + phone);
    String code = loginForm.getCode();
    if(cacheCode == null || !cacheCode.equals(code)){
        return Result.fail("验证码错误");
    }
    // 根据手机号查询用户
    User user = query().eq("phone",phone).one();
    if(user == null){
        user = createUserWithPhone(phone);
    }
    //保存用户信息到redis中
    String token = UUID.randomUUID().toString(true);
    String tokenKey = RedisConstants.LOGIN_USER_KEY + token;
    UserDTO userDTO = BeanUtil.copyProperties(user,UserDTO.class);
    Map<String, Object> userMap = BeanUtil.beanToMap(userDTO,new HashMap<>()
            , CopyOptions.create()
                    .setIgnoreNullValue(true)
                    .setFieldValueEditor((fieldName,fieldValue) -> fieldValue.toString()));  // 把User转换成map
    stringRedisTemplate.opsForHash().putAll(tokenKey,userMap);//存储用户信息
    stringRedisTemplate.expire(tokenKey,RedisConstants.LOGIN_USER_TTL,TimeUnit.MINUTES); // 设置有效期
    return Result.ok(token);
}

4.重新实现登录校验的代码逻辑:

这里就是重新实现拦截器的代码

public class RefreshTokenInterceptor implements HandlerInterceptor {

    private StringRedisTemplate stringRedisTemplate;

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

    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 1.获取请求头中的token
        String token = request.getHeader("authorization");
        if (StrUtil.isBlank(token)) {
            return true;
        }
        // 2.基于TOKEN获取redis中的用户
        String key  = LOGIN_USER_KEY + token;
        Map<Object, Object> userMap = stringRedisTemplate.opsForHash().entries(key);
        // 3.判断用户是否存在
        if (userMap.isEmpty()) {
            return true;
        }
        // 5.将查询到的hash数据转为UserDTO
        UserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);
        // 6.存在,保存用户信息到 ThreadLocal
        UserHolder.saveUser(userDTO);
        // 7.刷新token有效期
        stringRedisTemplate.expire(key, LOGIN_USER_TTL, TimeUnit.MINUTES);
        // 8.放行
        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        // 移除用户
        UserHolder.removeUser();
    }
}

这里因为我们设置的token有过期删除时间,所以我们每次操作经过拦截器的时候,都需要刷新过期时间。然后因为有的接口因没有使用拦截器但是我们也需要刷新过期时间,所以建议把更新过期时间和拦截未登录用户写成两个拦截器。但是这里的代码是写在一起的,可以自己改一下。


网站公告

今日签到

点亮在社区的每一天
去签到