【问题标题】:Angular - Set headers for every requestAngular - 为每个请求设置标头
【发布时间】:2016-03-31 13:35:59
【问题描述】:

我需要在用户登录后为每个后续请求设置一些授权标头。


要为特定请求设置标头,

import {Headers} from 'angular2/http';
var headers = new Headers();
headers.append(headerName, value);

// HTTP POST using these headers
this.http.post(url, data, {
  headers: headers
})
// do something with the response

Reference

但是以这种方式为每个请求手动设置请求标头是不可行的。

如何设置用户登录后设置的标头,并在注销时删除这些标头?

【问题讨论】:

标签: angular


【解决方案1】:

HTTP 拦截器是执行此操作的正确方法。在这里没有看到关于如何完全实现它的正确文档,所以我提供了一个指向 Google 官方指南的链接。在实施之前我会通读文档,因为在安全性和使用多个拦截器包方面存在很多潜在缺陷。

https://angular.io/guide/http#intercepting-requests-and-responses

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

import { Observable } from 'rxjs';

/** Pass untouched request through to the next request handler. */
@Injectable()
export class NoopInterceptor implements HttpInterceptor {

  intercept(req: HttpRequest<any>, next: HttpHandler):
    Observable<HttpEvent<any>> {
    return next.handle(req);
  }
}

【讨论】:

    【解决方案2】:

    您可以使用拦截器,而不是一次又一次地为每个请求设置标头。

    每个发出的请求都将通过拦截器,您可以在其中设置身份验证标头,然后释放请求。

    【讨论】:

      【解决方案3】:

      您可以使用一些授权标头创建自己的 http 客户端:

      import {Injectable} from '@angular/core';
      import {HttpClient, HttpHeaders} from '@angular/common/http';
      
      @Injectable({
        providedIn: 'root'
      })
      export class HttpClientWithAuthorization {
      
        constructor(private http: HttpClient) {}
      
      createAuthorizationHeader(bearerToken: string): HttpHeaders {
        const headerDict = {
          Authorization: 'Bearer ' + bearerToken,
        }
        return new HttpHeaders(headerDict);
      }
      
      get<T>(url, bearerToken) {
        this.createAuthorizationHeader(bearerToken);
        return this.http.get<T>(url, {
          headers: this.createAuthorizationHeader(bearerToken)
        });
      }
      
      post<T>(url, bearerToken, data) {
        this.createAuthorizationHeader(bearerToken);
        return this.http.post<T>(url, data, {
          headers: this.createAuthorizationHeader(bearerToken)
        });
      }
      }
      

      然后在你的服务类中注入它而不是HttpClient

      @Injectable({
        providedIn: 'root'
      })
      export class SomeService {
      
        constructor(readonly httpClientWithAuthorization: HttpClientWithAuthorization) {}
      
        getSomething(): Observable<Object> {
          return this.httpClientWithAuthorization.get<Object>(url,'someBearer');
        }
      
        postSomething(data) {
          return this.httpClientWithAuthorization.post<Object>(url,'someBearer', data);
        }
      }
      

      【讨论】:

        【解决方案4】:

        HTTP 拦截器是 now available,通过 @angular/common/http 中的新 HttpClientAngular 4.3.x 及更高版本

        现在为每个请求添加标头非常简单:

        import {
          HttpEvent,
          HttpInterceptor,
          HttpHandler,
          HttpRequest,
        } from '@angular/common/http';
        import { Observable } from 'rxjs';
         
        export class AddHeaderInterceptor implements HttpInterceptor {
          intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
            // Clone the request to add the new header
            const clonedRequest = req.clone({ headers: req.headers.append('Authorization', 'Bearer 123') });
        
            // Pass the cloned request instead of the original request to the next handle
            return next.handle(clonedRequest);
          }
        }
        

        有一个principle of immutability,这就是在设置新内容之前需要克隆请求的原因。

        由于编辑标题是一项非常常见的任务,实际上有一个快捷方式(在克隆请求时):

        const clonedRequest = req.clone({ setHeaders: { Authorization: 'Bearer 123' } });

        创建拦截器后,您应该使用HTTP_INTERCEPTORS 提供注册它。

        import { HTTP_INTERCEPTORS } from '@angular/common/http';
        
        @NgModule({
          providers: [{
            provide: HTTP_INTERCEPTORS,
            useClass: AddHeaderInterceptor,
            multi: true,
          }],
        })
        export class AppModule {}
        

        【讨论】:

        • 我实现了这一点,在执行 ng serve 时我可以看到请求标头,但是在执行 ng b prod 并在 tomcat 中部署时,我看不到标头...使用 spring-boot,标头去哪儿了?
        • 不知道是不是因为我正在使用 Express 节点 API,但即使使用官方 Angular 文档也对我不起作用。 ://
        • ERROR TypeError: CreateListFromArrayLike call on non-object
        • 如何将任何东西注入 HttpInterceptor?
        • 来自以下答案的一个很好的安全说明 [stackoverflow.com/a/35047978/887092] - 此拦截器还可能将您的 JWT 令牌(如果您正在使用它)发送到第三方网络服务器。这意味着他们可以冒充您(直到到期日)。为了使这个答案更安全,您需要条件逻辑,因此仅为正确的服务器发送正确的标头。
        【解决方案5】:

        最简单的

        创建一个config.ts 文件

        import { HttpHeaders } from '@angular/common/http';
        
        export class Config {
            url: string = 'http://localhost:3000';
            httpOptions: any = {
                headers: new HttpHeaders({
                   'Content-Type': 'application/json',
                   'Authorization': JSON.parse(localStorage.getItem('currentUser')).token
                })
            }
        }
        

        然后在您的service 上,只需导入config.ts 文件

        import { Config } from '../config';
        import { HttpClient } from '@angular/common/http';
        
        @Injectable()
        export class OrganizationService {
          config = new Config;
        
          constructor(
            private http: HttpClient
          ) { }
        
          addData(data): Observable<any> {
             let sendAddLink = `${this.config.url}/api/addData`;
        
             return this.http.post(sendAddLink , data, this.config.httpOptions).pipe(
               tap(snap => {
              return snap;
                })
            );
         } 
        

        我认为这是最简单和最安全的。

        【讨论】:

          【解决方案6】:

          对于 Angular 5 及以上版本,我们可以使用 HttpInterceptor 来泛化请求和响应操作。 这有助于我们避免重复:

          1) 通用头文件

          2) 指定响应类型

          3) 查询请求

          import { Injectable } from '@angular/core';
          import {
            HttpRequest,
            HttpHandler,
            HttpEvent,
            HttpInterceptor,
            HttpResponse,
            HttpErrorResponse
          } from '@angular/common/http';
          import { Observable } from 'rxjs/Observable';
          import 'rxjs/add/operator/do';
          
          @Injectable()
          export class AuthHttpInterceptor implements HttpInterceptor {
          
            requestCounter: number = 0;
            constructor() {
            }
          
            intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
          
              request = request.clone({
                responseType: 'json',
                setHeaders: {
                  Authorization: `Bearer token_value`,
                  'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
                }
              });
          
              return next.handle(request).do((event: HttpEvent<any>) => {
                if (event instanceof HttpResponse) {
                  // do stuff with response if you want
                }
              }, (err: any) => {
                if (err instanceof HttpErrorResponse) {
                  // do stuff with response error if you want
                }
              });
            }
          }
          

          我们可以使用这个 AuthHttpInterceptor 类作为 HttpInterceptor 的提供者:

          import { BrowserModule } from '@angular/platform-browser';
          import { NgModule } from '@angular/core';
          import { AppComponent } from './app.component';
          import { AppRoutingModule } from './app.routing-module';
          import { AuthHttpInterceptor } from './services/auth-http.interceptor';
          import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
          import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
          
          @NgModule({
            declarations: [
              AppComponent
            ],
            imports: [
              BrowserModule,
              AppRoutingModule,
              HttpClientModule,
              BrowserAnimationsModule,
            ],
            providers: [
              {
                provide: HTTP_INTERCEPTORS,
                useClass: AuthHttpInterceptor,
                multi: true
              }
            ],
            exports: [],
            bootstrap: [AppComponent]
          })
          export class AppModule {
          }
          

          【讨论】:

            【解决方案7】:

            扩展BaseRequestOptions 在这种情况下可能会有很大帮助。查看以下代码:

            import {provide} from 'angular2/core';
            import {bootstrap} from 'angular2/platform/browser';
            import {HTTP_PROVIDERS, Headers, Http, BaseRequestOptions} from 'angular2/http';
            
            import {AppCmp} from './components/app/app';
            
            
            class MyRequestOptions extends BaseRequestOptions {
              constructor () {
                super();
                this.headers.append('My-Custom-Header','MyCustomHeaderValue');
              }
            } 
            
            bootstrap(AppCmp, [
              ROUTER_PROVIDERS,
              HTTP_PROVIDERS,
              provide(RequestOptions, { useClass: MyRequestOptions })
            ]);
            

            这应该在每次调用中包含“My-Custom-Header”。

            更新:

            为了能够随时更改标题而不是上面的代码,您还可以使用以下代码添加新标题:

            this.http._defaultOptions.headers.append('Authorization', 'token');
            

            删除你可以做

            this.http._defaultOptions.headers.delete('Authorization');
            

            还有另一个函数可以用来设置值:

            this.http._defaultOptions.headers.set('Authorization', 'token');
            

            上述解决方案在打字稿上下文中仍然不完全有效。 _defaultHeaders 受到保护,不应该像这样使用。我建议使用上述解决方案进行快速修复,但从长远来看,最好围绕也处理身份验证的 http 调用编写自己的包装器。以 auth0 中的以下示例为例,它更好更干净。

            https://github.com/auth0/angular2-jwt/blob/master/angular2-jwt.ts

            更新 - 2018 年 6 月 我看到很多人都在寻求这个解决方案,但我会提出其他建议。全局附加标头会将身份验证令牌发送到从您的应用发出的 每个 api 调用。因此,去往第三方插件(如对讲机或 zendesk 或任何其他 api)的 api 调用也将携带您的授权标头。这可能会导致重大的安全漏洞。 因此,请改为全局使用拦截器,但手动检查传出呼叫是否针对您服务器的 api 端点,然后附加身份验证标头。

            【讨论】:

            • this.http._defaultOptions.headers.delete('My-Custom-Header') 所以上面的过程可以通过下面的代码来缩短 this.http._defaultOptions.headers.append('My-New -Custom-Header', 'newvalue')
            • @Dinistro 是的,现在我不建议自己这样做。由于角度 beta 限制以及我在全球范围内控制身份验证流程的习惯,我不得不想出这个解决方法。但我相信现在github.com/auth0/angular2-jwt/blob/master/angular2-jwt.ts 有一个更好更干净的解决方案。
            • 使用 BaseRequestOptions 的问题是它的构造函数在浏览器的应用程序生命周期中只运行一次。因此,如果您想在一段时间内更改标头值(例如 csrf_token),则不能以这种方式进行(即使在此类中覆盖合并方法也无济于事:()
            • 问题是,如果您使用包装器,则需要重新编写直接访问 HTTP 的第三方库才能使用它。我仍然不知道如何解决这个问题。真的需要一个拦截器。不知道是否有人知道更好的方法。
            • 嗨,在 angular4 中 _defaultOptions 受到保护,因此无法从服务中调用
            【解决方案8】:

            回答你的问题,你可以提供一个服务来包装来自 Angular 的原始 Http 对象。如下所述。

            import {Injectable} from '@angular/core';
            import {Http, Headers} from '@angular/http';
            
            @Injectable()
            export class HttpClient {
            
              constructor(private http: Http) {}
            
              createAuthorizationHeader(headers: Headers) {
                headers.append('Authorization', 'Basic ' +
                  btoa('username:password')); 
              }
            
              get(url) {
                let headers = new Headers();
                this.createAuthorizationHeader(headers);
                return this.http.get(url, {
                  headers: headers
                });
              }
            
              post(url, data) {
                let headers = new Headers();
                this.createAuthorizationHeader(headers);
                return this.http.post(url, data, {
                  headers: headers
                });
              }
            }
            

            您可以注入这个 (HttpClient),而不是注入 Http 对象。

            import { HttpClient } from './http-client';
            
            export class MyComponent {
              // Notice we inject "our" HttpClient here, naming it Http so it's easier
              constructor(http: HttpClient) {
                this.http = httpClient;
              }
            
              handleSomething() {
                this.http.post(url, data).subscribe(result => {
                    // console.log( result );
                });
              }
            }
            

            我还认为,可以通过提供您自己的扩展 Http 的类来为 Http 类使用多个提供程序来完成某些事情...请参阅此链接:http://blog.thoughtram.io/angular2/2015/11/23/multi-providers-in-angular-2.html

            【讨论】:

            • 'this.http = http;'在哪里来自,我相信我们需要在使用之前声明它?
            • 角标头(设置和附加功能)正在“规范化”标头的键并使其小写。来自 Headers.d.ts://“HTTP 字符集由不区分大小写的标记标识”//tools.ietf.org/html/rfc2616 的规范对于那些没有符合规范的后端的人;这是一个绕过: let headersMap = .get(options, 'headers._headersMap', new Map()); headersMap.set('授权', [.replace(Bearer ${token}, /\"/g, '')]);
            • @DiegoUnanue 我正在使用 Angular 2 的最终版本,并且 Thierry 的实现工作正常。只需将导入语句中的“angular2”替换为“@angular”即可。
            • Mark Pieszak- 我应该包括 HttpClient 的提供程序吗?
            • 现在 TS 抛出错误:`Argument of type '{ headers: Headers; }' 不能分配给“RequestOptionsArgs”类型的参数`
            【解决方案9】:

            这就是我为每个请求设置令牌的方式。

            import { RequestOptions, BaseRequestOptions, RequestOptionsArgs } from '@angular/http';
            
            export class CustomRequestOptions extends BaseRequestOptions {
            
                constructor() {
                    super();
                    this.headers.set('Content-Type', 'application/json');
                }
                merge(options?: RequestOptionsArgs): RequestOptions {
                    const token = localStorage.getItem('token');
                    const newOptions = super.merge(options);
                    if (token) {
                        newOptions.headers.set('Authorization', `Bearer ${token}`);
                    }
            
                    return newOptions;
                }
            }
            

            并在 app.module.ts 中注册

            @NgModule({
                declarations: [
                    AppComponent
                ],
                imports: [
                    BrowserModule
                ],
                providers: [
                    { provide: RequestOptions, useClass: CustomRequestOptions }
                ],
                bootstrap: [AppComponent]
            })
            export class AppModule { }
            

            【讨论】:

              【解决方案10】:

              我可以选择更简单的解决方案 > 将新标题添加到默认选项合并或通过您的 api get(或其他)函数加载。

              get(endpoint: string, params?: any, options?: RequestOptions) {
                if (!options) {
                  options = new RequestOptions();
                  options.headers = new Headers( { "Accept": "application/json" } ); <<<<
                }
                // [...] 
              }
              

              当然,您可以在默认选项或您的班级中的任何内容中将此标头外部化。 这是在 Ionic 生成的 api.ts @Injectable() 导出类 API {}

              它非常快,对我有用。我不想要 json/ld 格式。

              【讨论】:

                【解决方案11】:

                我喜欢覆盖默认选项的想法,这似乎是一个不错的解决方案。

                但是,如果您打算扩展 Http 类。请务必通读!

                这里的一些答案实际上显示了 request() 方法的错误重载,这可能导致难以捕捉的错误和奇怪的行为。这是我自己偶然发现的。

                此解决方案基于 Angular 4.2.x 中的 request() 方法实现,但应该与未来兼容:

                import {Observable} from 'rxjs/Observable';
                import {Injectable} from '@angular/core';
                
                import {
                  ConnectionBackend, Headers,
                  Http as NgHttp,
                  Request,
                  RequestOptions,
                  RequestOptionsArgs,
                  Response,
                  XHRBackend
                } from '@angular/http';
                
                
                import {AuthenticationStateService} from '../authentication/authentication-state.service';
                
                
                @Injectable()
                export class Http extends NgHttp {
                
                  constructor (
                    backend: ConnectionBackend,
                    defaultOptions: RequestOptions,
                    private authenticationStateService: AuthenticationStateService
                  ) {
                    super(backend, defaultOptions);
                  }
                
                
                  request (url: string | Request, options?: RequestOptionsArgs): Observable<Response> {
                
                    if ('string' === typeof url) {
                
                      url = this.rewriteUrl(url);
                      options = (options || new RequestOptions());
                      options.headers = this.updateHeaders(options.headers);
                
                      return super.request(url, options);
                
                    } else if (url instanceof Request) {
                
                      const request = url;
                      request.url = this.rewriteUrl(request.url);
                      request.headers = this.updateHeaders(request.headers);
                
                      return super.request(request);
                
                    } else {
                      throw new Error('First argument must be a url string or Request instance');
                    }
                
                  }
                
                
                  private rewriteUrl (url: string) {
                    return environment.backendBaseUrl + url;
                  }
                
                  private updateHeaders (headers?: Headers) {
                
                    headers = headers || new Headers();
                
                    // Authenticating the request.
                    if (this.authenticationStateService.isAuthenticated() && !headers.has('Authorization')) {
                      headers.append('Authorization', 'Bearer ' + this.authenticationStateService.getToken());
                    }
                
                    return headers;
                
                  }
                
                }
                

                请注意,我以这种方式导入原始类 import { Http as NgHttp } from '@angular/http'; 以防止名称冲突。

                这里解决的问题是request() 方法有两个不同的调用签名。当传递Request 对象而不是URL string 时,Angular 会忽略options 参数。所以这两种情况都必须妥善处理。

                下面是如何将这个被覆盖的类注册到 DI 容器的示例:

                export const httpProvider = {
                  provide: NgHttp,
                  useFactory: httpFactory,
                  deps: [XHRBackend, RequestOptions, AuthenticationStateService]
                };
                
                
                export function httpFactory (
                  xhrBackend: XHRBackend,
                  requestOptions: RequestOptions,
                  authenticationStateService: AuthenticationStateService
                ): Http {
                  return new Http(
                    xhrBackend,
                    requestOptions,
                    authenticationStateService
                  );
                }
                

                通过这种方法,您可以正常注入Http 类,但您的覆盖类将被神奇地注入。这使您可以轻松集成您的解决方案,而无需更改应用程序的其他部分(多态性在起作用)。

                只需将httpProvider 添加到模块元数据的providers 属性即可。

                【讨论】:

                  【解决方案12】:

                  迟到总比没有好... =)

                  您可以采用扩展BaseRequestOptions(从这里https://angular.io/docs/ts/latest/guide/server-communication.html#!#override-default-request-options)的概念并“即时”刷​​新标题(不仅在构造函数中)。您可以像这样使用 getter/setter "headers" 属性覆盖:

                  import { Injectable } from '@angular/core';
                  import { BaseRequestOptions, RequestOptions, Headers } from '@angular/http';
                  
                  @Injectable()
                  export class DefaultRequestOptions extends BaseRequestOptions {
                  
                      private superHeaders: Headers;
                  
                      get headers() {
                          // Set the default 'Content-Type' header
                          this.superHeaders.set('Content-Type', 'application/json');
                  
                          const token = localStorage.getItem('authToken');
                          if(token) {
                              this.superHeaders.set('Authorization', `Bearer ${token}`);
                          } else {
                              this.superHeaders.delete('Authorization');
                          }
                          return this.superHeaders;
                      }
                  
                      set headers(headers: Headers) {
                          this.superHeaders = headers;
                      }
                  
                      constructor() {
                          super();
                      }
                  }
                  
                  export const requestOptionsProvider = { provide: RequestOptions, useClass: DefaultRequestOptions };
                  

                  【讨论】:

                  • 小更新:为了获得更好的性能,您可以考虑将所有静态标题(如“Content-Type”)移动到构造函数
                  【解决方案13】:

                  虽然我很晚才回答这个问题,但如果有人正在寻求更简单的解决方案。

                  我们可以使用 angular2-jwt。 angular2-jwt 在从 Angular 2 应用发出 HTTP 请求时自动附加 JSON Web 令牌 (JWT) 作为授权标头很有用。

                  我们可以使用高级配置选项设置全局标题

                  export function authHttpServiceFactory(http: Http, options: RequestOptions) {
                    return new AuthHttp(new AuthConfig({
                      tokenName: 'token',
                          tokenGetter: (() => sessionStorage.getItem('token')),
                          globalHeaders: [{'Content-Type':'application/json'}],
                      }), http, options);
                  }
                  

                  并发送每个请求令牌,如

                      getThing() {
                    let myHeader = new Headers();
                    myHeader.append('Content-Type', 'application/json');
                  
                    this.authHttp.get('http://example.com/api/thing', { headers: myHeader })
                      .subscribe(
                        data => this.thing = data,
                        err => console.log(error),
                        () => console.log('Request Complete')
                      );
                  
                    // Pass it after the body in a POST request
                    this.authHttp.post('http://example.com/api/thing', 'post body', { headers: myHeader })
                      .subscribe(
                        data => this.thing = data,
                        err => console.log(error),
                        () => console.log('Request Complete')
                      );
                  }
                  

                  【讨论】:

                  【解决方案14】:

                  经过一番调查,我发现最后也是最简单的方法是扩展我更喜欢的BaseRequestOptions
                  以下是我尝试并出于某种原因放弃的方法:
                  1.扩展BaseRequestOptions,并在constructor()中添加动态标题。如果我登录它就无法工作。它将被创建一次。所以它不是动态的。
                  2. 扩展Http。同上原因,我无法在constructor() 中添加动态标题。如果我重写request(..) 方法,并设置标题,像这样:

                  request(url: string|Request, options?: RequestOptionsArgs): Observable<Response> {
                   let token = localStorage.getItem(AppConstants.tokenName);
                   if (typeof url === 'string') { // meaning we have to add the token to the options, not in url
                    if (!options) {
                      options = new RequestOptions({});
                    }
                    options.headers.set('Authorization', 'token_value');
                   } else {
                    url.headers.set('Authorization', 'token_value');
                   }
                   return super.request(url, options).catch(this.catchAuthError(this));
                  }
                  

                  你只需要覆盖这个方法,而不是每个 get/post/put 方法。

                  3.我首选的解决方案是扩展BaseRequestOptions 并覆盖merge()

                  @Injectable()
                  export class AuthRequestOptions extends BaseRequestOptions {
                  
                   merge(options?: RequestOptionsArgs): RequestOptions {
                    var newOptions = super.merge(options);
                    let token = localStorage.getItem(AppConstants.tokenName);
                    newOptions.headers.set(AppConstants.authHeaderName, token);
                    return newOptions;
                   }
                  }
                  

                  每个请求都会调用这个merge() 函数。

                  【讨论】:

                  • 在所有给出的答案中,这是引起我注意的答案,因为我已经寻求基于扩展BaseRequestOptions 的解决方案。但是,可悲的是,这对我不起作用。有什么可能的原因吗?
                  • 让它工作。这个解决方案很好,我的服务器有问题。我必须为 CORS 飞行前请求做一些配置。参考这个链接stackoverflow.com/a/43962690/3892439
                  • 如何将AuthRequestOptions 绑定到应用程序的其余部分?我试着把它放在providers 部分,但它没有做任何事情。
                  • 您必须覆盖RequestOptions 的提供程序,而不是BaseRequestOptionsangular.io/api/http/BaseRequestOptions
                  • 在我的应用程序中,我只是扩展了 BaseRequestOptions,它已经扩展了 RequestOptions。然后在 app.module 中,你应该设置提供者:{ provide: RequestOptions, useClass: AuthRequestOptions }
                  【解决方案15】:

                  angular 2.0.1 及更高版本有一些变化:

                      import {RequestOptions, RequestMethod, Headers} from '@angular/http';
                      import { BrowserModule } from '@angular/platform-browser';
                      import { HttpModule }     from '@angular/http';
                      import { AppRoutingModule } from './app.routing.module';   
                      import { AppComponent }  from './app.component';
                  
                      //you can move this class to a better place
                      class GlobalHttpOptions extends RequestOptions {
                          constructor() { 
                            super({ 
                              method: RequestMethod.Get,
                              headers: new Headers({
                                'MyHeader': 'MyHeaderValue',
                              })
                            });
                          }
                        }
                  
                      @NgModule({
                  
                        imports:      [ BrowserModule, HttpModule, AppRoutingModule ],
                        declarations: [ AppComponent],
                        bootstrap:    [ AppComponent ],
                        providers:    [ { provide: RequestOptions, useClass: GlobalHttpOptions} ]
                      })
                  
                      export class AppModule { }
                  

                  【讨论】:

                  • 不行,自己试过了。除了刷新之外什么都不会被调用。
                  【解决方案16】:

                  Angular 2.1.2 中,我通过扩展有角度的 Http 来解决这个问题:

                  import {Injectable} from "@angular/core";
                  import {Http, Headers, RequestOptionsArgs, Request, Response, ConnectionBackend, RequestOptions} from "@angular/http";
                  import {Observable} from 'rxjs/Observable';
                  
                  @Injectable()
                  export class HttpClient extends Http {
                  
                    constructor(protected _backend: ConnectionBackend, protected _defaultOptions: RequestOptions) {
                  
                      super(_backend, _defaultOptions);
                    }
                  
                    _setCustomHeaders(options?: RequestOptionsArgs):RequestOptionsArgs{
                      if(!options) {
                        options = new RequestOptions({});
                      }
                      if(localStorage.getItem("id_token")) {
                  
                        if (!options.headers) {
                  
                          options.headers = new Headers();
                  
                  
                        }
                        options.headers.set("Authorization", localStorage.getItem("id_token"))
                      }
                      return options;
                    }
                  
                  
                    request(url: string|Request, options?: RequestOptionsArgs): Observable<Response> {
                      options = this._setCustomHeaders(options);
                      return super.request(url, options)
                    }
                  }
                  

                  然后在我的 App Providers 中,我能够使用自定义工厂来提供“Http”

                  import { RequestOptions, Http, XHRBackend} from '@angular/http';
                  import {HttpClient} from './httpClient';
                  import { RequestOptions, Http, XHRBackend} from '@angular/http';
                  import {HttpClient} from './httpClient';//above snippet
                  
                  function httpClientFactory(xhrBackend: XHRBackend, requestOptions: RequestOptions): Http {
                    return new HttpClient(xhrBackend, requestOptions);
                  }
                  
                  @NgModule({
                    imports:[
                      FormsModule,
                      BrowserModule,
                    ],
                    declarations: APP_DECLARATIONS,
                    bootstrap:[AppComponent],
                    providers:[
                       { provide: Http, useFactory: httpClientFactory, deps: [XHRBackend, RequestOptions]}
                    ],
                  })
                  export class AppModule {
                    constructor(){
                  
                    }
                  }
                  

                  现在我不需要声明每个 Http 方法,并且可以在整个应用程序中正常使用http

                  【讨论】:

                  • 这个答案对我来说效果最好,因为我能够过滤到我的 api 服务器的 url,并且只将 Auth Token 添加到对其进行的调用。我将请求更改为: request(url: string|Request, options?: RequestOptionsArgs): Observable { var _url: string = url.toString(); if (_url.indexOf('api.myserver.com') > -1) { options = this._setCustomHeaders(options); } 返回 super.request(url, options) }
                  • 在我的情况下,withCredentials 和 Headers 是从请求方法中的 url 参数中获取的。我更改了这样的代码: request(url: string|Request, options?: RequestOptionsArgs): Observable { options = this._setCustomHeaders(options); if (typeof(url) === "object") { (url).withCredentials = options.withCredentials; (url).headers = options.headers; } 返回 super.request(url, options) }
                  • 您重载的request() 方法有两个调用签名,并且options 属性仅在url 指定为字符串时使用。如果urlRequest 的实例,则options 属性将被忽略。这可能导致难以捕捉错误。详情请看我的回答。
                  • 请注意,此解决方案具有some issues with server platform。不过有workarounds to avoid it
                  • 这对我有用,直到 angular 4.2。 4.3 有拦截器。
                  【解决方案17】:

                  通过扩展 Angular 2 Http Provider 创建自定义 Http 类,并简单地覆盖自定义 Http 类中的 constructorrequest 方法。下面的示例在每个 http 请求中添加 Authorization 标头。

                  import {Injectable} from '@angular/core';
                  import {Http, XHRBackend, RequestOptions, Request, RequestOptionsArgs, Response, Headers} from '@angular/http';
                  import {Observable} from 'rxjs/Observable';
                  import 'rxjs/add/operator/map';
                  import 'rxjs/add/operator/catch';
                  
                  @Injectable()
                  export class HttpService extends Http {
                  
                    constructor (backend: XHRBackend, options: RequestOptions) {
                      let token = localStorage.getItem('auth_token'); // your custom token getter function here
                      options.headers.set('Authorization', `Bearer ${token}`);
                      super(backend, options);
                    }
                  
                    request(url: string|Request, options?: RequestOptionsArgs): Observable<Response> {
                      let token = localStorage.getItem('auth_token');
                      if (typeof url === 'string') { // meaning we have to add the token to the options, not in url
                        if (!options) {
                          // let's make option object
                          options = {headers: new Headers()};
                        }
                        options.headers.set('Authorization', `Bearer ${token}`);
                      } else {
                      // we have to add the token to the url object
                        url.headers.set('Authorization', `Bearer ${token}`);
                      }
                      return super.request(url, options).catch(this.catchAuthError(this));
                    }
                  
                    private catchAuthError (self: HttpService) {
                      // we have to pass HttpService's own instance here as `self`
                      return (res: Response) => {
                        console.log(res);
                        if (res.status === 401 || res.status === 403) {
                          // if not authenticated
                          console.log(res);
                        }
                        return Observable.throw(res);
                      };
                    }
                  }
                  

                  然后配置您的主要app.module.ts 以将XHRBackend 作为ConnectionBackend 提供程序和RequestOptions 提供给您的自定义Http 类:

                  import { HttpModule, RequestOptions, XHRBackend } from '@angular/http';
                  import { HttpService } from './services/http.service';
                  ...
                  @NgModule({
                    imports: [..],
                    providers: [
                      {
                        provide: HttpService,
                        useFactory: (backend: XHRBackend, options: RequestOptions) => {
                          return new HttpService(backend, options);
                        },
                        deps: [XHRBackend, RequestOptions]
                      }
                    ],
                    bootstrap: [ AppComponent ]
                  })
                  

                  之后,您现在可以在您的服务中使用您的自定义 http 提供程序。例如:

                  import { Injectable }     from '@angular/core';
                  import {HttpService} from './http.service';
                  
                  @Injectable()
                  class UserService {
                    constructor (private http: HttpService) {}
                  
                    // token will added automatically to get request header
                    getUser (id: number) {
                      return this.http.get(`/users/${id}`).map((res) => {
                        return res.json();
                      } );
                    }
                  }
                  

                  这是一份综合指南 - http://adonespitogo.com/articles/angular-2-extending-http-provider/

                  【讨论】:

                  • 这种方法非常适合使用替代类提供程序。您可以使用“provide: Http”代替模块中的“provide: HttpService”,让您可以像往常一样使用 Http。
                  • 如何向这个扩展的 http 类添加其他属性?例如,路由器:路由器或任何自定义可注入服务。
                  • @shafeequemat 你不能用这个来做到这一点。您可以在自定义 http 类中定义另一个方法,例如 setRouter(router)。或者您可以创建另一个类并在其中注入您的自定义 http 类,而不是相反。
                  【解决方案18】:

                  这是已接受答案的改进版本,已针对 Angular2 final 进行了更新:

                  import {Injectable} from "@angular/core";
                  import {Http, Headers, Response, Request, BaseRequestOptions, RequestMethod} from "@angular/http";
                  import {I18nService} from "../lang-picker/i18n.service";
                  import {Observable} from "rxjs";
                  @Injectable()
                  export class HttpClient {
                  
                      constructor(private http: Http, private i18n: I18nService ) {}
                  
                      get(url:string):Observable<Response> {
                          return this.request(url, RequestMethod.Get);
                      }
                  
                      post(url:string, body:any) {   
                          return this.request(url, RequestMethod.Post, body);
                      }
                  
                      private request(url:string, method:RequestMethod, body?:any):Observable<Response>{
                  
                          let headers = new Headers();
                          this.createAcceptLanguageHeader(headers);
                  
                          let options = new BaseRequestOptions();
                          options.headers = headers;
                          options.url = url;
                          options.method = method;
                          options.body = body;
                          options.withCredentials = true;
                  
                          let request = new Request(options);
                  
                          return this.http.request(request);
                      }
                  
                      // set the accept-language header using the value from i18n service that holds the language currently selected by the user
                      private createAcceptLanguageHeader(headers:Headers) {
                  
                          headers.append('Accept-Language', this.i18n.getCurrentLang());
                      }
                  }
                  

                  当然,如果需要,它应该扩展到 deleteput 之类的方法(我的项目中目前还不需要它们)。

                  优点是get/post/...方法中的重复代码较少。

                  请注意,在我的例子中,我使用 cookie 进行身份验证。我需要 i18n 的标头(Accept-Language 标头),因为我们的 API 返回的许多值都是用用户的语言翻译的。在我的应用中,i18n 服务保存用户当前选择的语言。

                  【讨论】:

                  • 你是如何让 tslint 像 let 一样忽略标题的?
                  【解决方案19】:

                  您可以在路由中使用canActive,如下所示:

                  import { Injectable } from '@angular/core';
                  import { Router } from '@angular/router';
                  import { CanActivate } from '@angular/router';
                  import { AuthService } from './auth.service';
                  
                  @Injectable()
                  export class AuthGuard implements CanActivate {
                  
                    constructor(private auth: AuthService, private router: Router) {}
                  
                    canActivate() {
                      // If user is not logged in we'll send them to the homepage 
                      if (!this.auth.loggedIn()) {
                        this.router.navigate(['']);
                        return false;
                      }
                      return true;
                    }
                  
                  }
                  
                  const appRoutes: Routes = [
                    {
                      path: '', redirectTo: '/deals', pathMatch: 'full'
                    },
                    {
                      path: 'special',
                      component: PrivateDealsComponent,
                      /* We'll use the canActivate API and pass in our AuthGuard.
                         Now any time the /special route is hit, the AuthGuard will run
                         first to make sure the user is logged in before activating and
                         loading this route. */
                      canActivate: [AuthGuard]
                    }
                  ];
                  

                  取自:https://auth0.com/blog/angular-2-authentication

                  【讨论】:

                    【解决方案20】:

                    像下面这样保持一个单独的服务怎么样

                                import {Injectable} from '@angular/core';
                                import {Headers, Http, RequestOptions} from '@angular/http';
                    
                    
                                @Injectable()
                                export class HttpClientService extends RequestOptions {
                    
                                    constructor(private requestOptionArgs:RequestOptions) {
                                        super();     
                                    }
                    
                                    addHeader(headerName: string, headerValue: string ){
                                        (this.requestOptionArgs.headers as Headers).set(headerName, headerValue);
                                    }
                                }
                    

                    当你从另一个地方调用它时,使用this.httpClientService.addHeader("Authorization", "Bearer " + this.tok);

                    您将看到添加的标题,例如:- Authorization 如下

                    【讨论】:

                      【解决方案21】:

                      虽然我回答得很晚,但它可能会帮助其他人。要在使用@NgModule 时向所有请求注入标头,可以执行以下操作:

                      (我在 Angular 2.0.1 中对此进行了测试)

                      /**
                       * Extending BaseRequestOptions to inject common headers to all requests.
                       */
                      class CustomRequestOptions extends BaseRequestOptions {
                          constructor() {
                              super();
                              this.headers.append('Authorization', 'my-token');
                              this.headers.append('foo', 'bar');
                          }
                      }
                      

                      现在在@NgModule 中执行以下操作:

                      @NgModule({
                          declarations: [FooComponent],
                          imports     : [
                      
                              // Angular modules
                              BrowserModule,
                              HttpModule,         // This is required
                      
                              /* other modules */
                          ],
                          providers   : [
                              {provide: LocationStrategy, useClass: HashLocationStrategy},
                              // This is the main part. We are telling Angular to provide an instance of
                              // CustomRequestOptions whenever someone injects RequestOptions
                              {provide: RequestOptions, useClass: CustomRequestOptions}
                          ],
                          bootstrap   : [AppComponent]
                      })
                      

                      【讨论】:

                      • 你需要@Injectable 并在类中定义标题,我通过@Injectable() 测试成功 export class CustomRequestOptions extends BaseRequestOptions { headers:Headers = new Headers({ 'Authorization': 'xxx' }); }
                      • 好吧,我在 2.0.0 中做了这个,没有检查 2.0.1
                      • 这里的重要提示我遇到了一个问题,即使使用@Inject/@Injectable 也无法向CustomRequestOptions 注入任何东西。我意识到的解决方案是扩展RequestOptions,而不是BaseRequestOptions。提供 BaseRequestOptions 将不起作用,但扩展 RequestOptions 反而会使 DI 再次工作。
                      • 这个解决方案很简单,但是如果用户注销并重新登录并且他的令牌更改 - 它将不再起作用,因为 Authorization 标头仅在应用程序初始化时设置一次。
                      • 是的,正确的@AlexeyVParamonov。这仅在令牌被设置一次时才有用。否则我们会像你说的那样为这个案例编写拦截器。
                      猜你喜欢
                      • 2017-12-02
                      • 1970-01-01
                      • 1970-01-01
                      • 2021-07-16
                      • 1970-01-01
                      • 2012-08-06
                      • 1970-01-01
                      • 1970-01-01
                      • 2019-10-13
                      相关资源
                      最近更新 更多