最近在调“黑马点评”项目的时候不少同学都卡在同一个地方明明验证码校验通过了Redis 里也存了用户信息偏偏登录成功后页面不跳转到主页。要么一直在加载要么跳到 404要么直接被拦截器打回登录页。这个“黑马点评登录后跳转主页问题”看着不大背后却牵扯到一整条登录链路前端怎么存 token、请求头怎么携带、后端拦截器怎么放行、跳转路径是否写对。而且它特别容易成为面试官追问的点简历里写了登录模块手上却说不清楚跳转链路在现场会很尴尬。这篇文章我不会只给结论会把从登录接口返回数据到前端收到结果后决定往哪儿跳再到后端主页接口被校验和放行整个过程拆开讲。同时把我实测遇到过的几种典型症状、排查顺序和修正代码都记录下来正在做黑马点评项目、准备把登录模块写进简历或者刚学完 Spring Boot 拦截器、Redis 想练手的朋友可以直接照着排查。1. 黑马点评登录链路与跳转逻辑拆解1.1 短信登录流程里为什么会有“跳转主页”这一步黑马点评的登录本质是“短信验证码登录”和传统的账号密码登录在跳转问题上没本质区别但多了一层 Redis 存储和随机 token 的生成逻辑。整体流程是这样用户打开登录页输入手机号点击获取验证码。后端生成随机验证码以手机号为 key 存入 Redis并设置过期时间。用户输入验证码再次提交到登录接口。后端校验验证码是否正确校验通过后生成一个随机的 token一般用 UUID。后端把用户信息以 token 为 key 写入 Redis。前端拿到 token 和用户信息把 token 存到 localStorage 或者请求头上。前端根据业务需要跳转到项目主页。问题几乎都出在第 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 重定向次数过多登录页、主页、拦截器三方打架浏览器报“重定向次数过多”是特别崩溃的问题。典型的死循环是这样用户访问/被拦截器判断未登录重定向到/login.html。用户在登录页登录成功前端跳转到/index.html。主页接口/user/info又因为某种原因没通过校验后端把这个请求重定向到/login.html。登录逻辑又判断已经登录再跳回主页。这个循环的本质是“判断登录态的标准”前后端不一致。后端主页接口认为你有 token 就是登录但静态资源页面或某个接口又要求走 Session或者前端跳转后的地址正好没被拦截器放行。排查时最简单的办法是把浏览器 Network 面板里的请求记录按顺序看找到循环的重定向链。能看到从哪一步开始又跳回登录页基本就能确定是哪个接口校验不通过。2.7 浏览器缓存和旧 JS 导致跳转后状态错乱这类问题最迷惑。代码最新版本已经改成跳转/home.html了但本地预览还是跳到/index.html或者跳过去之后调用的接口地址还是旧地址。这大概率是浏览器缓存了旧的 JS 文件。黑马点评是静态页面项目没有复杂的前端打包流程很多同学直接用 vscode 的 Live Server 打开浏览器会缓存 JS。登录后跳转看起来一切正常可页面上还是显示很久以前的逻辑甚至会因为旧代码访问旧接口而报错。实操时建议每次改完前端 JS打开开发者工具勾选 Network 面板里的“Disable cache”再硬刷新一次Mac 快捷键是 Cmd Shift RWindows 是 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 几个我被问到最多的点黑马点评项目面试里经常被追问“登录后跳转主页它内部经历了什么”这个问题比单纯的代码实现更考验理解。一个合格回答思路是前端提交手机号验证码到/user/login。后端校验验证码成功后生成 UUID 作为 token用户信息序列化后以login:token:{token}为 key 存入 Redis。后端把 token 返回给前端。前端将 token 保存到 localStorage并跳转到index.html。后续浏览器向主页数据接口发起请求时请求头携带 token。后端拦截器从请求头取出 token再到 Redis 查用户信息查询成功就把用户信息放行到 Controller 层查询失败则返回 401。主页拿到数据以后渲染页面整个跳转流程完成。把这个流程解释清楚比单纯背代码强很多。它能证明你真的理解登录态是如何跨页面保持的也解释了为什么跳转主页的时候不能只改一个 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 续期、拦截器白名单这些点一并想清楚这个项目才能真正写进简历里撑住场面。