返回令牌之前的 AcquireToken Observable 错误

Posted

技术标签:

【中文标题】返回令牌之前的 AcquireToken Observable 错误【英文标题】:AcquireToken Observable errors before returning token 【发布时间】:2018-08-30 08:26:06 【问题描述】:

我正在使用 adal-angular4 库在 Angular 4 单页应用程序中实现隐式流程。这个应用程序调用一个 web api 来显示结果。这两个应用程序都托管在 azure 中(在特定租户中)并进行了适当的注册,并且配置工作正常。

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

这里是各个组件的相关代码

1. AuthenticationGuard.ts

从 '@angular/core' 导入 Injectable ; 从 'rxjs/Observable' 导入 Observable ; 从'@angular/router'导入路由器、CanActivate、CanActivateChild、ActivatedRouteSnapshot、RouterStateSnapshot、NavigationExtras; 从“adal-angular4”导入 AdalService ;

@Injectable() 导出类 AuthenticationGuard 实现 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);

    appcomponent.ts

    从'@angular/core'导入组件,OnInit; 从'adal-angular4'导入AdalService; 从'@angular/router'导入路由器; 从'../environments/environment'导入环境;

    @组件( 选择器:'应用程序根', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] ) 导出类 AppComponent 实现 OnInit title = '我的门户'; isLoggedIn = 假; 登录用户:字符串; 私人身体标签:任何; 构造函数(私有 adalService:AdalService,私有路由器:路由器) this.adalService.init(environment.azureConfig); this.bodyTag = document.getElementsByTagName("body")

    ngOnInit() console.log("AppComponent 初始化"); this.adalService.handleWindowCallback(); 如果(this.adalService.userInfo.authenticated) this.isLoggedIn = this.adalService.userInfo.authenticated; this.loggedInUser = this.adalService.userInfo.profile.name; document.addEventListener("keydown", () => this.handleEvt);

    注销():无效 this.adalService.logOut();

    LoginComponent.ts

    从'@angular/core'导入组件,OnInit,Injectable,Inject; 从'@angular/router'导入ActivatedRoute,路由器; 从'adal-angular4'导入AdalService; 从 '../app.constants' 导入 APP_CONFIG, AppConfig ;

    @组件( 选择器:'应用程序登录', templateUrl: './login.component.html', styleUrls: ['./login.component.css'] ) 导出类 LoginComponent 实现 OnInit

    isLoggedIn = false; 登录用户:字符串; localLoggedInUser:任何; tokenNew: 字符串;

    构造函数( 私有路由:ActivatedRoute, 私有路由器:路由器, 私人 adalService:AdalService, @Inject(APP_CONFIG) 私有appConfig:AppConfig )

    ngOnInit() this.adalService.handleWindowCallback(); 如果(this.adalService.userInfo.authenticated) this.isLoggedIn = this.adalService.userInfo.authenticated; this.loggedInUser = this.adalService.userInfo.profile.name ;

    登录():无效 常量 returnUrl = this.route.snapshot.queryParams['returnUrl'] || '/'; 如果(this.adalService.userInfo.authenticated) this.router.navigate([returnUrl]); 别的 this.adalService.login();

    ListService.ts(负责通过 getAll() 方法进行第一次 API 调用。

    从 '@angular/core' 导入 Injectable, OnInit, Inject ; 从'@angular/http'导入Headers, Http, Response, RequestOptions; 从 '@angular/common/http' 导入 HttpClient, HttpHeaders ; 从 'rxjs/Observable' 导入 Observable ; 导入'../rxjs-extensions'; 从 '../app.constants' 导入 APP_CONFIG, AppConfig ; @Injectable() 导出类 ProductService 实现 OnInit 标头:标头; 令牌新:字符串; 项目:产品[]; 登录用户名:字符串; 登录用户电子邮件:字符串; baseUrl = 'https://myproductsapi/'; productListEndpoint = this.baseUrl + '/products'; 构造函数(私有http:HttpClient,私有httpBase:Http, @Inject(APP_CONFIG) 私有 appConfig:AppConfig,私有 adalService:AdalService) this.adalService.handleWindowCallback(); this.setLoggedInUserDetails();

    public getAllProducts(): Observable> 返回 this.httpBase.get(this.productListEndpoint, this.getRequestOptionsWithHeadersForHTTP()) .map(响应 => 常量 tmp = response.json(); 返回 tmp; );

私有 getRequestOptionsWithHeadersForHTTP(): RequestOptions this.attemptAcquireToken(); this.tokenNew = this.adalService.getCachedToken(this.appConfig.resourceApplicationId); const headersNew = new Headers( 'Content-Type': 'application/json' ); headersNew.append('授权', '承载' + this.tokenNew); 常量选项=新的请求选项(); 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 之前如何确保令牌存在 - 就目前而言,检索产品列表的方法调用顺序如下

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

    getAllProducts() 调用 getRequestOptionsWithHeadersForHTTP() 来获取 RequestOptions (headers) 与之前的 http 请求相关联 向 api 端点发出请求。

    getRequestOptionsWithHeadersForHTTP() 调用尝试AcquireToken() 以 从 adal 服务获取令牌。

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

这只发生在用户第一次登录并且缓存中没有令牌时。对于后续尝试,这可以正常工作(检索缓存的令牌)。

【问题讨论】:

【参考方案1】:

你可以像这样使用 Promise 或链 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);
        
    );

等等……

【讨论】:

感谢您的回复。我已经更改了返回 Observables 的方法。但它与“订阅不是属性”错误。我确定我没有返回适当的 Observables 或在上游方法之一中正确处理它们 - 我应该一直返回 Observables,一直到 main 方法吗?我应该对所有这些 Observable 使用“.subscribe”吗?请在问题中查看我更新的代码。谢谢 @aiguo - 不。不得不恢复到我的第一个实现 - 这是没有承诺的。它第一次尝试获取令牌时给了我“错误”,但随后的请求很好。这是在内部应用程序中,因此利益相关者没有太多的反对——他们现在就接受这个。但我对找到正确的方法非常感兴趣。 @nesh_s 好的。我正在做非常相似的事情,并在我的路由守卫中使用acquireToken,就像你在上面展示的那样。但是,我面临的问题是在 acquireToken 成功正文中(即当令牌更新成功时),我返回 Observable.of(true),但没有发生重定向。我只是看到一个空白页面,好像路由保护不允许处理,直到我再次刷新页面然后将我带到正确的页面,因为直到那时令牌才被刷新。 你认为你可以发布一些代码sn-p,我可以看看。重定向似乎更像是一个路径/路由问题,而不是令牌刷新本身 - 我可能错了,代码 sn-p 在这里很有用。

以上是关于返回令牌之前的 AcquireToken Observable 错误的主要内容,如果未能解决你的问题,请参考以下文章

AcquireToken(string resource, string clientId, UserCredential userCredential) 不适用于 Azure 身份验证

ADAL AcquireToken Windows 身份验证 UWP

在完成块完成之前返回的函数

iOS 11. KVO_IS_RETAINING_ALL_OBSERVERS_OF_THIS_OBJECT_IF_IT_CRASHES_AN_OBSERVER_WAS_OVERRELEASED_OR_

Graphql模式返回null [重复]

iOS 11. KVO_IS_RETAINING_ALL_OBSERVERS_OF_THIS_OBJECT_IF_IT_CRASHES_AN_OBSERVER_WAS_OVERRELEASED_OR_