返回令牌之前的AcquireToken Observable错误

时间:2018-03-21 09:32:00

标签: angular azure oauth azure-active-directory adal

我在角度4单页面应用程序中使用adal-angular4库进行隐式流程实现。此应用程序调用web api来显示结果。这两个应用程序都托管在azure(在特定租户中)并正确注册并且配置有效。

我面临的问题是,当第一次调用API时,getstoken不会立即返回令牌,它会出错。作为错误处理的一部分,这会导致页面错误(我自觉的决定)。但是在几秒钟内,acquiretoken返回令牌。因此,如果我在几秒钟后刷新页面,则api调用成功,一切都按预期工作。

以下是各种组件的相关代码

1. AuthenticationGuard.ts 

import { Injectable } from '@angular/core'; import { Observable } from 'rxjs/Observable'; import { Router, CanActivate, CanActivateChild, ActivatedRouteSnapshot, RouterStateSnapshot, NavigationExtras } from '@angular/router'; import { AdalService } from 'adal-angular4';

@Injectable() export class AuthenticationGuard implements CanActivate, CanActivateChild {

constructor(
    private router: Router,
    private adalSvc: AdalService
) { }

canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
    if (this.adalSvc.userInfo.authenticated) {
        return true;
    } else {
        this.router.navigate(['/login'], { queryParams: { returnUrl: state.url } });
        return false;
    }
}

canActivateChild(childRoute: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
    return this.canActivate(childRoute, state);
}}
  1. appcomponent.ts

    import { Component, OnInit } from '@angular/core'; import { AdalService } from 'adal-angular4'; import { Router } from '@angular/router'; import { environment } from '../environments/environment';

    @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent implements OnInit { title = 'My portal'; isLoggedIn = false; loggedInUser: string; private bodyTag: any; constructor(private adalService: AdalService, private router: Router) { this.adalService.init(environment.azureConfig); this.bodyTag = document.getElementsByTagName("body") }

    ngOnInit() { console.log("AppComponent Init"); this.adalService.handleWindowCallback(); if (this.adalService.userInfo.authenticated) { this.isLoggedIn = this.adalService.userInfo.authenticated; this.loggedInUser = this.adalService.userInfo.profile.name; } document.addEventListener("keydown", () => this.handleEvt); }

    logout(): void { this.adalService.logOut(); }}

  2. LoginComponent.ts

    import { Component, OnInit, Injectable, Inject } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; import { AdalService } from 'adal-angular4'; import { APP_CONFIG, AppConfig } from '../app.constants';

    @Component({ selector: 'app-login', templateUrl: './login.component.html', styleUrls: ['./login.component.css'] }) export class LoginComponent implements OnInit {

    isLoggedIn = false; loggedInUser: string; localLoggedInUser: any; tokenNew: string;

    constructor( private route: ActivatedRoute, private router: Router, private adalService: AdalService, @Inject(APP_CONFIG) private appConfig: AppConfig ) { }

    ngOnInit() { this.adalService.handleWindowCallback(); if (this.adalService.userInfo.authenticated) { this.isLoggedIn = this.adalService.userInfo.authenticated; this.loggedInUser = this.adalService.userInfo.profile.name ; } }

    login(): void { const returnUrl = this.route.snapshot.queryParams['returnUrl'] || '/'; if (this.adalService.userInfo.authenticated) { this.router.navigate([returnUrl]); } else { this.adalService.login(); } } }

  3. ListService.ts (responsible for making the first API call via the getAll() method.

    import { Injectable, OnInit, Inject } from '@angular/core'; import {Headers, Http, Response, RequestOptions} from '@angular/http'; import { HttpClient, HttpHeaders } from '@angular/common/http'; import { Observable } from 'rxjs/Observable'; import '../rxjs-extensions'; import { APP_CONFIG, AppConfig } from '../app.constants'; @Injectable() export class ProductService implements OnInit { headers: Headers; tokenNew: string; items: Products[]; loggedInUserName: string; loggedInUserEmail: string; baseUrl = 'https://myproductsapi/'; productListEndpoint = this.baseUrl + '/products'; constructor( private http: HttpClient, private httpBase: Http, @Inject(APP_CONFIG) private appConfig: AppConfig, private adalService: AdalService) { this.adalService.handleWindowCallback(); this.setLoggedInUserDetails(); }

    public getAllProducts(): Observable> { return this.httpBase.get(this.productListEndpoint, this.getRequestOptionsWithHeadersForHTTP()) .map(response => { const tmp = response.json(); return tmp; }); }

private getRequestOptionsWithHeadersForHTTP(): RequestOptions { this.attemptAcquireToken(); this.tokenNew = this.adalService.getCachedToken(this.appConfig.resourceApplicationId); const headersNew = new Headers({ 'Content-Type': 'application/json' }); headersNew.append('Authorization', 'Bearer ' + this.tokenNew); const options = new RequestOptions(); options.headers = headersNew;

return options; } private attemptAcquireToken() { this.tokenNew = this.adalService.getCachedToken(this.appConfig.resourceApplicationId); console.log('1. token from attemptacquiretoken ' + this.tokenNew); // this.tokenNew = null; if (this.tokenNew == null) { console.log('2. token new is null - trying to acquiretoken using adal'); this.adalService.acquireToken(this.appConfig.resourceApplicationId).subscribe( token => { console.log('3. token acquired ' + token); //this happens but only a few seconds later this.tokenNew = token; }, // never comes error => { console.log('4. Error when acquiring token'); //this is called straight away console.log(error); }); } }

如何在调用API之前确保令牌存在 - 就目前而言,检索产品列表的方法调用顺序如下:

  1. 调用getAllProducts() - 这是负责的主要方法 调用API。这会启动对其他方法的嵌套调用。

  2. getAllProducts()调用getRequestOptionsWithHeadersForHTTP()来获取 RequestOptions(标头)之前与http请求关联 将请求发送到api端点。

  3. getRequestOptionsWithHeadersForHTTP()调用attemptAcquireToken() 从adal服务获取令牌。

  4. attemptAcquireToken()调用acquiretoken() - 该方法首先检查是否 有一个缓存的令牌,如果没有,它会调用acquiretoken 找回一个。在这里,调用立即执行“错误”lambda中的代码块 - 从而在UI中引发错误。经过几次 在getstoken收回令牌时的秒数 执行“token”lambda中的代码。

  5. 这仅在用户首次登录且缓存中没有令牌时才会发生。对于后续尝试,这可以正常工作(检索缓存的令牌)。

1 个答案:

答案 0 :(得分:0)

你可以像这样使用Promise或chain observable:

private attemptAcquireToken():Observable<boolean> {
    this.tokenNew = this.adalService.getCachedToken(this.appConfig.resourceApplicationId);
    console.log('1. token from attemptacquiretoken ' + this.tokenNew);
    // this.tokenNew = null;
    if (this.tokenNew == null) {
        console.log('2. token new is null - trying to acquiretoken using adal');
        this.adalService.acquireToken(this.appConfig.resourceApplicationId).subscribe(
          token => {
            ...
            return Observable.of(true);
          }, // never comes
          error => {
            ....
            return Observable.of(false);
    }  
}

private getRequestOptionsWithHeadersForHTTP(): Observable<RequestOptions> {
    this.attemptAcquireToken().subsribe(r => {
    if (r !== false) {
            ...
            options.headers = headersNew;
            return Observable.of(options);
        } else {
            return Observable.of(undefined);
        }
    });
}

依旧......