☰
黑马点评登录后跳转主页失败?一文拆解登录链路与拦截器排查策略
2026/9/28 15:15:53 网站建设 项目流程

最近在调“黑马点评”项目的时候,不少同学都卡在同一个地方:明明验证码校验通过了,Redis 里也存了用户信息,偏偏登录成功后页面不跳转到主页。要么一直在加载,要么跳到 404,要么直接被拦截器打回登录页。这个“黑马点评登录后跳转主页问题”看着不大,背后却牵扯到一整条登录链路:前端怎么存 token、请求头怎么携带、后端拦截器怎么放行、跳转路径是否写对。而且它特别容易成为面试官追问的点,简历里写了登录模块,手上却说不清楚跳转链路在现场会很尴尬。

这篇文章我不会只给结论,会把从登录接口返回数据,到前端收到结果后决定往哪儿跳,再到后端主页接口被校验和放行,整个过程拆开讲。同时把我实测遇到过的几种典型症状、排查顺序和修正代码都记录下来,正在做黑马点评项目、准备把登录模块写进简历,或者刚学完 Spring Boot 拦截器、Redis 想练手的朋友,可以直接照着排查。

1. 黑马点评登录链路与跳转逻辑拆解

1.1 短信登录流程里,为什么会有“跳转主页”这一步

黑马点评的登录本质是“短信验证码登录”,和传统的账号密码登录在跳转问题上没本质区别,但多了一层 Redis 存储和随机 token 的生成逻辑。整体流程是这样:

  1. 用户打开登录页,输入手机号,点击获取验证码。
  2. 后端生成随机验证码,以手机号为 key 存入 Redis,并设置过期时间。
  3. 用户输入验证码,再次提交到登录接口。
  4. 后端校验验证码是否正确,校验通过后,生成一个随机的 token(一般用 UUID)。
  5. 后端把用户信息以 token 为 key 写入 Redis。
  6. 前端拿到 token 和用户信息,把 token 存到 localStorage 或者请求头上。
  7. 前端根据业务需要,跳转到项目主页。

问题几乎都出在第 6 步和第 7 步之间。前端拿到登录成功的响应后,需要自己决定跳转地址;而跳转完成之后,主页页面要发起的各种查询请求又必须依赖 token。也就是说,“跳转主页”不只是改一行window.location.href那么简单,它要同时保证前端路由地址、后端拦截器路径、token 存储位置三者一致,才能顺利打开主页。

注意:黑马点评的前端页面是独立部署的静态页面,不是典型意义上的前后端分离 SPA 应用,但它通过 HTTP 接口和后端交互,登录态的传递方式完全是前后端分离风格。

1.2 “主页”到底由谁来跳,先分清三种形态

很多同学一上来就改代码,却没说清楚项目里的“跳转”发生在哪一层。我至少见过三种情况:

  • 第一种,前端页面通过location.href或window.location.href跳转。这是黑马点评静态页面最常用的方式。登录接口返回成功后,前端 JS 写一行跳转逻辑。
  • 第二种,后端接口返回redirect:前缀,由 Spring MVC 完成重定向。比如登录接口直接return "redirect:/index.html"。
  • 第三种,前端是 Vue/React 这类 SPA 应用,通过 vue-router 或 react-router 做路由跳转。黑马点评原始项目不是这种,但当你自己改造项目时可能引入这种写法。

这三种形态的排查方式完全不同。第一种问题通常出现在前端 cookie、localStorage 或请求头携带上;第二种问题出在后端返回路径和拦截器放行路径的匹配上;第三种问题往往出在前端路由守卫上,需要检查路由表里/路径对应的组件和后端主页接口是否正常。

所以碰到“登录后跳转主页问题”,第一件事不是改代码,而是去项目源码里搜一下redirect和location.href,看清楚当前项目的登录成功跳转到底写在哪里。这决定了你接下来要看前端代码还是后端代码。

2. 登录后跳转主页失败的七种典型原因

2.1 拦截器未放行主页接口,登录后又被“送回”登录页

黑马点评项目里通常会写一个拦截器LoginInterceptor,在preHandle方法里检查当前请求是否携带有效 token。拦截器一般会放行/user/login、/user/code这类登录相关接口,但主页接口默认不放行,因为主页需要登录用户信息。

如果你的拦截器放行路径写的是"/**",然后自己写逻辑判断请求 URI 是否等于/user/login,那大概率会出现一个经典现象:登录接口能成功,跳转到主页后,主页发起的每个请求都会被拦截器判定为未登录,然后重定向回登录页。

典型的拦截器代码如下:

@Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { HttpSession session = request.getSession(); Object user = session.getAttribute("user"); if (user == null) { response.setStatus(401); return false; } return true; }

这段代码的问题暴露得很明显:它把登录态完全放在 Session 里,而黑马点评登录跳转时,前后端是通过 token + Redis 来通信的。如果前端没有把 token 放到 cookie 或者请求头里,后端的 Session 永远拿不到用户对象。所以我遇到这类问题时,会先检查项目用的登录态方案到底是 Session 还是 Redis token。

如果是 Redis token 方案,拦截器逻辑应该是这样:

String token = request.getHeader("authorization"); if (token == null) { response.setStatus(401); return false; } Object user = stringRedisTemplate.opsForValue().get("login:token:" + token); if (user == null) { response.setStatus(401); return false; }

同时还要确认拦截器注册时是否对/index.html、/、/home等路径做了处理。如果是全量拦截,那必须在前端跳转之前就保证主页接口能携带请求头,或者把主页本身的静态资源路径配置成放行。

2.2 前端跳转地址拼接出现 undefined 或双斜杠

这个现象在热词里也出现过,跳转地址变成了/homeundefined或/index.htmlundefined。原因通常是前端代码用了字符串拼接,而其中一个变量没取到值:

axios.post('/user/login', params).then(res => { let redirectUrl = res.data.data.redirectUrl; window.location.href = '/' + redirectUrl; });

如果res.data.data.redirectUrl不存在,redirectUrl是 undefined,跳转地址就成了/undefined。或者地址本身就是/index.html,但代码在前面又加了一个/,变成//index.html,少数 Web 容器会把双斜杠解析成协议相对地址,表现非常诡异。

排查这类问题最快的方式:登录成功之后,先console.log打印接口返回的完整对象,再打印拼接后的跳转地址。看到 undefined 立刻就能定位。不要直接location.href = res.data.data.url加/,而是先判断字段是否存在:

let redirectUrl = res.data.data && res.data.data.redirectUrl; window.location.href = redirectUrl || '/index.html';

2.3 请求头里没带 Token,主页接口把跳转后的请求当游客处理

我在这个项目上踩过最典型的坑是:登录接口明明把 token 存进了 localStorage,但跳转到主页后所有的请求都没有携带这个 token。原因非常简单,前端用 axios 请求主页数据时,没有配置统一的请求拦截器来加请求头。

黑马点评原始项目里,前端会有一段类似于下面这样的代码:

axios.interceptors.request.use(config => { config.headers.Authorization = localStorage.getItem('token'); return config; });

如果这段代码漏掉了,或者写在登录页自己的 JS 文件里,而主页的 JS 是另一个文件,那主页请求自然不带 token,后端就把主页请求识别为未登录用户。表现就是你在主页看到登录框一闪而过,然后整个页面的数据都是空的。

这也是为什么我一直强调,排查跳转问题之前先看 Network 面板里“主页请求的请求头”。如果请求头里连authorization都没有,那就别怀疑后端拦截器了,先补前端拦截器。

2.4 后端 Token 解析失败,主页接口拿到不到用户信息

如果请求头带上了 token,但后端在 Redis 里查不到,一样会跳回登录页。可能原因有两个:

第一,Redis 过期时间太短。黑马点评的登录 token 常常被设置成 30 分钟有效期。你在本地慢慢调页面、刷新、看源码,超过 30 分钟后 token 就失效了。跳转主页时主页接口再拿这个 token 去查 Redis,查不到,就报未登录。

第二,Redis 里存的 key 和前端传来的 token 不一致。常见于经常用StringRedisTemplate存储时,key 写成了"login:token:" + token,而读的时候写成了token,或者反过来。这种不一致只靠肉眼很难发现,要加日志打印出来对比。

我在实际操作中会写一行日志:

log.debug("校验 token:{},Redis key:{}", token, "login:token:" + token);

然后在后端日志里确认实际拼接后的 key 究竟长什么样。这比在代码里断点还方便。

2.5 后端重定向 URL 写错,导致主页 404

如果登录接口不是返回 JSON,而是后端直接重定向,容易遇到 404。常见写法:

return "redirect:index.html";

这种相对路径的重定向有个隐患:它会相对于当前请求的路径来解析。假如登录请求路径是/user/login,重定向解析出来的地址可能是/user/index.html,而这个地址根本不存在,于是主页 404。

稳妥的写法是写绝对路径:

return "redirect:/index.html";

这样无论在/user/login还是/api/login下,都会重定向到根路径下的index.html。

同样,使用HttpServletResponse.sendRedirect时也要注意:

response.sendRedirect(request.getContextPath() + "/index.html");

如果项目部署在 Tomcat 根路径,request.getContextPath()是空字符串,拼接后没问题;如果项目有上下文路径,不拼接就要踩坑。

2.6 重定向次数过多:登录页、主页、拦截器三方打架

浏览器报“重定向次数过多”是特别崩溃的问题。典型的死循环是这样:

  1. 用户访问/,被拦截器判断未登录,重定向到/login.html。
  2. 用户在登录页登录成功,前端跳转到/index.html。
  3. 主页接口/user/info又因为某种原因没通过校验,后端把这个请求重定向到/login.html。
  4. 登录逻辑又判断已经登录,再跳回主页。

这个循环的本质是“判断登录态的标准”前后端不一致。后端主页接口认为你有 token 就是登录,但静态资源页面或某个接口又要求走 Session;或者前端跳转后的地址正好没被拦截器放行。

排查时最简单的办法是把浏览器 Network 面板里的请求记录按顺序看,找到循环的重定向链。能看到从哪一步开始又跳回登录页,基本就能确定是哪个接口校验不通过。

2.7 浏览器缓存和旧 JS 导致跳转后状态错乱

这类问题最迷惑。代码最新版本已经改成跳转/home.html了,但本地预览还是跳到/index.html,或者跳过去之后调用的接口地址还是旧地址。这大概率是浏览器缓存了旧的 JS 文件。

黑马点评是静态页面项目,没有复杂的前端打包流程,很多同学直接用 vscode 的 Live Server 打开,浏览器会缓存 JS。登录后跳转看起来一切正常,可页面上还是显示很久以前的逻辑,甚至会因为旧代码访问旧接口而报错。

实操时建议每次改完前端 JS,打开开发者工具,勾选 Network 面板里的“Disable cache”,再硬刷新一次(Mac 快捷键是 Cmd + Shift + R,Windows 是 Ctrl + Shift + R)。如果项目上线了,最好给静态资源加上版本号或者协商缓存,否则用户手机里的旧页面会一直触发跳转问题。

3. 从登录到主页的一步步排查流程

3.1 先复现一次,并打开 Network 面板记录线索

遇到这个问题不要急着改代码。先把项目启动起来,浏览器打开登录页,登录一次,在操作的同时观察 Network 面板的请求列表。重点关注三条请求:

  • 第一次:POST /user/login,看响应状态和响应体。
  • 第二次:跳转后的GET /index.html,看是不是 200,以及加载的 JS/CSS 是否完整。
  • 第三次:主页数据接口(比如/blog/hot),看请求头和响应状态。

我的经验是,90% 的跳转问题在看完这三条请求之后,原因就浮出水面了。如果登录接口本身返回 500,那跳转问题就是登录失败,先修登录。如果登录接口正常,但主页数据接口 401,那就是前端 token 没传或者后端拦截器校验失败。如果主页接口 200 但页面空白,那就是前端 JS 渲染逻辑问题,跟跳转关系不大。

3.2 看登录接口的响应体,确认有没有拿到 token

先看POST /user/login的响应结构。正常应该是:

{ "success": true, "data": { "token": "a1b2c3d4...", "user": { "id": 1, "name": "张三" } } }

如果 token 缺失,那前端根本没法在后续请求里携带,跳转过去也白搭。常见原因是后端登录接口返回的对象里,字段名跟前端代码期待的不一致。前端死等result.data.token,后端返回的却是result.data.accessToken,一取就是 undefined,跳转地址就变成/undefined。

我建议统一字段名。前端和后端约定好登录接口返回体格式,固定是{ token, user },后端哪怕改造也不能随意改成{}。有条件的在控制器层做 DTO,别直接返回 Map。

3.3 看主页请求的请求头和响应码定位卡点

这一步最关键。登录成功后,不要急着看页面,直接去 Network 面板点击主页数据接口,查看请求详情:

  • 如果请求头里没有authorization,问题在前端统一请求封装。
  • 如果请求头里有 token,但响应是 401,问题在后端拦截器解析。
  • 如果响应是 200,但数据为空,问题在业务逻辑或 SQL。
  • 如果响应是 302,就要看 Location 头指向哪里,多半是拦截器在重定向到登录页。

我看过太多同学卡在“跳转主页后页面一直转圈”,最后发现是主页接口返回了 401,但前端没有全局处理 401 的逻辑,导致页面卡死。正确做法是在前端 axios 响应拦截器里统一处理:

axios.interceptors.response.use(res => res, err => { if (err.response && err.response.status === 401) { localStorage.removeItem('token'); window.location.href = '/login.html'; } return Promise.reject(err); });

这样即使 token 失效也能看到明显的跳回登录页,而不是卡在原地。

3.4 动手修改代码,从后端拦截器到前端跳转逐一验证

这里我整理一套我实测过可用的修正组合,可以直接抄。

后端拦截器注册类:

@Configuration public class WebConfig implements WebMvcConfigurer { @Resource private LoginInterceptor loginInterceptor; @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(loginInterceptor) .addPathPatterns("/**") .excludePathPatterns( "/user/login", "/user/code", "/blog/hot", "/shop/**", "/shop-type/**", "/index.html", "/login.html", "/js/**", "/css/**", "/img/**" ); } }

前端登录成功跳转代码:

async function login(phone, code) { const { data: res } = await axios.post('/user/login', { phone, code }); if (res.success) { localStorage.setItem('token', res.data.token); window.location.href = '/index.html'; } }

前端请求封装:

axios.interceptors.request.use(config => { const token = localStorage.getItem('token'); if (token) { config.headers.Authorization = token; } return config; });

后端拦截器校验:

@Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String token = request.getHeader("Authorization"); if (StrUtil.isBlank(token)) { response.setStatus(401); return false; } String key = RedisConstants.LOGIN_USER_KEY + token; String userJson = stringRedisTemplate.opsForValue().get(key); if (StrUtil.isBlank(userJson)) { response.setStatus(401); return false; } UserDTO user = JSONUtil.toBean(userJson, UserDTO.class); UserHolder.saveUser(user); return true; }

改完之后,重新启动项目,再重复一次登录跳转。正常情况下可以看到主页接口的请求头携带了 token,后端也校验通过,页面顺利加载。

3.5 部署到 Linux 环境后容易出现的额外差异

本地运行好好的,一部署到服务器就跳转失败,差别通常有三个:

第一,上下文路径不同。本地 IDEA 访问是http://localhost:8080/index.html,服务器如果通过 Nginx 转发,目录结构可能多了一层webapps/项目名。所有redirect路径都要用request.getContextPath()拼接,避免 404。

第二,静态资源路径大小写问题。Linux 文件系统对大小写敏感,本地 Windows 不敏感。如果本地写Index.html能打开,部署到 Linux 上跳转index.html就 404,就是因为文件名首字母的大小写不一致。

第三,Redis 环境变量和端口没对齐。服务器上 Redis 与本地配置不一致,代码里写的localhost根本连不上,token 存不进 Redis,登录接口虽然返回成功,但后续校验必然失败,跳转后照样被弹回登录页。这种问题单看前端跳转代码永远查不出来,必须看后端启动日志和 Redis 连接状态。

4. 问题速查表与独门避坑心得

4.1 常见症状、原因与优先处理手段

把遇到过的跳转问题整理成一个速查表,方便后面遇到类似情况直接对照,效率高很多。

症状可能原因优先检查和处理
登录成功后跳到/undefined前端响应体字段名拼错或返回结构不符先打印登录接口完整返回体,再检查res.data.token
登录成功后一直跳回登录页拦截器未放行主页接口或 token 校验失败看主页数据接口的请求头和响应码,确认 token 是否被拦截器识别
主页接口 401前端请求未携带 token 或 Redis 中 token 已过期检查 axios 统一请求拦截器,检查 Redis 中 key 是否存在
主页接口 200 但页面空白前端渲染数据逻辑错误,或 JS 文件加载 404看 Console 面板 JS 报错,检查 JS 引用路径
重定向次数过多登录态判断标准前后端不一致在 Network 面板里找到循环起点,比对该接口的拦截关系
跳转后主页样式丢失index.html 引用的 CSS、JS 路径是以相对路径写的改成绝对路径,或者通过 CDN/服务器静态资源映射解决
本地能跳,服务器上跳到 404上下文路径、大小写、静态资源目录问题检查request.getContextPath(),核对 Linux 文件大小写
登录接口返回成功但无 token后端返回体字段命名不一致统一{ success, data }结构,字段名前后端一起定

4.2 排障时值得养成的几个好习惯

第一,先看日志,别先下结论。在后端控制器和拦截器里加日志,特别是打印收到的 token、拼接的 Redis key、是否放行。用log.info打印关键路径,能省去很多盲测时间。

第二,前端别用window.location.href裸跳就完事,要考虑 token 为空时的兜底。比如没有 token 跳去登录页,有 token 但跳转 404 时留在当前页报错,避免用户白屏。

第三,验证码接口和登录接口的接口路径必须配在拦截器放行列表里,但权限校验不能漏。很多同学为了省事把/user/**全部放行,导致别人可以绕过登录直接访问用户信息接口,这是特别典型的接口安全问题。跳转主页这个功能虽然能解决,但面试官下一句问“你拦截器放行了哪个路径路径,安全怎么保证”,答不上来就容易掉分。正确做法是只放行真正不需要登录的接口,比如验证码接口、登录接口、热门店铺列表等公开数据接口。

4.3 几个我被问到最多的点

黑马点评项目面试里经常被追问“登录后跳转主页它内部经历了什么”,这个问题比单纯的代码实现更考验理解。

一个合格回答思路是:

  1. 前端提交手机号验证码到/user/login。
  2. 后端校验验证码,成功后生成 UUID 作为 token,用户信息序列化后以login:token:{token}为 key 存入 Redis。
  3. 后端把 token 返回给前端。
  4. 前端将 token 保存到 localStorage,并跳转到index.html。
  5. 后续浏览器向主页数据接口发起请求时,请求头携带 token。
  6. 后端拦截器从请求头取出 token,再到 Redis 查用户信息,查询成功就把用户信息放行到 Controller 层,查询失败则返回 401。
  7. 主页拿到数据以后渲染页面,整个跳转流程完成。

把这个流程解释清楚,比单纯背代码强很多。它能证明你真的理解登录态是如何跨页面保持的,也解释了为什么跳转主页的时候不能只改一个 URL。

4.4 再分享一个小工具

如果你反复调试跳转问题,建议在浏览器控制台里封装一个小工具函数,专门用来查看登录态和跳转环境:

function debugLogin() { console.log('当前路径:', location.href); console.log('token:', localStorage.getItem('token')); console.log('用户信息:', localStorage.getItem('user')); }

登录成功、跳转后、报错时分别执行一次,就能很清楚地看到 token 是什么时候丢的,是登录时没存,还是跳转后被清掉了。很多时候问题不是出在某一段代码里,而是出在事件顺序上,这时候一个全局调试函数比断点还好用。

我个人在实际排障中的体会是,跳转问题十有八九不是“跳转”本身写错了,而是登录态链条里某一环没对上。只要把前端存 token、请求头带 token、后端校验 token、拦截器放行路径这四件事从头到尾捋一遍,绝大多数问题都能在 15 分钟内解决。如果你正在做黑马点评项目,也别只把登录跳通就完事,顺手把 Redis 过期时间、token 续期、拦截器白名单这些点一并想清楚,这个项目才能真正写进简历里撑住场面。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询