SpringBoot+Vue腾讯云部署登录401排查实录
【摘要】SpringBoot+Vue项目部署腾讯云后出现登录失败问题,前端报401错误。经排查发现两大核心问题:1)Nginx代理路径配置错误,将/api/user/login转发为/user/login,导致JWT白名单校验失败;2)服务器数据库中的BCrypt密码密文与本地不一致。解决方案包括:修正Nginx配置保留/api前缀路径(proxy_pass http://127.0.0.1:12
Spring Boot + Vue
技术栈与工具准备
- Spring Boot 后端框架特性与优势
- Vue.js 前端框架特性与优势
- 腾讯云基础服务:CVM(云服务器)、轻量应用服务器、MySQL 数据库、Redis 等
- 辅助工具:Nginx、Docker、Jenkins(可选)
环境配置与项目打包
- JDK、Maven/Gradle 环境安装与配置
- Node.js 与 npm/yarn 环境安装
- Spring Boot 项目打包(JAR 或 WAR)
- Vue 项目打包(npm run build 生成静态文件)
腾讯云服务器初始化
- 购买与登录云服务器(CVM 或轻量应用服务器)
- 安全组配置(开放 80、443、后端服务端口)
- 服务器基础环境搭建(JDK、Nginx、数据库客户端等)
后端部署方案
- 上传 Spring Boot JAR 包至服务器
- 通过
nohup或systemd实现后台运行 - 配置 HTTPS(可选:腾讯云 SSL 证书申请与 Nginx 配置)
前端部署方案
- 上传 Vue 静态文件至服务器指定目录(如
/var/www/html) - 配置 Nginx 反向代理与静态资源托管
- 解决跨域问题(Nginx 代理或后端 CORS 配置)
数据库与缓存服务
- 腾讯云 MySQL 数据库创建与连接配置
- Redis 服务配置与 Spring Boot 集成
- 数据备份与迁移策略
自动化部署进阶(可选)
- Docker 容器化部署(编写 Dockerfile 与 docker-compose.yml)
- Jenkins 持续集成与部署(Git 钩子触发构建)
监控与运维
- 腾讯云云监控配置(CPU、内存、网络流量)
- 日志收集与分析(ELK 或腾讯云日志服务)
- 常见问题排查(端口占用、资源不足等)
安全优化建议
- 服务器防火墙规则细化
- 定期更新依赖库版本
- 敏感信息加密(JWT、数据库密码等)
部署腾讯云后登录失败:从 401 到 Nginx 代理路径再到 BCrypt 密码排查
一、问题现象
本地运行时,Spring Boot + Vue 项目可以正常登录。
但是部署到腾讯云服务器后,登录失败,前端提示:
{ "code": "401", "msg": "认证失败,请重新登录" }
浏览器开发者工具中看到请求:
POST /api/user/login
返回:
401 Unauthorized
一开始以为是密码错误,但后面发现问题并不只是密码,而是多个部署问题叠加导致。
二、最终结论
本次登录失败主要由两个问题造成:
问题 1:Nginx 代理路径错误 前端请求 /api/user/login 但 Nginx 转发后,后端实际收到 /user/login 导致 JWT 白名单不匹配,直接返回 401
问题 2:云服务器数据库中的 admin 密码和本地不一致 Nginx 修复后,请求能进入登录逻辑 但后端返回“用户名或密码错误” 说明服务器数据库里的密码密文和本地不同
最终正确处理方式:
-
Nginx 保留 /api 前缀转发到后端
-
确认后端实际端口是 1235
-
删除重复的 Nginx 默认配置
-
确认服务器数据库中的用户密码密文和本地一致
三、整体请求链路
部署后的请求链路应该是:
浏览器 | | 访问 http://服务器IP v Nginx 80 端口 | | Vue 静态页面 v 前端发起请求:/api/user/login | | Nginx 反向代理 v Spring Boot:127.0.0.1:1235/api/user/login | | 查询数据库 v MySQL:lost_found_db.user
正确情况下,后端应该收到:
/api/user/login
而不是:
/user/login
四、排查路线图
本次排查可以总结为下面这条路线:
前端提示 401 ↓ 浏览器 Network 查看登录接口 ↓ 服务器 curl 测试后端端口 ↓ 发现 8080 不通,1235 才是后端端口 ↓ 查看 jar 内 application.properties ↓ 确认 server.port=1235,数据库为 lost_found_db ↓ 测试 /user/login,返回 401 ↓ 反编译 JwtAuthenticationFilter ↓ 发现 JWT 白名单是 /api/user/login ↓ 发现 Nginx 把 /api/user/login 转成了 /user/login ↓ 修改 Nginx proxy_pass,保留 /api 前缀 ↓ 401 变成“用户名或密码错误” ↓ 说明请求已进入登录逻辑 ↓ 检查 MySQL user 表 ↓ 发现服务器数据库密码密文和本地不一致
五、第一层问题:后端实际端口不是 8080
最开始以为后端跑在 "8080",于是测试:
curl -i -X POST http://127.0.0.1:8080/user/login
结果:
Connection refused
说明 "8080" 没有服务。
查看 Java 进程:
ps -ef | grep java
看到后端是这样启动的:
java -jar app.jar --spring.profiles.active=prod --spring.jpa.hibernate.ddl-auto=update
查看 jar 中的配置:
cd /opt/app/backend unzip -p app.jar BOOT-INF/classes/application.properties
发现:
server.port=1235
所以后端实际端口是:
1235
因此后续测试应使用:
curl -i -X POST http://127.0.0.1:1235/api/user/login
而不是:
curl -i -X POST http://127.0.0.1:8080/api/user/login
六、第二层问题:以为加载 prod,实际只有 application.properties
启动命令中写了:
--spring.profiles.active=prod
所以一开始以为项目加载的是:
application-prod.yml
或者:
application-prod.properties
但查看 jar 包:
jar tf app.jar | grep application
结果只有:
BOOT-INF/classes/application.properties
也就是说,jar 里面没有 prod 配置文件。
实际使用的配置是:
spring.datasource.url=jdbc:mysql://localhost:3306/lost_found_db?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai spring.datasource.username=root spring.datasource.password=123456 server.port=1235
这里要注意:
--spring.profiles.active=prod
并不会凭空创建 prod 配置。
如果 jar 中没有:
application-prod.properties
或:
application-prod.yml
那就不会加载 prod 专属配置。
七、第三层问题:Nginx 转发时去掉了 /api 前缀
前端请求是:
POST /api/user/login
最开始 Nginx 配置为:
location /api/ { proxy_pass http://127.0.0.1:1235/; }
这个配置会导致:
前端请求:/api/user/login 后端收到:/user/login
也就是说,Nginx 把 "/api" 前缀去掉了。
这在普通接口里不一定有问题,但本项目的 JWT 过滤器白名单写的是:
/api/user/login
所以后端收到 "/user/login" 后,并不会认为这是公开接口,直接返回:
{ "code": "401", "msg": "认证失败,请重新登录" }
八、为什么确定是 JWT 过滤器拦截?
反编译 "UserService.login()" 后可以看到:
如果密码错误,抛出:用户名或密码错误
但是接口实际返回的是:
认证失败,请重新登录
这说明请求没有进入登录方法,而是被过滤器提前拦截了。
继续反编译 "JwtAuthenticationFilter":
cd /tmp/app javap -classpath BOOT-INF/classes -p -c org.example.springboot.config.JwtAuthenticationFilter
可以看到过滤器中有类似逻辑:
response.setStatus(401); response.setContentType("application/json;charset=UTF-8"); response.getWriter().write("{"code":"401","msg":"认证失败,请重新登录"}");
并且公开路径包括:
/api/user/login /api/user/register /api/user/forget /api/user/add
关键点在这里:
JWT 白名单:/api/user/login Nginx 转发后:/user/login 结果:白名单不匹配,返回 401
九、错误配置与正确配置对比
错误配置
location /api/ { proxy_pass http://127.0.0.1:1235/; }
路径变化:
/api/user/login → /user/login
结果:
JWT 白名单不匹配 返回 401:认证失败,请重新登录
正确配置
location /api/ { proxy_pass http://127.0.0.1:1235/api/;
proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header Authorization $http_authorization; proxy_set_header token $http_token; proxy_set_header satoken $http_satoken;
}
路径变化:
/api/user/login → /api/user/login
结果:
JWT 白名单匹配 请求进入 UserController.login()
十、Nginx 配置重复问题
排查时通过:
sudo nginx -T | grep -A25 -B5 "location /api"
发现有两个配置文件里都写了 "/api/":
/etc/nginx/conf.d/myapp.conf /etc/nginx/sites-enabled/default
这会导致自己改了一个配置,但实际生效的可能是另一个。
解决方法:
sudo rm -f /etc/nginx/sites-enabled/default sudo nginx -t sudo systemctl reload nginx
再确认:
sudo nginx -T | grep -A25 -B5 "location /api"
确保只剩下一个 "/api/" 配置,并且里面是:
proxy_pass http://127.0.0.1:1235/api/;
十一、Nginx 修复前后的接口返回对比
修复前
测试:
curl -i -X POST http://127.0.0.1/api/user/login \ -H "Content-Type: application/json" \ -d '{"username":"admin","password":"123456"}'
返回:
{ "code": "401", "msg": "认证失败,请重新登录" }
说明:
还没进入登录逻辑 被 JWT 过滤器拦截了
修复后
再次测试:
curl -i -X POST http://127.0.0.1/api/user/login \ -H "Content-Type: application/json" \ -d '{"username":"admin","password":"123456"}'
返回:
{ "code": "-1", "msg": "用户名或密码错误" }
说明:
Nginx 路径已经正确 JWT 没有再拦截登录接口 请求已经进入 UserService.login()
这个返回虽然还是登录失败,但已经是业务层失败,不再是部署路径问题。
十二、第四层问题:服务器数据库密码和本地不一致
进入服务器 MySQL:
mysql -u root -p
查看数据库:
USE lost_found_db; SHOW TABLES; SELECT * FROM user LIMIT 10;
用户表中存在:
user
admin 用户也存在:
username = admin password = $2a$10$... status = 1
其中 password 是 BCrypt 密文。
这说明项目不是明文密码登录,而是类似这样校验:
passwordEncoder.matches(前端输入的明文密码, 数据库中的BCrypt密文)
所以不能直接执行:
UPDATE user SET password = '123456' WHERE username = 'admin';
这样会导致永远登录失败。
十三、如何修复数据库密码问题
推荐方式是:从本地数据库复制 admin 的 BCrypt 密文到云服务器数据库。
-
本地数据库查询 admin 密文
USE lost_found_db;
SELECT username, password FROM user WHERE username = 'admin';
复制本地 "password" 字段。
-
云服务器数据库更新 admin 密码
USE lost_found_db;
UPDATE user SET password = '本地admin用户的BCrypt密文' WHERE username = 'admin';
-
重新测试登录
curl -i -X POST http://127.0.0.1/api/user/login \ -H "Content-Type: application/json" \ -d '{"username":"admin","password":"123456"}'
如果返回用户信息和 token,说明登录成功。
十四、最终推荐 Nginx 配置
完整示例:
server { listen 80; server_name 你的服务器IP或域名;
root /opt/app/frontend;
index index.html;
location / {
try_files $uri $uri/ /index.html;
}
location /api/ {
proxy_pass http://127.0.0.1:1235/api/;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Authorization $http_authorization;
proxy_set_header token $http_token;
proxy_set_header satoken $http_satoken;
}
}
修改后执行:
sudo nginx -t sudo systemctl reload nginx
十五、快速定位类似问题的方法
如果部署后登录失败,可以按下面顺序排查。
-
看前端请求地址
浏览器打开:
F12 → Network
确认登录接口:
/api/user/login
-
直接请求后端
curl -i -X POST http://127.0.0.1:1235/api/user/login \ -H "Content-Type: application/json" \ -d '{"username":"admin","password":"123456"}'
如果返回:
{ "code": "-1", "msg": "用户名或密码错误" }
说明后端接口通了,问题在账号密码或数据库。
如果返回:
{ "code": "401", "msg": "认证失败,请重新登录" }
说明可能被 JWT 或 Security 拦截。
-
再请求 Nginx
curl -i -X POST http://127.0.0.1/api/user/login \ -H "Content-Type: application/json" \ -d '{"username":"admin","password":"123456"}'
对比两个结果:
直连 1235 成功进入业务逻辑 走 Nginx 返回 401
说明 Nginx 转发路径有问题
-
查看 Nginx 生效配置
sudo nginx -T | grep -A25 -B5 "location /api"
确认:
proxy_pass http://127.0.0.1:1235/api/;
-
查看 jar 中配置
jar tf app.jar | grep application unzip -p app.jar BOOT-INF/classes/application.properties
确认:
server.port spring.datasource.url spring.datasource.username spring.datasource.password
-
查数据库用户
USE lost_found_db;
SELECT username, password, status FROM user WHERE username = 'admin';
确认:
用户存在 密码是 BCrypt 密文 状态正常
十六、一张表总结问题
阶段| 现象| 原因| 解决方式 后端端口| 访问 8080 失败| 后端实际端口是 1235| 查看 application.properties,改 Nginx 端口 配置文件| 以为加载 prod| jar 中没有 application-prod 配置| 查看 jar tf app.jar,确认真实配置 JWT 认证| 登录返回 401| Nginx 去掉 /api,白名单不匹配| proxy_pass 改为 "http://127.0.0.1:1235/api/" Nginx 配置| 改了但不生效| 存在多个 Nginx 配置| 删除 sites-enabled/default 密码校验| 返回用户名或密码错误| 云服务器数据库密码和本地不同| 同步本地 BCrypt 密文或重新导入数据库 数据库密码| 明文改 123456 无效| 项目使用 BCrypt| 必须写入 BCrypt 密文
十七、这次踩坑的核心教训
-
401 不一定是密码错
如果返回:
{ "code": "401", "msg": "认证失败,请重新登录" }
优先怀疑:
JWT 过滤器 Spring Security 请求头 token Nginx 路径转发
而不是直接怀疑密码。
-
“用户名或密码错误”才是真的进入登录逻辑
如果返回:
{ "code": "-1", "msg": "用户名或密码错误" }
说明请求已经到达登录业务方法。
这时才应该去查:
数据库账号 数据库密码密文 用户状态 密码加密方式
-
Nginx 的 proxy_pass 路径规则很容易踩坑
这两个配置非常像,但效果完全不同:
proxy_pass http://127.0.0.1:1235/;
会导致:
/api/user/login → /user/login
而:
proxy_pass http://127.0.0.1:1235/api/;
会导致:
/api/user/login → /api/user/login
如果后端 JWT 白名单写的是 "/api/user/login",就必须保留 "/api"。
-
云服务器数据库一定要单独检查
本地可以登录,不代表服务器可以登录。
部署后至少要检查:
SELECT username, password, status FROM user WHERE username = 'admin';
确认云服务器数据库中的用户数据和本地一致。
十八、最终结论
本次 Spring Boot + Vue 项目部署到腾讯云后无法登录,最终定位为:
Nginx 转发路径错误 + JWT 白名单路径不匹配 + 服务器数据库密码密文与本地不一致
修复顺序是:
-
确认后端真实端口是 1235
-
确认后端真实登录接口是 /api/user/login
-
修改 Nginx,让它保留 /api 前缀
-
删除重复 Nginx 默认配置
-
确认接口从 401 变成“用户名或密码错误”
-
同步本地数据库中的 BCrypt 密码密文
-
登录成功
这个问题的关键不是“密码对不对”,而是要先判断请求有没有真正进入登录逻辑。
只要能区分:
401 认证失败,请重新登录
和:
用户名或密码错误
排查方向就会清晰很多。
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐


所有评论(0)