如何在Angular 5中收到401响应时重定向到登录页面?

21

我正在开发一个使用OAuth2隐式流的Angular 5应用程序。

我有一些服务执行HTTP调用,以下是我的一个服务示例:

@Injectable()
export class MyService {

  constructor(public http: HttpClient) { }

  public getAll(): Observable<Persona[]> {
    return this.http.get<Persona[]>("http://mywebservice/persone");
  }
}

我正在使用拦截器来进行授权和添加自定义属性。以下是我的auth拦截器:

import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable } from "rxjs";

@Injectable()
export class AuthInterceptor implements HttpInterceptor {

  constructor() {

  }
  intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    let accessToken = sessionStorage.getItem("access_token");
    if(accessToken)
    {
        request = request.clone({
        setHeaders: {
            Authorization: `Bearer ${accessToken}`
        }
        });
    }

    return next.handle(request);
  }
}

我使用服务的方式如下:

public myMethod() {
    this.myService.getAll().subscribe(
        result => {
            console.log(result);
        }, error => {
            // I don't want add redirection there...
            console.error(error);
        });
}

现在我的需求是,当任何HTTP调用收到401结果时,应用程序将用户重定向到登录页面。

我如何避免代码重复而实现此功能呢?

非常感谢!


为什么不编写另一个拦截器来完成这个任务? - jonrsharpe
https://angular.io/tutorial/toh-pt6#handleerror - Amit Chigadani
我建议您查看这个示例:https://github.com/gothinkster/angular-realworld-example-app。您应该使用路由守卫、路由解析器和HTTP拦截器的组合。 - CornelC
@jonrsharpe,我该如何在拦截器中访问响应对象?谢谢。 - ilMattion
4个回答

28

我通过以下方式更改拦截器来解决我的问题:

@Injectable()
export class AuthInterceptor implements HttpInterceptor {

  constructor() {

  }
  intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    let accessToken = sessionStorage.getItem("access_token");
    if(accessToken)
    {
        request = request.clone({
        setHeaders: {
            Authorization: `Bearer ${accessToken}`
        }
        });
    }

    return next.handle(request).do((event: HttpEvent<any>) => {
      if (event instanceof HttpResponse) {
      }
    }, (err: any) => {
      if (err instanceof HttpErrorResponse) {
        if (err.status === 401) {
            this.router.navigate(['login']);
        }
      }
    });
  }
}

我在那里找到了解决方案:https://medium.com/@ryanchenkie_40935/angular-authentication-using-the-http-client-and-http-interceptors-2f9d1540eb8


1
这是处理401错误的不错解决方案。但是关于httprequest中编写的catch块怎么办?如何避免它? - Bh00shan
3
你从哪里获取了路由器实例? - Alexander
4
这个"this.router"是从哪里来的,先生? - Rajkumar M
3
在 Angular 13 中无法工作。不支持 do 方法。 - Dhwanil Patel

25

为了帮助新读者,注意在Angular 7中应该使用pipe()而不是do()或catch():

return next.handle(request).pipe(catchError(err => {
    if (err.status === 401) {
        MyComponent.logout();
    }
    const error = err.error.message || err.statusText;
        return throwError(error);
}));

只要你使用rxjs,那么你就是在使用Angular解决问题的方式。感谢提供理想的解决方案。 - Ahmed Shehatah

4

以下是Angular 11内容:

import {
  HttpErrorResponse,
  HttpEvent,
  HttpHandler,
  HttpInterceptor,
  HttpRequest,
} from "@angular/common/http";
import { Injectable } from "@angular/core";
import { Observable, throwError } from "rxjs";
import { catchError, map } from "rxjs/operators";
import HttpStatusCode from "src/app/models/enums/HttpStatusCode";
import { AuthenticationService } from "./authentication.service";

@Injectable()
export class AuthInterceptor implements HttpInterceptor {
  constructor(private authenticationService: AuthenticationService) {}

  intercept(
    request: HttpRequest<any>,
    next: HttpHandler
  ): Observable<HttpEvent<any>> {
    return next.handle(request).pipe(
      map((event: HttpEvent<any>) => {
        return event;
      }),
      catchError(
        (
          httpErrorResponse: HttpErrorResponse,
          _: Observable<HttpEvent<any>>
        ) => {
          if (httpErrorResponse.status === HttpStatusCode.UNAUTHORIZED) {
            this.authenticationService.redirectToLogin();
          }
          return throwError(httpErrorResponse);
        }
      )
    );
  }
}

下面的方法声明在AuthenticationService中

public redirectToLogin(path: string, queryParams: any) {
    this.router.navigate([path], {
        queryParams,
        queryParamsHandling: "merge",
    });
}

1

将错误处理程序附加到通用请求处理程序:

return next.handle(request).catch(err => {
    if (err.status === 401) {
         // Redirect here
    }
}

你可以直接在拦截器中导入路由器,但更好的做法是创建一个身份验证服务或类似的服务,导入路由器并调用它来进行重定向。

嗨Siro,谢谢你的帮助,但是当我添加catch语句后,我得到了一个通用的运行时异常next.handle(...).catch不是一个函数。 - ilMattion
HttpHandler 返回一个可观察对象: https://angular.io/api/common/http/HttpHandler 也许你遗漏了一些 rxjs 的能力,尝试添加这个: import 'rxjs/add/operator/catch'; (导入可能因使用的 rxjs 版本而异) - Siro

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接