如何使用角度 4.3 中的新 httpClient 处理未经授权的请求(状态为 401 或 403)

Posted

技术标签:

【中文标题】如何使用角度 4.3 中的新 httpClient 处理未经授权的请求(状态为 401 或 403)【英文标题】:How to handle unauthorized requests(status with 401 or 403) with new httpClient in angular 4.3 【发布时间】:2018-02-11 12:32:37 【问题描述】:

我有一个auth-interceptor.service.ts 来处理请求

import Injectable from '@angular/core';
import HttpErrorResponse, HttpEvent, HttpHandler, HttpInterceptor, HttpRequest from '@angular/common/http';
import Observable from 'rxjs/Observable';
import Cookie from './cookie.service';
import Router from '@angular/router';

@Injectable()
export class AuthInterceptor implements HttpInterceptor 
    constructor(private router: Router) 
    intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> 
        // Clone the request to add the new header.
        const authReq = req.clone(headers: req.headers.set(Cookie.tokenKey, Cookie.getToken()));
        // Pass on the cloned request instead of the original request.
        return next.handle(authReq).catch(this.handleError);
    

    private handleError(err: HttpErrorResponse): Observable<any> 
        console.log(err);
        if (err.status === 401 || err.status === 403) 
            Cookie.deleteUser();
            this.router.navigateByUrl(`/login`);
            return Observable.of(err.message);
        
        // handle your auth error or rethrow
        return Observable.throw(err);
    

但我收到以下错误。没有任何事情发生,例如它没有删除 cookie 或没有导航到登录页面 任何帮助或建议将不胜感激。

【问题讨论】:

HttpClient 支持拦截器。您应该使用它来透明地处理错误,而不是强制所有代码直接使用 ApiService 而不是 HttpClient。 @JBNizet 我也有一个身份验证拦截器。我已经更新了帖子。那是处理此类请求的地方吗?有示例代码吗?? 如果您使用您的方法,您将需要包装整个 http 服务。我建议使用拦截器或扩展 http 服务并仅覆盖请求方法。 @bryan60 我还有一个拦截器来为每个请求添加标头。我不确定如何处理拦截器中任何请求的响应。我已经更新了帖子 在答案中添加了一个示例 【参考方案1】:

你应该使用你的拦截器并像这样处理它:

@Injectable()
export class AuthInterceptor implements HttpInterceptor 
    constructor(private router: Router)  

    private handleAuthError(err: HttpErrorResponse): Observable<any> 
        //handle your auth error or rethrow
        if (err.status === 401 || err.status === 403) 
            //navigate /delete cookies or whatever
            this.router.navigateByUrl(`/login`);
            // if you've caught / handled the error, you don't want to rethrow it unless you also want downstream consumers to have to handle it as well.
            return of(err.message); // or EMPTY may be appropriate here
        
        return throwError(err);
    

    intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> 
    // Clone the request to add the new header.
        const authReq = req.clone(headers: req.headers.set(Cookie.tokenKey, Cookie.getToken()));
        // catch the error, make specific functions for catching specific errors and you can chain through them with more catch operators
        return next.handle(authReq).pipe(catchError(x=> this.handleAuthError(x))); //here use an arrow function, otherwise you may get "Cannot read property 'navigate' of undefined" on angular 4.4.2/net core 2/webpack 2.70
    

不需要 http 服务包装器。

要使用路由器,您需要一个工厂供应商,例如:

 providers: [
     
         provide: HTTP_INTERCEPTORS,
         useFactory: function(router: Router) 
           return new AuthInterceptor(router);
         ,
         multi: true,
         deps: [Router]
      ,
      .... other providers ...
  ]

您在哪里提供拦截器(可能是 app.module)。不要使用箭头函数。当您尝试为 prod 构建时,工厂函数不支持它们。

工作笨蛋:https://plnkr.co/edit/UxOEqhEHX1tCDVPDy488?p=preview

【讨论】:

非常感谢您的回复并添加到我的代码中。我遇到了一些奇怪的错误。我不确定为什么 this.router 未定义。我已经用回复更新了帖子。感谢您的帮助。 你需要一个工厂函数来提供一个 httpinterceptor 并注入路由器...见这个 github gist.github.com/mrgoos/45ab013c2c044691b82d250a7df71e4c 我认为是在拦截器中使用路由器,http拦截器在路由器之前注册,因此由于angular的分层注入器,没有它就无法注入 是的,我也遇到了同样的错误。看起来我需要你提供的 gist 链接中的工厂函数。 我添加了一个工厂提供者的例子,答案是 github 在工厂函数中使用了箭头函数,你不应该这样做。您的构建将失败。【参考方案2】:

根据@bryan60 的建议,我对他的解决方案做了一些更改

在 app.module.ts 中:

providers: [
     
        provide: HTTP_INTERCEPTORS,
        useFactory: function(injector: Injector) 
            return new AuthInterceptor(injector);
        ,
        multi: true,
        deps: [Injector]
    ,
      .... other providers ...
]

在 auth-interceptor.service.ts 中:

import Injectable, Injector from '@angular/core';
import HttpErrorResponse, HttpEvent, HttpHandler, HttpInterceptor, HttpRequest from '@angular/common/http';
import Observable from 'rxjs/Observable';
import Cookie from './cookie.service';
import Router from '@angular/router';
import UserService from './user.service';
import ToasterService from '../toaster/toaster.service';
import 'rxjs/add/operator/catch';
import 'rxjs/add/observable/throw';

@Injectable()
export class AuthInterceptor implements HttpInterceptor 
    constructor(private injector: Injector) 

    private handleError(err: HttpErrorResponse): Observable<any> 
        let errorMsg;
        if (err.error instanceof Error) 
            // A client-side or network error occurred. Handle it accordingly.
            errorMsg = `An error occurred: $err.error.message`;
         else 
            // The backend returned an unsuccessful response code.
            // The response body may contain clues as to what went wrong,
            errorMsg = `Backend returned code $err.status, body was: $err.error`;
        
        if (err.status === 404 || err.status === 403) 
            this.injector.get(UserService).purgeAuth();
            this.injector.get(ToasterService).showError(`Unauthorized`, errorMsg);
            this.injector.get(Router).navigateByUrl(`/login`);
        
        console.error(errorMsg);
        return Observable.throw(errorMsg);
    

    intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> 
        // Clone the request to add the new header.
        const authReq = req.clone(headers: req.headers.set(Cookie.tokenKey, Cookie.getToken()));
        // Pass on the cloned request instead of the original request.
        return next.handle(authReq).catch(err => this.handleError(err));
    

如果您在构建时使用 AOT,请尝试:

export function authInterceptorFactory(injector: Injector) 
    return new AuthInterceptor(injector);


providers: [
         
            provide: HTTP_INTERCEPTORS,
            useFactory: authInterceptorFactory,
            multi: true,
            deps: [Injector]
        ,
          .... other providers ...
]

【讨论】:

为什么使用注入器而不是显式注入?您的服务现在有 3 个隐藏的依赖项。如果你只在一个地方做这没什么大不了的,但是如果你把它变成一种习惯,你的项目可能会变得难以修改和维护。 是的,谢谢你的信息。这是我唯一这样做的地方,它直接导入到应用程序模块中。我确保没有其他模块导入此拦截器。【参考方案3】:

上面@bryan60 的回答很好用,如果有人像我一样遇到问题并发现下面一行中的错误

return next.handle(authReq).catch(x=> this.handleAuthError(x));

使用 do() 处理错误(如果遇到 catch() 问题)

导入文件:

import 'rxjs/add/operator/do';

处理错误:

return next.handle(authReq)
 .do(
    success => /*todo*/,
    err => this.handleAuthError(authReq)
    );


handleAuthError(err: any) 
    if(err.status === 401 || err.status === 403) 
    this.storageService.clear();
    window.location.href = '/home';
    

我希望这对某人有所帮助。

【讨论】:

如何注入 storageService?

以上是关于如何使用角度 4.3 中的新 httpClient 处理未经授权的请求(状态为 401 或 403)的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 Apache HttpClient 4.3 处理 Cookie

HttpClient 4.3连接池参数配置及源码解读

如何在测试中模拟 Angular 4.3 httpClient 的错误响应

如何使用httpclient拦截角度jsonp请求

android 使用Apache httpclient 4.3 出错

如何将异步服务用于角度 httpClient 拦截器