返回令牌之前的 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_
iOS 11. KVO_IS_RETAINING_ALL_OBSERVERS_OF_THIS_OBJECT_IF_IT_CRASHES_AN_OBSERVER_WAS_OVERRELEASED_OR_