Angular 17 DI类拦截器异步操作异常:无限加载/请求拦截求解
Angular Firebase Auth拦截器导致无限加载/请求拦截问题
我在Angular应用中集成了Firebase Auth,为实现授权功能创建了拦截器,直接调用Firebase的idToken异步流获取令牌。我认为YouTube教程里把Token存在localStorage的方案有安全隐患,所以没采用,但现在遇到了浏览器无限加载,或者拦截所有后端HTTP请求(除了Firebase Auth自身请求)的问题。
AuthService代码
import { Injectable, inject, signal } from '@angular/core'; import { Auth, createUserWithEmailAndPassword, sendEmailVerification, sendPasswordResetEmail, signInWithEmailAndPassword, signInWithPopup, signOut, updateProfile, user, idToken } from '@angular/fire/auth'; import { Observable, from, switchMap } from 'rxjs'; import { User } from '../models/user.model'; import { GoogleAuthProvider } from '@angular/fire/auth'; @Injectable({ providedIn: 'root' }) export class AuthService { firebaseAuth=inject(Auth); user$ = user(this.firebaseAuth); currentUserSig = signal<User | null | undefined>(undefined); authToken$ = idToken(this.firebaseAuth); register(email:string,username:string,password:string):Observable<void>{ const promise = createUserWithEmailAndPassword(this.firebaseAuth,email,password).then( response => { sendEmailVerification(response.user) updateProfile(response.user,{displayName:username}) } ); return from(promise); } logIn(email:string,password:string):Observable<void>{ const promise = signInWithEmailAndPassword( this.firebaseAuth, email, password ).then(()=>{}); return from (promise); } logOut():Observable<void>{ const promise = signOut(this.firebaseAuth).then(response=> console.log(response)); return from (promise); } forgetPassword(email:string):Observable<void>{ const promise = sendPasswordResetEmail(this.firebaseAuth,email); return from (promise); } googleSignIn():Observable<void>{ const provider = new GoogleAuthProvider(); provider.addScope('profile'); provider.addScope('email'); provider.setCustomParameters({ prompt: 'select_account' }); const promise = signInWithPopup(this.firebaseAuth,provider).then( response =>{ console.log(response); } ) return from (promise); } }
AuthInterceptor代码
import { Injectable } from '@angular/core'; import { HttpRequest, HttpHandler, HttpEvent, HttpInterceptor } from '@angular/common/http'; import { Observable, switchMap, take } from 'rxjs'; import { AuthService } from '../../services/auth.service'; @Injectable() export class AuthInterceptor implements HttpInterceptor { constructor(private authService: AuthService) {} intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> { return this.authService.authToken$.pipe( take(1), switchMap(token => { const authReq = request.clone({ setHeaders: { Authorization: token ? `Bearer ${token}` : '' } }); return next.handle(authReq); }) ); } }
问题原因与解决方案
核心问题分析
- 无限加载/请求循环:Firebase的
idToken$流在用户未登录时会持续发出null值,拦截器每次处理请求都会订阅这个流,若请求触发Firebase Auth状态检查,就会形成循环订阅,引发无限加载。 - 无差别拦截请求:当前拦截器没有排除无需授权的请求(比如Firebase Auth API、公共接口、静态资源),导致这些请求被添加空的Authorization头,触发后端异常或额外处理,进而阻塞请求。
具体修复步骤
1. 排除无需授权的请求
修改拦截器,跳过Firebase Auth域名、公共接口等无需授权的请求:
import { Injectable } from '@angular/core'; import { HttpRequest, HttpHandler, HttpEvent, HttpInterceptor } from '@angular/common/http'; import { Observable, switchMap, take } from 'rxjs'; import { AuthService } from '../../services/auth.service'; @Injectable() export class AuthInterceptor implements HttpInterceptor { // 定义无需授权的请求域名/路径 private excludedUrls = [ 'https://identitytoolkit.googleapis.com', // Firebase Auth API '/api/public', // 公共接口前缀(根据实际业务调整) '/assets' // 静态资源路径 ]; constructor(private authService: AuthService) {} intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> { // 检查是否为无需授权的请求,直接放行 const isExcluded = this.excludedUrls.some(url => request.url.includes(url)); if (isExcluded) { return next.handle(request); } return this.authService.authToken$.pipe( take(1), switchMap(token => { // 仅当token存在时添加Authorization头,避免空值 let authReq = request; if (token) { authReq = request.clone({ setHeaders: { Authorization: `Bearer ${token}` } }); } return next.handle(authReq); }) ); } }
2. 优化Token流的订阅逻辑
在AuthService中创建组合流,仅当用户已登录时才获取Token,避免未登录状态下的无效订阅:
// 在AuthService中添加 import { filter } from 'rxjs'; // ... // 仅当用户存在时返回Token流 authTokenWithUser$ = this.user$.pipe( filter(user => !!user), // 过滤未登录状态 switchMap(() => this.idToken(this.firebaseAuth)) );
然后修改拦截器使用该流,并添加未登录状态的 fallback:
// 拦截器中替换原有流逻辑 import { catchError } from 'rxjs'; // ... return this.authService.authTokenWithUser$.pipe( take(1), switchMap(token => { const authReq = request.clone({ setHeaders: { Authorization: `Bearer ${token}` } }); return next.handle(authReq); }), // 用户未登录时直接放行请求(根据业务需求调整) catchError(() => next.handle(request)) );
3. 移除空Authorization头
原代码中token不存在时会设置Authorization: '',这可能导致后端解析错误,改为仅当token存在时才添加该头,保持原请求的完整性。
额外优化建议
- 在AuthService中监听
user$流更新currentUserSig信号,方便组件快速获取用户状态:
// AuthService构造函数中添加 constructor() { this.user$.subscribe(user => { this.currentUserSig.set(user ? { id: user.uid, email: user.email, displayName: user.displayName } : null); }); }
内容的提问来源于stack exchange,提问作者Berlin Johns. M
相关产品推荐
相关产品推荐

