【问题标题】:Angular HTTP Client returning null inside interceptorAngular HTTP客户端在拦截器内返回null
【发布时间】:2017-09-04 04:12:14
【问题描述】:

在下面的代码中,我发布了两个代码在控制台中返回的示例。对于拦截器,我应该返回一个可观察的。我已经使用 switchmap 将本地存储承诺转换为可观察的。我仍然使用这种方法返回 null。我的 observable 被包裹在函数周围,所以我应该得到一个不是 null 的值。谢谢!

Interceptor.js

import { fromPromise } from 'rxjs/observable/fromPromise';

accessToken: string; 
emptyToken: any;


    intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {

//example

        this.storage.get('token').then((val) => {
             this.accessToken = val
            return console.log(this.accessToken, ' this is returning null value for token')
        })

//trying to return actual header
          return fromPromise(this.storage.get('token')).switchMap(access => {

            this.accessToken = access
            console.log(this.accessToken, ' this is returning null value for token')
            const authReq = req.clone({
                setHeaders: {
                Authorization: this.accessToken
                }
                });

                return next.handle(authReq)
           })
}
}

我已经添加了下面的更新代码,请忽略上面的代码,仍然得到相同的结果。正如下面的答案之一,他们的所有假设都是正确的。归结为将令牌放入可观察的登录中。问题不在于拦截器,该代码工作正常。我需要以某种方式获取我的异步值而不返回 null。 this.storage.ready() 方法也给了我同样的结果。 身份验证服务在我的拦截器之后被调用,因此我还没有生成任何令牌。我将如何首先调用我的身份验证服务?

login(userName: string, password: string, route: string = null): any {
     this.storage.set('tokens', 'able to retrieve this token value inside interceptor');

    this.logout(false);

    this.doLogin(userName, password)
      .subscribe(response => {

        let token:string = JSON.stringify(response.access_token);
        this.storage.set('token', token)

      }

      }

interceptor.ts

get(url: string, options?: RequestOptionsArgs): Observable<Response> {
  return Observable.fromPromise(
    this.getRequestOptionArgs(options)
  ).mergeMap((options) => {
    return super.get(url, options)
  })
  }

private getRequestOptionArgs(options?: RequestOptionsArgs) {
    return this.storage.get('token').then((token) => {
      console.log(token, 'token for the get')
      if (options == null) {
        options = new RequestOptions();
      }

      if (options.headers == null) {
        options.headers = new Headers();
      }

      if (token !== null) {
        options.headers.append('Authorization', 'Bearer ' + token);
      }
      options.headers.append('Content-Type', 'application/json');

      return options;
    });
  }

【问题讨论】:

  • 尝试在.switchMap(...)之前添加.filter(access =&gt; typeof access === string)
  • 我仍然得到相同的结果
  • 对不起 string 需要用引号引起来:typeof access === 'string'
  • 好消息是我不再收到那个空错误了。坏消息是我根本无法恢复访问权限,所以它只是停滞不前。
  • 因为 promise 只会发出一个可观察的 once,在您的情况下显然是 null。你确定你保存了令牌吗?

标签: angular ionic2 ionic3


【解决方案1】:

我认为问题在于您的 StorageService 正在返回 null,而不是拦截器的问题。

存储服务正在使用 Promise,这意味着它只会发出一个值。如果您收到null,那么我猜您还没有存储令牌。

你首先从哪里获得令牌?服务器?在这种情况下,第一个请求将没有令牌。

但是...您说过,当您直接(同步)访问localStorage 时,您将获得一个值,并且一切正常。所以我假设你已经有一个令牌在存储中。

查看Ionic Storage 的文档,似乎还有另一种可能性。存储有一个ready 方法,所以可能存储还没有准备好。

您可以尝试这样的事情(未经测试),在尝试获取令牌之前首先等待准备好的承诺解决:

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

import 'rxjs/add/operator/mergeMap';
import 'rxjs/add/observable/fromPromise';

@Injectable()
export class Interceptor implements HttpInterceptor {

    constructor (private storage: StorageService) {}

    intercept (request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
        // chain the ready promise with the get token promise
        return Observable.fromPromise(this.storage.ready().then(this.storage.get('token')))
            .mergeMap((token: string) => {

                const authReq = request.clone({
                    setHeaders: {
                        Authorization: token
                    }
                });

                return next.handle(authReq);

            });
    }
}

只是猜测...

【讨论】:

  • 这种方法不起作用,尽管您对其他一切都是正确的。发布了我更新的代码。
【解决方案2】:

问题正在发生,因为this.storage.get('token').then() 是一个承诺,而承诺.then() 只是在调用之后才设置this.token

您需要向构造函数添加一个方法,该方法将从 localStorage 获取令牌并将其存储在拦截器中。这将在您创建拦截器实例时触发。

拦截器

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

const tokenStorageKey = 'token'
const tokenType = 'Bearer'

@Injectable()
export class AuthInterceptor implements HttpInterceptor {
    private token: any;
    constructor (private storage: StorageService) {
        this.getToken();
    }

    intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
        let contentTypeSet = req.headers.set('Content-Type', 'application/json');
        let authHeaderSet = contentTypeSet.set('Authorization', `${tokenType} ${this.token}`)

        const authReq = req.clone({
            headers: authHeaderSet
        });

            return next.handle(authReq);
        }
    }

    private getToken(): void{
        this.storage.get(tokenStorageKey).then((token: any) => {
            this.token = token;
        });
    }
}

您还可以添加一个publish 事件,让所有订阅的方法都知道令牌已被获取。

【讨论】:

  • 感谢您的回答!我在这里查看您的答案 -stackoverflow.com/questions/45250609/… 并且我已经这样实现了它。但是,在构造函数中添加 get 令牌时,我得到了相同的结果。
  • 您确定令牌在本地存储中可用吗?
  • 当我调用令牌时,我能够在控制台中获取值。当我使用 this.storage.set('token' , token) 时,我的假设是因为上面的行具有令牌值,即令牌,然后将令牌放入 storage.set 将其保存到本地存储。我还看到了你的 github 代码,我暂时不得不删除 post 拦截器,因为正文没有被 JSON 字符串化,它是一个字符串,如果这有意义的话。但这不是重点。
【解决方案3】:

幸运的是,在开发 JWT 解决方案时(甚至在原生拦截器存在之前),我已经不得不解决这种情况。

你真正想要的行为是这样的:

  1. 发送请求

    1.1。如果本地存储中不存在令牌,则附加 "Authorization Bearer"

    1.2。 else 从本地存储中读取令牌

  2. 使用成功的“授权”令牌获取响应

  3. 将此令牌保存到本地存储

还请记住,所有拦截器都会为每个请求调用。此外,在注册时,订单很重要! 流程是这样的:

请求创建 -> 拦截器A,拦截器B -> 发送到服务器 -> 从服务器接收 -> 拦截器A,拦截器B -> 订阅功能 所以这就是你的this.doLogin(...).subscribe(...)

所以,我所做的并向您建议的是,有两种类型的拦截器。 如果当前用户不存在令牌,则一个 AuthorizationAppendingInterceptor 将附加 Authorization Bearer

第二个应该是……像AuthorizationReceivedInterceptor,它将事件过滤为HttpRespone 的实例,并查找标头Authorization,该标头将在成功登录时由服务器发送。只有这样,在接收拦截器中,您才能将令牌写入本地存储。


一些代码:

AuthorizationAppendingInterceptor

鉴于this.authService 是您处理令牌内容的实例

intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
  // Get the auth header from the service.
  const headers: HttpHeaders = this.authService.appendAuthorizationHeader(req.headers);

  // Clone the request to add the new header.
  const authReq: HttpRequest<any> = req.clone({ headers: headers });

  // Pass on the cloned request instead of the original request.
  return next.handle(authReq);
}

授权接收拦截器

鉴于AuthService.AUTHORIZATION_HEADER = 'Authorization'this.authService 是您处理令牌内容的实例。

intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
return next
  .handle(req)
  .do(event => {
        if (event instanceof HttpResponse) {
          const response: HttpResponse<any> = event as HttpResponse<any>;
          if (!response.headers || !response.headers.has(AuthService.AUTHORIZATION_HEADER)) {
            return;
          }

          this.authService.setToken(response.headers.get(AuthService.AUTHORIZATION_HEADER));
        }
      }
  );

}


为了完整起见,这里是来自authServiceappendAuthorizationHeader 方法

public appendAuthorizationHeader(headers: HttpHeaders): HttpHeaders {
  if (!this.token || !this.token.canAppendToHeaders()) { return; }
  return headers.append(AuthService.AUTHORIZATION_HEADER, this.token.getTokenString());
}

实现此代码后,您的身份验证机制无需对应用程序逻辑进行任何更多调整即可工作,并且还支持自动重新登录,因为一旦令牌无效,AuthorizationAppendingInterceptor 将自动触发,之后,@987654334 @ 将处理本地存储令牌的更新。

【讨论】:

    【解决方案4】:

    你为什么不为你的服务做这样的事情:

    createAThing(userId :string, timestamp :string, reason: string): Promise<Appointment> {
    
        let bodySearchParam = new URLSearchParams();
        bodySearchParam.append('userId', userId );
        bodySearchParam.append('timestamp', this.datetotimestamp(timestamp).toString());
        bodySearchParam.append('reason', reason);
        let body = bodySearchParam.toString();
    
        console.log(body) // check if my body is ok
        return this.http.post(this.AppointmentUrlWrite,body)
                      .toPromise()
                      .then(response =>
                        console.log(response.json().data)
                      )
                      .catch(this.handleError);
      }
    

    以我的拙见,它会比你的方法更好

    【讨论】:

    • 不幸的是,使用 Promise 也得到了相同的结果。
    猜你喜欢
    • 2018-05-17
    • 2020-03-14
    • 1970-01-01
    • 2023-03-13
    • 1970-01-01
    • 1970-01-01
    • 2017-11-07
    • 2018-05-19
    • 2016-01-06
    相关资源
    最近更新 更多