typescript 与拦截器和模块冲突

ehxuflar  于 2023-05-01  发布在  TypeScript
关注(0)|答案(1)|浏览(130)

我有一个 Jmeter 板,您只能通过登录页面访问。我已经创建了一个导入到应用程序主模块中的登录组件和一个用于 Jmeter 板本身的延迟加载模块。
我已经为 Jmeter 板的路由创建了一个拦截器,以便在登录后使用令牌设置头部,并将其导入到这个延迟加载模块的提供程序中。但是,它也会拦截登录路由,尽管它是在 Jmeter 板模块中配置的,而不是在主模块中。
因此,如果我在登录时从后端得到一个错误(例如401 - Wrong credentials),它会显示来自拦截器的吐司错误。

app.module.ts

import { NgModule } from "@angular/core";
import { BrowserModule } from "@angular/platform-browser";

import { AppRoutingModule } from "./app-routing.module";
import { AppComponent } from "./app.component";
import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
import { LoginComponent } from "./components/login/login.component";
import { MaterialModule } from "./material/material.module";
import { ReactiveFormsModule } from "@angular/forms";
import { HttpClientModule } from "@angular/common/http";
import { ToastrModule } from "ngx-toastr";

@NgModule({
  declarations: [AppComponent, LoginComponent],
  imports: [
    BrowserModule,
    AppRoutingModule,
    BrowserAnimationsModule,
    MaterialModule,
    ReactiveFormsModule,
    HttpClientModule,
    ToastrModule.forRoot()
  ],
  providers: [],
  bootstrap: [AppComponent],
})
export class AppModule {}

app-routing。module.ts

import { NgModule } from "@angular/core";
import { RouterModule, Routes } from "@angular/router";
import { LoginComponent } from "./components/login/login.component";
import { AuthGuard } from "./guards/auth.guard";

const routes: Routes = [
  { path: "login", component: LoginComponent },
  {
    path: "dashboard",
    loadChildren: () =>
      import("./components/dashboard/dashboard.module").then(
        (m) => m.DashboardModule
      ),
    canActivate: [AuthGuard],
  },
  { path: "", redirectTo: "/login", pathMatch: "full" },
  { path: "**", redirectTo: "/dashboard", pathMatch: "full" },
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule],
})
export class AppRoutingModule {}

dashboard.module.ts

import { NgModule } from "@angular/core";
import { CommonModule } from "@angular/common";
import { PageNotFoundComponent } from "./components/page-not-found/page-not-found.component";
import { DashboardRoutingModule } from "./dashboard-routing.module";
import { ForbiddenComponent } from "./components/forbidden/forbidden.component";
import { SidenavComponent } from "./components/sidenav/sidenav.component";
import { SidenavService } from "./services/sidenav.service";
import { HTTP_INTERCEPTORS } from "@angular/common/http";
import { ToastrModule } from "ngx-toastr";
import { TokenInterceptor } from "src/app/interceptors/token.interceptor";
import { DashboardComponent } from "./dashboard.component";
import { ToolbarComponent } from "./components/toolbar/toolbar.component";

@NgModule({
  declarations: [
    DashboardComponent,
    PageNotFoundComponent,
    ForbiddenComponent,
    ToolbarComponent,
    SidenavComponent,
  ],
  imports: [CommonModule, DashboardRoutingModule, ToastrModule.forRoot()],
  providers: [
    SidenavService,
    {
      provide: HTTP_INTERCEPTORS,
      useClass: TokenInterceptor,
      multi: true,
    },
  ],
})
export class DashboardModule {}

** Jmeter 板路由。module.ts**

import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { PageNotFoundComponent } from './components/page-not-found/page-not-found.component';
import { DashboardComponent } from './dashboard.component';

const routes: Routes = [
  {
    path: '',
    component: DashboardComponent,
    children: [
      {
        path: 'notifications',
        loadChildren: () =>
          import('./components/notifications/notifications.module').then(
            (m) => m.NotificationsModule
          )
      },
    ],
  },
  { path: '**', component: PageNotFoundComponent },
];

@NgModule({
  imports: [RouterModule.forChild(routes)],
  exports: [RouterModule],
})
export class DashboardRoutingModule {}

token.interceptor.ts

import { Injectable } from "@angular/core";
import {
  HttpRequest,
  HttpHandler,
  HttpEvent,
  HttpInterceptor,
  HttpErrorResponse,
} from "@angular/common/http";
import { Observable, catchError, throwError } from "rxjs";
import { AuthService } from "../services/auth.service";
import { ToastrService } from "ngx-toastr";
import { Router } from "@angular/router";

@Injectable()
export class TokenInterceptor implements HttpInterceptor {
  constructor(
    private auth: AuthService,
    private toastr: ToastrService,
    private router: Router
  ) {}

  intercept(
    request: HttpRequest<unknown>,
    next: HttpHandler
  ): Observable<HttpEvent<unknown>> {
    const token = this.auth.accessToken;

    if (!!token) {
      request = request.clone({
        setHeaders: { Authorization: `Bearer ${token}` },
      });
    }

    return next.handle(request).pipe(
      catchError((err: any) => {
        if (err instanceof HttpErrorResponse) {
          if (err.status === 401) {
            this.toastr.warning(
              "Try to login again",
              "Session has expired"
            );
            this.router.navigate(["login"]);
          }
        }
        return throwError((err: any) => new Error(err));
      })
    );
  }
}
ha5z0ras

ha5z0ras1#

拦截器在每个请求上运行。它们不绑定到路由或特定的模块,即使您在特定的模块中声明它。当你的DashboardModule被延迟加载后,它会拦截你发送的每个请求。如果您希望行为仅绑定到特定的路由,则应该使用路由防护。
但我认为在您的情况下(如果我正确理解了所需的行为),您可以更改拦截器逻辑,使其仅处理具有Authorization头的请求,并将其他请求直接转发到下一个处理程序。我建议像这样改变拦截器的逻辑:

import { Injectable } from "@angular/core";
import { HttpRequest, HttpHandler, HttpEvent, HttpInterceptor, HttpErrorResponse } from "@angular/common/http";
import { Observable, catchError, throwError } from "rxjs";
import { AuthService } from "../services/auth.service";
import { ToastrService } from "ngx-toastr";
import { Router } from "@angular/router";

@Injectable()
export class TokenInterceptor implements HttpInterceptor {
  constructor(
    private auth: AuthService,
    private toastr: ToastrService,
    private router: Router
  ) {}

  intercept(
    request: HttpRequest<unknown>,
    next: HttpHandler
  ): Observable<HttpEvent<unknown>> {
    const token = this.auth.accessToken;
    
    // If no token, simply forward the request to next handler and return the result.
    if (!token) {
      return next.handle(request);
    }
    // If there is a token, add it to the request and handle potential 401 response
    const clonedRequest = request.clone({
      setHeaders: { Authorization: `Bearer ${token}` },
    });
    return next.handle(clonedRequest).pipe(
      catchError((err: any) => {
        if (err instanceof HttpErrorResponse) {
          if (err.status === 401) {
            this.toastr.warning(
              "Try to login again",
              "Session has expired"
            );
            this.router.navigate(["login"]);
          }
        }
        // throw the original error
        return throwError(err);
      })
    );
  }
}

因此,不同之处在于,在这种情况下,401错误处理不会添加到没有令牌的请求(如登录请求)中,而是仅添加到具有添加了承载令牌的Authorization报头的请求中。
防止未经授权的请求从DashboardModule中发送到服务器应该使用路由保护来完成,但看起来这部分已经包含在AuthGuard中了。

**注意:**我也改变了throwError逻辑,通过像你一样将错误 Package 在另一个Error类中,你可能会意外地破坏其他拦截器或应用程序逻辑,期望HttpErrorResponse类型的错误。

相关问题