在Angular 4的canactivate守卫中返回一个可观察对象内部的可观察对象。

4

我在路由的canActivate守卫中需要做两件事情。首先,我需要通过调用OpenIdcSecutiry服务来检查用户是否被授权。如果用户已经被授权,那么我还必须通过调用另一个返回包含用户角色的UserProfile的服务来检查用户的角色。我知道我可以返回一个Observable,实际上,如果我只这样做:

return this.oidcSecurityService.getIsAuthorized();

它可以工作,但显然我没有检查用户角色。现在我的代码是这样的:

canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {

return this.oidcSecurityService.getIsAuthorized()
  .map(
    data => {
      if (data === false) {
        this.router.navigate(['/login']);
        return data;
      }
      if (data === true) {
        const roles = route.data['roles'] as Array<string>;
        if (roles) {
        this.profileService.getObservableUserProfileFromServer().map(userProfile => {
          const userRoles = userProfile.Roles;
          for (const role of roles) {
            if (userRoles.indexOf(role) > -1) {
              return true;
            }
          }
          return false;
        });
        }
        return data;
      }
    },
    error => {
      this.router.navigate(['/login']);
      return error;
    }
  );

this.profileService.getObservableUserProfileFromServer()从未被调用,我不知道如何在确认用户已经获得授权后检查用户角色。我该怎么做呢?我能在我的可观察对象中返回另一个可观察对象吗?

编辑1:我将代码更改为以下内容:

canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {

return this.oidcSecurityService.getIsAuthorized()
  .map(
    data => {
      if (data === false) {
        this.router.navigate(['/login']);
        return data;
      }
      if (data === true) {
        const roles = route.data['roles'] as Array<string>;
        if (roles) {
          return this.profileService.getObservableUserProfileFromServer().toPromise().then(userProfile => {
              const userRoles = userProfile.Roles;
              for (const role of roles) {
                if (userRoles.indexOf(role) > -1) {
                  return true;
                }
              }
              return false;
          }
            );
        }
        return data;
      }
    },
    error => {
      this.router.navigate(['/login']);
      return error;
    }
  );

// 返回此.oidcSecurityService.getIsAuthorized();

}

现在我可以看到如何调用this.profileService.getObservableUserProfileFromServer并从服务器接收结果,但是结果没有在canActivate守卫中进行评估,它只是忽略了结果,并且显示导航已被取消。

最终编辑:

@llai 给了我答案,必须使用两个switchmap来链接多个observables,最终代码如下:

canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> {

return this.oidcSecurityService.getIsAuthorized()
  .switchMap(
    data => {
      if (data === false) {
        this.router.navigate(['/login']);
        return Observable.of(false); // user not logged in, canActivate = false
      } else if (data === true) {
        const roles = route.data['roles'] as Array<string>;
        if (roles) {
          // return inner observable
          return this.profileService.getObservableUserProfileFromServer()
            .switchMap(userProfile => {
              const userRoles = userProfile.Roles;
              for (const role of roles) {
                if (userRoles.indexOf(role) > -1) {
                  // role found, canActivate = true
                  return Observable.of(true);
                }
              }
              // no matching role, canActivate = false
              return Observable.of(false);
            });
        } else {
          // no roles defined in route data, canActivate = false
          return Observable.of(true);
        }
      }
    });

}


你尝试使用switchMap而不是map并返回它了吗?'return this.profileService.getObservableUserProfileFromServer().switchMap' - Christian Benseler
为了测试,我把代码改成了:if (roles) { return this.profileService.getObservableUserProfileFromServer().switchMap(userProfile => { return Observable.of(true); });但它甚至没有调用 userProfile 服务的服务器。 - pablito
1个回答

6
正如克里斯蒂安在评论中提到的那样,您应该使用 switchMap 来链接您的 observables。
在您的第一次实现中,您没有返回您的内部 observable,因此路由器从未订阅它,因此它永远不会被调用。
在您的第二个实现中,您在 map 函数中“订阅”(转换为 promise + then)可观察对象,这使得内部 observable 触发。但是,由于 observable 没有返回给守卫,因此守卫不会等待内部 observable 完成。
相反,您应该链接 observables 并将链接的序列返回给守卫。这将允许守卫订阅该序列并等待正确的响应。
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> {

    return this.oidcSecurityService.getIsAuthorized()
      .switchMap(
        data => {
          if (data === false) {
            this.router.navigate(['/login']);
            return Observable.of(false); // user not logged in, canActivate = false
          }else if (data === true) {
            const roles = route.data['roles'] as Array<string>;
            if (roles) {
              // return inner observable
              return this.profileService.getObservableUserProfileFromServer()
                .map(userProfile => {
                  const userRoles = userProfile.Roles;
                  for (const role of roles) {
                    if (userRoles.indexOf(role) > -1) {
                      // role found, canActivate = true
                      return true;
                    }
                  }
                  // no matching role, canActivate = false
                  return false;
                });
            }else{
              // no roles defined in route data, canActivate = false
              return Observable.of(false)
            }
          }
        }
      }
    );

}

@pablito 你在哪一行得到了那个错误?请确保在switchMap中始终返回一个带布尔值的observable。另外,指定canActivate守卫应该期望 Observable<boolean> - LLai
在这行代码中:return this.oidcSecurityService.getIsAuthorized().switchMap( - pablito
1
@pablito 我已经更新了我的回答。尝试删除你的第二个输入以切换Map err => {...}。switchMap的第二个输入实际上是一个结果选择器(https://www.learnrxjs.io/operators/transformation/switchmap.html),它可以操纵switchMap的输出。我们这里不需要它。 - LLai
我已经更新了代码,删除了 err => 部分,现在它显示:无法从使用中推断出类型参数 'R' 的类型参数。请考虑明确指定类型参数。 类型参数候选项 'boolean' 不是有效的类型参数,因为它不是候选项 'Observable<boolean>' 的超类型。(同一行,第一句话) - pablito
1
你是对的,使用第二个map也是正确的,但必须返回布尔值。最后我使用了先用switchmap再用返回布尔值的map。非常感谢! - pablito
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接