2

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

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

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

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

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

对我做错的事情的任何更正也将不胜感激。这是我第一个使用 Angular 的原型。


更新

在应用 Ben 的建议并花时间了解 APP_INITIALIZER(标记为实验性且 imo 记录非常稀少)之后,感觉有点矫枉过正。我以 Configuration 类的以下自定义提供程序结束,该提供程序被注入到使用 Swagger-CodeGen 生成的 TypeScript-Angular2 服务代码中:

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 生成的代码中调用,并返回最新的 jwt 以用于 HTTP 标头。感觉很干净,而且很有效。如果(以及为什么)这是解决我的问题的错误方法,请告诉我。

4

2 回答 2

1

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

于 2017-05-26T20:38:17.840 回答
0

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

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

或者干脆

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

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

所以我同时使用的解决方案是 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);
    }
  }
}

What I don't like about this solution is that it will intercept all HTTPClient calls and that's why I had to add if (req.url.startsWith(environment.usersAPIBasePath) ... but if all your HTTPClient calls are going to be to your API you can remove that part of the conditional.

This is how that app's providers goes in app.module.ts:

  providers: [
    ...
    { provide: BASE_PATH, useValue: environment.usersAPIBasePath },
    { provide: HTTP_INTERCEPTORS, useClass: UsersApiAuthInterceptorService, multi: true },
  ],
于 2018-10-16T22:38:28.430 回答