N维世界 2025-08-04 16:32 采纳率: 90%
浏览 9
已结题

黑马点评redis实现登录功能的疑惑

黑马点评——用redis存储code、token实现登录过程的bug
在UserServiceImpl中我已经把token返回,但是在登录后还是要重新登录,发现拦截器获取到的authorization值是[object Object]
代码放下面

@Service
@Slf4j
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
    @Autowired
    private UserMapper userMapper;
    @Resource
    private StringRedisTemplate stringRedisTemplate;

    /*
    * 发送验证码
    * */
    @Override
    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);

        //保存到session
        //session.setAttribute("code",code);

        //发送验证码
        log.info("发送验证码成功:{}",code);
        //ok
        return Result.ok();
    }

    /*
    * 校验验证码,用户信息
    * */
    @Override
    public Result login(LoginFormDTO loginForm, HttpSession session) {
        String phone = loginForm.getPhone();
        Object cacheCode = stringRedisTemplate.opsForValue().get(RedisConstants.LOGIN_CODE_KEY + phone);
        String code = loginForm.getCode();
        //校验手机号
        if(RegexUtils.isPhoneInvalid(phone)){
            return Result.fail("手机号格式错误");
        }
        //校验验证码
        if(cacheCode == null || !cacheCode.toString().equals(code)){
            return Result.fail("验证码错误");
        }
        //根据手机号查询用户
        User user = query().eq("phone",phone).one();
        if (user == null) {
            user = createNewUser(phone);
        }
        UserDTO userDTO = new UserDTO();
        BeanUtils.copyProperties(user,userDTO);
        //随机生成token
        String token = UUID.randomUUID().toString();
        //将userdto 存储到redis中
        Map<String,Object> userMap = BeanUtil.beanToMap(userDTO,new HashMap<>(),
                CopyOptions.create()
                        .setIgnoreNullValue(true)
                        .setFieldValueEditor((fieldName,fieldValue) -> fieldValue.toString())
                );
        //存到redis中
        String tokenKey = RedisConstants.LOGIN_USER_KEY + token;
        stringRedisTemplate.opsForHash().putAll(tokenKey,userMap);
        //设置token有效期
        stringRedisTemplate.expire(tokenKey,RedisConstants.LOGIN_USER_TTL,TimeUnit.MINUTES);
        log.info("token:{}",token);
        //session.setAttribute("user",userDTO);
        return Result.ok(token);
    }

    private User createNewUser(String phone) {
        User user = new User();
        user.setPhone(phone);
        user.setNickName("user_" + RandomUtil.randomString(10));
        save(user);
        log.info("创建新用户成功:{}",user);
        return user;
    }
}

拦截器

public class LoginInterceptor implements HandlerInterceptor {

    private static final Logger log = LoggerFactory.getLogger(LoginInterceptor.class);
    private StringRedisTemplate stringRedisTemplate;

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

    /*@Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //获取session
        HttpSession session = request.getSession();

        //获取用户
        Object user = session.getAttribute("user");
        //判断用户是否存在
        if (user == null){
            response.setStatus(401);
            return false;
        }
        //存在,保存用户信息到threadlocal
        UserHolder.saveUser((UserDTO) user);
        //放行
        return true;
    }
*/

    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //获取token
        String token = request.getHeader("authorization");
        // 去除 Bearer 前缀(如果存在)
        if (token.startsWith("Bearer ")) {
            token = token.substring(7);
        }

        String tokenKey = RedisConstants.LOGIN_USER_KEY + token;
        log.info("token:{}",token);
        log.info("tokenkey:{}",tokenKey);

        if (StrUtil.isBlank(token)){
            response.setStatus(401);
            return false;
        }


        Map<Object,Object> userMap =  stringRedisTemplate.opsForHash().entries(tokenKey);
        if (userMap.isEmpty()){
            response.setStatus(401);
            return false;
        }
        //将查询到的结果转换为dto对象
        UserDTO userDTO = new UserDTO();
        BeanUtil.fillBeanWithMap(userMap,userDTO,false);

        //保存用户信息到threadlocal
        UserHolder.saveUser(userDTO);

        //刷新token有效期
        stringRedisTemplate.expire(tokenKey, RedisConstants.LOGIN_USER_TTL, TimeUnit.MINUTES);

        //返回true
        return true;
    }


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

注册拦截器

@Configuration
public class MvcConfig implements WebMvcConfigurer {

    @Resource
    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"
                );
    }
}

controller

@PostMapping("/login")
    public Result login(@RequestBody LoginFormDTO loginForm, HttpSession session){
        // TODO 实现登录功能
        return Result.ok(userService.login(loginForm,session));
    }

  • 写回答

5条回答 默认 最新

  • 一杯年华@编程空间 2025-08-04 22:08
    关注

    一杯年华@编程空间帮您解答,本答案结合 ChatGPT4.0和DeepSeek整理
    我曾经遇到过类似的问题,当时也是登录后频繁需要重新登录,排查后发现核心问题出在 token 的传递和获取环节。结合你的情况,我来分析下:

    从现象来看,拦截器获取到的 authorization 值是 [object Object],这说明前端传递到后端的并不是有效的 token 字符串,更可能是一个对象。这通常是因为前端没有正确提取后端返回的 token,而是直接把整个响应对象当作 token 放到了请求头里,导致后端拿到的是对象的默认字符串形式。

    接下来给你两种解决方案:

    第一种是检查前端对登录响应的处理。后端登录接口返回的是包含 token 的结果,前端需要从这个结果中正确提取出 token 字符串,而不是直接使用整个响应对象。比如,假设后端返回的结构是 {code:200, data:"token值"},前端就需要获取 data 字段的值作为 token。

    第二种是规范请求头的格式。即使前端正确提取了 token,也需要按照约定的格式传递,比如在 token 前加上 "Bearer " 前缀(就像你拦截器里处理的那样)。如果前端漏掉了这个前缀,或者格式错误,也可能导致 token 验证失败。

    其中最优的方案是先排查前端对登录响应的处理。因为 [object Object] 明显是对象的特征,这说明前端极有可能没有从后端返回的结果中提取出真正的 token 字符串,而是把整个响应对象直接放到了 authorization 头里。你可以让前端同学检查下:登录成功后,是否正确拿到了后端返回的 token 值,而不是整个 Result 对象。只要前端能正确提取 token 并按格式传递,拦截器就能获取到有效的 token,登录状态就不会丢失了。

    希望这些分析能帮到你,楼主采纳哦。如有问题请继续留言。

    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论
查看更多回答(4条)

报告相同问题?

问题事件

  • 已结题 (查看结题原因) 8月8日
  • 已采纳回答 8月8日
  • 修改了问题 8月4日
  • 创建了问题 8月4日