redis实战-实现登录校验

基于Session实现登录、校验的流程以及存在的问题
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
这种使用session在单体tomcat服务器时没有问题,当在tomcat集群时,tomcat的session存储空间不是共享的,当在集群下,某台tomcat服务器的session存储了用户信息时,当用户再次发起请求,负载均衡将请求映射到其他tomcat时,此时的tomcat的session没有用户的信息,导致用户需要再次登录。所以,基于这种弊端,使用redis缓存用户信息。
提示:使用tomcat服务器session存储信息时,尽量存储少量信息,存储信息较大时会影响tomcat的性能。
这种使用session在单体tomcat服务器时没有问题,当在tomcat集群时,tomcat的session存储空间不是共享的,当在集群下,某台tomcat服务器的session存储了用户信息时,当用户再次发起请求,负载均衡将请求映射到其他tomcat时,此时的tomcat的session没有用户的信息,导致用户需要再次登录。所以,基于这种弊端,使用redis缓存用户信息。
下面介绍使用redis实现共享session登录校验。大体登录、校验流程不变,使用redis代替session。

在这里插入图片描述
发送验证码

 Result sendCode(String phone, HttpSession session);
 
 @Override
    public Result sendCode(String phone, HttpSession session) {
        if (RegexUtils.isPhoneInvalid(phone)){
            return Result.fail("手机号格式错误!");
        }
        String code = RandomUtil.randomNumbers(6);
        //session.setAttribute("code",code);
        redisTemplate.opsForValue().set(LOGIN_CODE_KEY +":" + phone,code,LOGIN_CODE_TTL, TimeUnit.MINUTES);

        logger.info("验证码为:" + code);
        return Result.ok();
    }
    
     @PostMapping("/code")
    public Result sendCode(@RequestParam("phone") String phone, HttpSession session) {
        //  发送短信验证码并保存验证码
        return userService.sendCode(phone,session);
    }

短信验证码登录、注册

Result login(LoginFormDTO loginForm, HttpSession session);

public Result login(LoginFormDTO loginForm, HttpSession session) {
        System.out.println("UserServiceImpl:" +session.toString());
        String phone = loginForm.getPhone();
        if (RegexUtils.isPhoneInvalid(phone)){
            return Result.fail("手机号格式错误!");
        }
        String code = loginForm.getCode();
        //String cacheCode = (String)session.getAttribute("code");
        String cacheCode = redisTemplate.opsForValue().get(LOGIN_CODE_KEY +":" + phone);
        if (cacheCode == null || !cacheCode.equals(code)){
            return Result.fail("验证码错误");
        }
        User user = query().eq("phone", phone).one();
        if (user == null){
            user = createUserWithPhone(phone);
        }
        //session.setAttribute("user",user);
        UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class);
       Map<String, Object> map = BeanUtil.beanToMap(userDTO,new HashMap<>(), CopyOptions.create().setIgnoreNullValue(true)
               .setFieldValueEditor((fieldName, fieldValue)->fieldValue.toString()));
        String token = UUID.randomUUID().toString(true);
        String tokenKey =  LOGIN_USER_KEY + ":"+ token ;;
        redisTemplate.opsForHash().putAll(tokenKey,map);
        redisTemplate.expire(tokenKey,LOGIN_USER_TTL,TimeUnit.MINUTES);
        return Result.ok(token);
    }
    
	@PostMapping("/login")
    public Result login(@RequestBody LoginFormDTO loginForm, HttpSession session){
        //  实现登录功能
        Result login = userService.login(loginForm, session);
        return login;
    }

在这里插入图片描述
项目中的拦截器

@Configuration
public class MvcConfig implements WebMvcConfigurer {

    @Autowired
    private StringRedisTemplate redisTemplate;
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new RefreshTokenIncepter(redisTemplate)).order(0);
        registry.addInterceptor(new LoginIncepter())
                .excludePathPatterns("/user/code","/user/login","/blog/hot","/shop/**",
                        "/shop-type/**","/upload/**","/voucher/**").order(1);
    }
}

拦截器代码:每次请求后会刷新redis中的token(只会刷新与用户信息相关操作的token,浏览店铺等与用户无关的token不会刷新)

@Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String token = request.getHeader("Authorization");
        if (StrUtil.isBlank(token)){
            return true;
        }
        String key = RedisContants.LOGIN_USER_KEY + ":" + token;
        Map<Object, Object> usermap = redisTemplate.opsForHash().entries(key);
        if (usermap == null){
            return true;
        }
        UserDTO userDTO = new UserDTO();
        BeanUtil.fillBeanWithMap(usermap,userDTO,false);
        UserHolder.saveUser(userDTO);
        //刷新token
        redisTemplate.expire(key,RedisContants.LOGIN_USER_TTL, TimeUnit.MINUTES);
        return true;
    }

可以看出,这些与用户信息无关的路径不需要拦截,那么拦截器拦截的路径都是与用户信息有关(与登录有关的路径)。当用户浏览店铺等与用户信息相关的路径时,达到缓存时间删除用户信息后,用户再次返回下单、加购等与用户相关信息时,缓存中已经没有用户信息,此时用户还需要登录,这显然不符合业务场景。

改进:双拦截器
在这里插入图片描述
第一个拦截器拦截所有请求,如果用户已经登录,则只要用户发起任何非logout操作,都会刷新token,这样可以解决上述情况的问题。如果用户没有登录,经过第一个拦截器则放行,经过第二个拦截器时发现用户要发起下单、加购等请求时,会拦截,如果发起浏览店铺等请求则放行。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值