You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Angular+Parse+Keycloak通用化处理无效令牌错误方案咨询

最优方案:使用HTTP拦截器全局处理无效令牌错误

针对你的场景,**HTTP拦截器(HttpInterceptor)**是解决无效令牌错误通用化处理的最优方案,它能全局捕获所有HTTP请求错误,避免重复逻辑,同时通过延迟注入解决循环依赖问题。以下是具体实现步骤和代码示例:

1. 实现认证拦截器

创建拦截器统一处理209(无效会话令牌)错误,自动触发重新认证并重试请求,失败则执行登出逻辑:

import { Injectable, Injector } from '@angular/core';
import {
  HttpRequest,
  HttpHandler,
  HttpEvent,
  HttpInterceptor,
  HttpErrorResponse
} from '@angular/common/http';
import { Observable, throwError, from } from 'rxjs';
import { catchError, switchMap, retry } from 'rxjs/operators';
import { AuthService } from './auth.service';

@Injectable()
export class AuthInterceptor implements HttpInterceptor {
  // 用Injector延迟获取AuthService,避免循环依赖
  private authService: AuthService;

  constructor(private injector: Injector) {}

  intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {
    // 延迟初始化AuthService,规避循环依赖
    if (!this.authService) {
      this.authService = this.injector.get(AuthService);
    }

    return next.handle(request).pipe(
      catchError((error: HttpErrorResponse) => {
        // 仅处理Parse的无效会话令牌错误(code=209)
        if (error.error?.code === 209) {
          // 调用重新认证方法,将Promise转为Observable
          return from(this.authService.authBack()).pipe(
            switchMap((success: boolean) => {
              if (success) {
                // 重新认证成功,重试原请求
                return next.handle(request);
              } else {
                // 认证失败,执行登出跳转
                this.authService.logout();
                this.authService.login();
                return throwError(() => new Error('重新认证失败'));
              }
            }),
            // 仅重试1次,避免无限循环
            retry(1),
            catchError((retryError) => {
              // 重试仍失败,强制登出
              this.authService.logout();
              this.authService.login();
              return throwError(() => retryError);
            })
          );
        }

        // 其他错误直接抛出,由业务代码自行处理
        return throwError(() => error);
      })
    );
  }
}

2. 注册拦截器

在AppModule中注册拦截器,使其全局生效:

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';

import { AppComponent } from './app.component';
import { AuthInterceptor } from './auth.interceptor';

@NgModule({
  declarations: [AppComponent],
  imports: [BrowserModule, HttpClientModule],
  providers: [
    { provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true }
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }

3. 改造通用资源服务(RessourceService)

移除服务内的错误处理逻辑,改用拦截器全局处理;同时通过Injector延迟获取AuthService,解决循环依赖:

import { Injector } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { environment } from '../environments/environment';
import { AuthService } from './auth.service';

export abstract class RessourceService<T> {
  protected readonly ressourceUrl: string;
  abstract getRessourceName(): string;
  readonly headers: HttpHeaders;
  protected readonly defaultRessourcePath = 'classes/';
  private authService: AuthService;

  constructor(
    protected httpClient: HttpClient,
    private injector: Injector,
    ressourcePath?: string
  ) {
    this.authService = this.injector.get(AuthService);
    this.ressourceUrl = `${environment.back.url}/parse/${
      ressourcePath ?? this.defaultRessourcePath
    }${this.getRessourceName()}`;
    this.headers = new HttpHeaders({
      'X-Parse-Application-Id': environment.back.appId,
      'Content-Type': 'application/json',
    });
  }

  getList(includes?: string): Observable<Array<T>> {
    const url = includes ? `${this.ressourceUrl}?include=${includes}` : this.ressourceUrl;
    return this.httpClient
      .get<ListResponse<T>>(url, { headers: this.getHeaders() })
      .pipe(map((list) => list.results));
    // 移除原有的catchError,交给拦截器处理
  }

  // 获取带会话令牌的请求头
  protected getHeaders(): HttpHeaders {
    const sessionToken = this.authService.getParseSessionToken();
    return sessionToken 
      ? this.headers.set('X-Parse-Session-Token', sessionToken) 
      : this.headers;
  }
}

// 辅助接口
interface ListResponse<T> {
  results: T[];
}

4. 更新业务服务(如CarService)

修改构造函数,传入Injector以适配父类:

import { Injectable, Injector } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { RessourceService } from './ressource.service';
import { Car } from './car.model';

@Injectable({ providedIn: 'root' })
export class CarService extends RessourceService<Car> {
  getRessourceName(): string {
    return 'Car';
  }

  constructor(protected httpClient: HttpClient, private injector: Injector) {
    super(httpClient, injector);
  }
}

5. 简化组件代码

组件无需再处理错误逻辑,拦截器会自动完成认证重试和登出跳转:

ngAfterViewInit(): void {
  this.carService.getList().subscribe((res: Array<Car>) => {
    this.carList = res;
  });
  // 错误已由拦截器全局处理,无需编写冗余逻辑
}

6. 完善AuthService(关键方法)

确保authBack方法返回Promise,供拦截器转换为Observable处理:

import { Injectable } from '@angular/core';
import { KeycloakService } from 'keycloak-angular';
import { UserService } from './user.service';

@Injectable({ providedIn: 'root' })
export class AuthService {
  constructor(
    private keycloak: KeycloakService,
    private userService: UserService
  ) {}

  // 重新认证后端(返回Promise,便于拦截器转换为Observable)
  async authBack(): Promise<boolean> {
    try {
      // 从Keycloak获取最新令牌
      const keycloakToken = await this.keycloak.getToken();
      // 调用Parse接口更新会话令牌
      const parseSession = await this.userService.loginWithKeycloak(keycloakToken).toPromise();
      // 存储新的会话令牌
      localStorage.setItem('parseSessionToken', parseSession.sessionToken);
      return true;
    } catch (error) {
      return false;
    }
  }

  // 获取存储的Parse会话令牌
  getParseSessionToken(): string | null {
    return localStorage.getItem('parseSessionToken');
  }

  logout(): void {
    localStorage.removeItem('parseSessionToken');
    this.keycloak.logout();
  }

  login(): void {
    this.keycloak.login();
  }
}

方案优势

  • 全局统一处理:所有HTTP请求的无效令牌错误都由拦截器处理,彻底消除代码冗余
  • 解决循环依赖:通过Injector延迟注入AuthService,避免服务间循环依赖问题
  • 自动化流程:自动触发重新认证、请求重试,失败后自动登出跳转,无需手动干预
  • 逻辑清晰:错误处理逻辑集中在拦截器,业务代码更简洁

内容的提问来源于stack exchange,提问作者ScorprocS

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.20 23:15:46