【问题标题】:AcquireToken Observable errors before returning token返回令牌之前的 AcquireToken Observable 错误
【发布时间】: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);
}}
  1. 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(); }}

  2. 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(); } } }

  3. 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 之前如何确保令牌存在 - 就目前而言,检索产品列表的方法调用顺序如下

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

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

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

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

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

【问题讨论】:

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


    【解决方案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 在这里很有用。
    猜你喜欢
    • 2018-04-05
    • 2018-06-17
    • 1970-01-01
    • 1970-01-01
    • 2015-05-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多