将 OAuth2 访问令牌配置为 typescript-angular2 客户端

Configuring OAuth2 access token to typescript-angular2 client

我不完全理解如何从承诺 (oidc-client-js) 向使用 Swagger-CodeGen 生成的 API 代码提供 OAuth2 访问令牌。

提供常量值很容易,但我如何在下面进行更改以从 oidc-client-js 获取用户的访问令牌?我想知道 "correct" 方式。将此标记粘贴在全局变量中的某个位置很容易。

@NgModule({
  imports: [
    CommonModule,
    ApiModule.forConfig(() => new Configuration({
      accessToken: 'my-access-token' //this can also be a () => string function
    }))
  ],

在带有 OnInit 的普通组件中,我可以从 oidc-client 的 UserManager 实例中获取承诺中的令牌。让这两部分组合在一起让我感到困惑。一个好像是静态配置,另一个需要订阅单例的promise

this.userSubscription = this.authService.getUser().subscribe((user) => {
    if (user) {
        this.access_token = user.access_token;
    }
});

对我做错的事情的任何更正也将不胜感激。这是我使用 Angular.

的第一个原型

更新

在采纳 Ben 的建议并花时间理解 APP_INITIALIZER(在我看来,它被标记为实验性的并且文档很少)之后,感觉有点矫枉过正。我以配置 class 的以下自定义提供程序结束,它被注入 TypeScript-Angular2 使用 Swagger-CodeGen:

生成的服务代码
providers: [
  AuthService,
  AuthGuardService,
  {
    provide: Configuration,
    useFactory: (authSvc: AuthService) => new Configuration({accessToken: authSvc.getAccessToken.bind(authSvc)}),
    deps: [AuthService],
    multi: false
  }
]

我更改了我的 AuthService 以在该服务上存储用户的最新 access_token。 getAccessToken() 方法从 Swagger-CodeGen 生成的代码和 returns HTTP headers 中使用的最新 jwt 调用。它感觉干净而且有效。如果(以及为什么)这是解决我问题的错误方法,请告诉我。

您需要使用 APP_INITIALIZER 到 bootstrap 您的 API 令牌,请查看我的回答 Pass web application context to Angular2 Service 以查看如何执行此操作的示例。

我认为这是一个 swagger-codegen 错误,属性 签名应该是

accessToken?: string | (() => Promise<string>);

或者干脆

accessToken?: (() => Promise<string>);

原因是访问令牌过期,所以每次调用都会进行 客户端应检查令牌是否已过期,如果已过期则请求一个新令牌(令牌刷新),这意味着 HTTP 查询,因此承诺是处理访问令牌的最佳选择。如果您检查 Firebase 的 Javascript API,您会注意到 User.getIdToken() returns 一个承诺,因为它首先检查当前是否已过期,如果是则请求一个新的。

所以我同时使用的解决方案是Angular的HTTP拦截器:

import { Injectable } from '@angular/core';
import {
  HttpEvent, HttpInterceptor, HttpHandler, HttpRequest
} from '@angular/common/http';
import { AngularFireAuth } from '@angular/fire/auth';
import * as firebase from 'firebase/app';
import { from } from 'rxjs';
import { mergeMap } from 'rxjs/operators';

import { environment } from '../environments/environment';

@Injectable({
  providedIn: 'root'
})
export class UsersApiAuthInterceptorService implements HttpInterceptor {

  constructor(private afAuth: AngularFireAuth) { }

  intercept(req: HttpRequest<any>, next: HttpHandler) {
    if (req.url.startsWith(environment.usersAPIBasePath) && this.afAuth.auth.currentUser) {
      return from(this.afAuth.auth.currentUser.getIdToken()).pipe(mergeMap(token => {
        console.log('UsersApiAuthInterceptorService got token', token);
        const authReq = req.clone({
          setHeaders: {
            Authorization: `Bearer ${token}`
          }
        });
        return next.handle(authReq);
      }));
    }
    else {
      return next.handle(req);
    }
  }
}

我不喜欢这个解决方案的地方在于它会拦截所有 HTTPClient 调用,这就是为什么我必须添加 if (req.url.startsWith(environment.usersAPIBasePath) ... 但如果您的所有 HTTPClient 调用都将发送到您的 API 你可以删除那部分条件。

这就是该应用程序的提供程序进入 app.module.ts:

的方式
  providers: [
    ...
    { provide: BASE_PATH, useValue: environment.usersAPIBasePath },
    { provide: HTTP_INTERCEPTORS, useClass: UsersApiAuthInterceptorService, multi: true },
  ],