【Redis】基于Redis实现共享Session登录

本文详细描述了如何使用Redis实现用户登录过程,包括短信验证码验证、用户信息存储、以及基于SpringMVC的拦截器确保登录状态和token的有效性。
摘要由CSDN通过智能技术生成

 用户登录是一种常用功能。这里记录一下基于Redis实现用户登录的代码。
 下面是登录的流程图:

  • 用户先提交手机号和验证码,服务器以手机号为key校验redis中存储的验证码,存在,则查询数据库中是否存在用户,不存在则创建并将token作为key,用户信息作为value保存在Redis中。
  • 用户登录成功后的请求需要在session中携带token,来进行身份的鉴权。
    在这里插入图片描述

1.用户短信验证码登录、注册代码

  • Controller层
	/**
     * 登录功能
     * @param loginForm 登录参数,包含手机号、验证码;或者手机号、密码
     */
    @PostMapping("/login")
    public Result login(@RequestBody LoginFormDTO loginForm, HttpSession session){
        // TODO 实现登录功能
        return userService.login(loginForm, session) ;
    }
  • Service层
	@Override
    public Result login(LoginFormDTO loginForm, HttpSession session) {
        String phone = loginForm.getPhone();
        // 1. 校验手机号
        if(RegexUtils.isPhoneInvalid(phone)){
            return Result.fail("手机号格式不正确");
        }

        //  2. 从Redis获取验证码
        String cacheCode = stringRedisTemplate.opsForValue().get(LOGIN_CODE_KEY + phone);
        String code = loginForm.getCode();
        if(cacheCode == null || !cacheCode.equals(code)){
            // 3. 不一致报错
            return Result.fail("验证码错误");
        }



        // 4. 一致根据手机号查用户
        User user = query().eq("phone", phone).one();
        if(user == null){
            // 5. 用户不存在
            user = createUserWithPhone(phone);
            // 6. 不存在创建用户

        }


        // 7. 保存用户信息到session
        // 7.1 生成token
        String token = UUID.randomUUID().toString(true);
        // 7.2 保存到redis,hash存储,使用Map减少与服务器交互次数
        UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class);
        // 将所有字段转为string
        Map<String, Object> userMap = BeanUtil.beanToMap(userDTO, new HashMap<>(),
                CopyOptions.create().setIgnoreNullValue(true).setFieldValueEditor((fileName,fileValue)->fileValue.toString()));
        // 7.3 存储
        stringRedisTemplate.opsForHash().putAll(LOGIN_USER_KEY+token,userMap);
        // 7.4 设置过期时间
        stringRedisTemplate.expire(LOGIN_USER_KEY+token, LOGIN_USER_TTL, TimeUnit.SECONDS);
        // 返回token
        System.out.println(token);
        return Result.ok(token);
    }

2.校验登录状态代码

 这段功能基于拦截器实现。考虑到用户的任何请求都可以刷新token的有效期,如下图所示,这部分由两个拦截器组成,第一个拦截器拦截所有请求,但都会放行,唯一的作用的token存在,刷新其在Redis中的有效期。第二个拦截没有token的请求。
在这里插入图片描述

  • 拦截器配置
@Configuration
public class MvcConfig implements WebMvcConfigurer {

    @Resource
    StringRedisTemplate stringRedisTemplate;
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new RefreshTokenInterceptor(stringRedisTemplate)).addPathPatterns("/**").order(0);
        registry.addInterceptor(new LoginInterceptor()).addPathPatterns("/**").excludePathPatterns(
                "/user/code",
                "/user/login",
                "/user/logout",
                "/shop/**",
                "/voucher/**",
                "/shop-type/**",
                "/upload/**",
                "/blog/hot"
        ).order(1);

    }
}

  • 第一个拦截器
public class RefreshTokenInterceptor implements HandlerInterceptor {

    // 没有注册为Bean,只能手动引入
    private StringRedisTemplate stringRedisTemplate;

    public RefreshTokenInterceptor(StringRedisTemplate redisTemplate) {
        this.stringRedisTemplate = redisTemplate;
    }
    @Override
    public boolean preHandle(javax.servlet.http.HttpServletRequest request, javax.servlet.http.HttpServletResponse response, Object handler) throws Exception {
        // 1. 获取请求头的token
        String token = request.getHeader("authorization");
        if (StrUtil.isBlank(token)) {
            return true;
        }

        // 2. 基于token获取用户
        Map<Object, Object> userMap = stringRedisTemplate.opsForHash().entries(RedisConstants.LOGIN_USER_KEY + token);

        // 3. 用户是否存在
        if(userMap.isEmpty()){
            // 4. 放行
            return true;
        }
        // 5. 转为UserDTO
        UserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);
        // 6. 保存用户信息到ThreadLocal
        UserHolder.saveUser(userDTO);
        // 刷新有效期
        stringRedisTemplate.expire(RedisConstants.LOGIN_USER_KEY+token, RedisConstants.LOGIN_USER_TTL, TimeUnit.MINUTES);
        // 7. 放行
        return true;
    }

    @Override
    public void afterCompletion(javax.servlet.http.HttpServletRequest request, javax.servlet.http.HttpServletResponse response, Object handler, Exception ex) throws Exception {
        // 4. 清理资源
        UserHolder.removeUser();
    }
}

  • 第二个拦截器
public class LoginInterceptor implements HandlerInterceptor {


    @Override
    public boolean preHandle(javax.servlet.http.HttpServletRequest request, javax.servlet.http.HttpServletResponse response, Object handler) throws Exception {
        // 1. 拦截
        if(UserHolder.getUser() == null){
            response.setStatus(401);
            return false;
        }
        // 7. 已登录,放行
        return true;
    }

    @Override
    public void afterCompletion(javax.servlet.http.HttpServletRequest request, javax.servlet.http.HttpServletResponse response, Object handler, Exception ex) throws Exception {
        // 4. 清理资源
        UserHolder.removeUser();
    }
}

  • 其中UserHolder为:
public class UserHolder {
    private static final ThreadLocal<UserDTO> 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();
    }
}
  • 11
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

CRE_MO

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值