Angular刷新请求NodeJS后端返回200但无Token问题排查
页面刷新时Angular请求后端Token异常问题
页面刷新时,Angular前端向NodeJS后端发起HTTP请求获取Token,偶尔会出现请求被取消的情况,甚至返回200状态码也收不到Token。但用Postman请求,或者在Angular构造函数里直接调用该请求都能正常拿到Token,怀疑是RxJS使用问题,但找不到具体原因。
AppComponent中的刷新逻辑
constructor( private router: Router, private auth: AuthService ) { // this.auth.requestServer(ServerMethod.GET, '/refresh').subscribe() // 直接调用此请求正常 router.events.pipe( switchMap((event) => { if (event instanceof NavigationStart) { const browserRefresh = !router.navigated; if (browserRefresh) { this.auth.deleteAccessToken(); return this.auth.requestServer(ServerMethod.GET, '/refresh'); } } return EMPTY; }) ).subscribe(); }
deleteAccessToken方法
deleteAccessToken() { sessionStorage.removeItem('accessToken'); this.tokenSubject.next(null); }
requestServer方法
requestServer(type: ServerMethod, path?: string, data?: any): Observable<any> { let httpOptions: httpOptions; switch (type) { case ServerMethod.POST: return this.server.post(path, data).pipe(tap(res => this.handleAccessToken(res))); case ServerMethod.GETALL: return this.server.getAll(this.getAllPath); case ServerMethod.GET: return this.server.get(path).pipe(tap(res => this.handleAccessToken(res))); default: return EMPTY; } }
Server服务中的get方法
get(path: string): Observable<any> { const url = this.serverAdress + path; return this.http.get(url); }
NodeJS后端refresh接口逻辑
module.exports.refresh_get = async (req, res) => { if (req.cookies && req.cookies.refreshToken) { // 从cookie中取出refreshToken const refreshToken = req.cookies.refreshToken; // 验证refresh token jwt.verify(refreshToken, 'secret', (err, decodedToken) => { if (err) { // 无效的Refresh Token res.status(406).json({ message: 'wrong refresh token' }); } else { // 生成新的accessToken const accessToken = createToken(decodedToken.id); // 生成新的refreshToken并设置到cookie中 const newRefreshToken = jwt.sign({ id: decodedToken.id, }, 'secret', { expiresIn: '1d' }); res.cookie('refreshToken', newRefreshToken, { httpOnly: true, sameSite: 'None', secure: true, maxAge: 24 * 60 * 60 * 1000 }); res.status(200).json({ accessToken }); } }) } else { res.status(406).json({ message: 'Unauthorized' }); } }
问题原因与解决方案
核心原因
- switchMap的取消特性:
switchMap会在新事件触发时取消未完成的Observable,页面刷新时路由事件可能多次触发,导致当前HTTP请求被中途取消。 - 路由事件时序误差:
router.navigated状态在页面刷新初期不稳定,可能导致浏览器刷新的判断逻辑失效,请求提前终止。 - 跨域凭证未携带:Angular HTTP请求默认不携带cookie,若后端依赖cookie中的refreshToken,会导致逻辑执行异常,返回空响应。
- 后端异步逻辑混乱:后端使用
async但嵌套回调函数,可能导致响应未正确发送。
针对性解决方案
1. 修正RxJS操作符与事件触发逻辑
用filter+first()确保只处理一次页面刷新的NavigationStart事件,避免重复触发导致请求被取消:
router.events.pipe( filter(event => event instanceof NavigationStart), first(), // 仅取首次路由启动事件 switchMap((event) => { const browserRefresh = !router.navigated; if (browserRefresh) { this.auth.deleteAccessToken(); return this.auth.requestServer(ServerMethod.GET, '/refresh'); } return EMPTY; }) ).subscribe({ next: res => console.log('Token获取成功', res), error: err => console.error('请求失败', err) });
2. 确保HTTP请求携带跨域凭证
修改Server服务的get方法,添加withCredentials: true,让请求携带cookie:
get(path: string): Observable<any> { const url = this.serverAdress + path; return this.http.get(url, { withCredentials: true }); // 开启凭证携带 }
3. 优化后端异步逻辑
改用util.promisify将jwt.verify转为Promise,配合async/await避免回调嵌套导致的响应异常:
const jwt = require('jsonwebtoken'); const util = require('util'); const verifyToken = util.promisify(jwt.verify); module.exports.refresh_get = async (req, res) => { try { if (!req.cookies?.refreshToken) { return res.status(406).json({ message: 'Unauthorized' }); } const refreshToken = req.cookies.refreshToken; const decodedToken = await verifyToken(refreshToken, 'secret'); const accessToken = createToken(decodedToken.id); const newRefreshToken = jwt.sign({ id: decodedToken.id }, 'secret', { expiresIn: '1d' }); res.cookie('refreshToken', newRefreshToken, { httpOnly: true, sameSite: 'None', secure: true, maxAge: 24 * 60 * 60 * 1000 }); res.status(200).json({ accessToken }); } catch (err) { res.status(406).json({ message: 'wrong refresh token' }); } }
4. 管理订阅生命周期
在组件中保存订阅对象,避免内存泄漏和意外取消:
private refreshSubscription: Subscription; constructor( private router: Router, private auth: AuthService ) { this.refreshSubscription = router.events.pipe( filter(event => event instanceof NavigationStart), first(), switchMap((event) => { const browserRefresh = !router.navigated; if (browserRefresh) { this.auth.deleteAccessToken(); return this.auth.requestServer(ServerMethod.GET, '/refresh'); } return EMPTY; }) ).subscribe(); } ngOnDestroy() { this.refreshSubscription?.unsubscribe(); }
内容的提问来源于stack exchange,提问作者Mike Twain
相关产品推荐
相关产品推荐

