Angular 2中的Oauth 2弹出窗口

7

我正在将现有的angular应用程序升级/重写为使用angular2。我的问题是,我想在新的弹出窗口中打开OAuth流程,并且一旦OAuth流程完成,使用window.postMessage与angular 2应用程序进行通信,以表明OAuth流程成功。

目前,在angular 2服务中我拥有以下内容:

export class ApiService { 
    constructor(private _loggedInService: LoggedInService) {
        window.addEventListener('message', this.onPostMessage, false);
     }

    startOAuthFlow() {
       var options = 'left=100,top=10,width=400,height=500';
       window.open('http://site/connect-auth', , options);
    }

    onPostMessage(event) {
      if(event.data.status === "200") {
          // Use an EventEmitter to notify the other components that user logged in
          this._loggedInService.Stream.emit(null);
      }
    }

}

此模板在OAuth流程结束时加载。

<html>
  <head>
    <title>OAuth callback</title>
    <script>
      var POST_ORIGIN_URI = 'localhost:8000';
      var message = {"status": "200", "jwt":"2"};
      window.opener.postMessage(message, POST_ORIGIN_URI);
      window.close();
    </script>
  </head>
</html>

像这样使用window.addEventListener似乎会完全破坏angular 2应用程序,导致this无法引用。

所以我的问题是,我能否使用window.addEventListener或者不应该使用postMessage来与angular2应用程序进行通信?

** 完全是angular2新手,非常感谢您的帮助。

4个回答

6

我在Github上有一个完整的Angular2 OAuth2骨架应用程序,您可以参考。

它使用一个Auth服务来进行OAuth2 Implicit授权,该服务又使用Window服务创建弹出窗口。然后它监视该窗口上的URL以获取访问令牌。

您可以通过此处访问演示OAuth2 Angular代码(使用Webpack)。

这是Auth服务中的登录例程,它将让您了解发生了什么,而不必查看整个项目。我在其中添加了一些额外的注释供您参考。

public doLogin() {
    var loopCount = this.loopCount;
    this.windowHandle = this.windows.createWindow(this.oAuthTokenUrl, 'OAuth2 Login');

    this.intervalId = setInterval(() => {
        if (loopCount-- < 0) { // if we get below 0, it's a timeout and we close the window
            clearInterval(this.intervalId);
            this.emitAuthStatus(false);
            this.windowHandle.close();
        } else { // otherwise we check the URL of the window
            var href:string;
            try {
                href = this.windowHandle.location.href;
            } catch (e) {
                //console.log('Error:', e);
            }
            if (href != null) { // if the URL is not null
                var re = /access_token=(.*)/;
                var found = href.match(re);
                if (found) { // and if the URL has an access token then process the URL for access token and expiration time
                    console.log("Callback URL:", href);
                    clearInterval(this.intervalId);
                    var parsed = this.parse(href.substr(this.oAuthCallbackUrl.length + 1));
                    var expiresSeconds = Number(parsed.expires_in) || 1800;

                    this.token = parsed.access_token;
                    if (this.token) {
                        this.authenticated = true;
                    }

                    this.startExpiresTimer(expiresSeconds);
                    this.expires = new Date();
                    this.expires = this.expires.setSeconds(this.expires.getSeconds() + expiresSeconds);

                    this.windowHandle.close();
                    this.emitAuthStatus(true);
                    this.fetchUserInfo();
                }
            }
        }
    }, this.intervalLength);
}

如果您有任何问题或在运行应用程序时遇到问题,请随时提问。


1
我在Chrome上的href = this.windowHandle.location.href;这一行代码处遇到了一个错误,错误信息是Error: DOMException: Blocked a frame with origin "http://localhost:[PORT]" from accessing a cross-origin frame. - 我是唯一遇到这个问题的吗?自从这篇文章发布以来,Chrome是否更新了他们的安全策略? - BoDeX
@[Michael Oryl] 这个能用于授权授予流程吗? - godhar

3

经过一番调查,我发现问题出在对this的解除引用上。这个Github Wiki帮助我更好地理解了这个问题。

为了解决这个问题,我需要做几件事情。首先,我创建了一个服务来封装添加事件监听器的操作。

import {BrowserDomAdapter} from 'angular2/platform/browser';

export class PostMessageService {
   dom = new BrowserDomAdapter();
   addPostMessageListener(fn: EventListener): void {
     this.dom.getGlobalEventTarget('window').addEventListener('message', fn,false)
   }
}

然后使用addPostMessageListener,我可以将一个函数附加到我的其他服务中以触发。

constructor(public _postMessageService: PostMessageService,
    public _router: Router) {
    // Set up a Post Message Listener
    this._postMessageService.addPostMessageListener((event) => 
          this.onPostMessage(event)); // This is the important as it means I keep the reference to this

}

然后它按照我的预期工作,保留对此的引用。

有任何想法如何在 Angular 2 RCs 中不再导出 BrowserDomAdapter 的情况下完成此操作吗? - simon
1
更新:实际上,你可以直接使用 window.addEventListener -- 重要的部分,正如你所指出的,是使用匿名函数来保留执行上下文。谢谢! - simon
你好,是不是可能从同一台机器但不同端口接收消息?我想要从我的jsp/java(8080端口)发送数据到angular2监听器(4200端口)。 - user3145373 ツ

1
我认为这是Angular2的方式:
(Dart代码,但TS应该很相似)
@Injectable()
class SomeService {
  DomAdapter dom;
  SomeService(this.dom) {
    dom.getGlobalEventTarget('window').addEventListener("message", fn, false);
  }
}

谢谢,这帮助我找到了解决方案! - royka
1
我认为现在可以使用 addGlobalEventListener('window', 'message', fn)。请参阅 https://angular.io/api/platform-browser/EventManager。 - Michael Dausmann

0

我曾经花了很长时间来尝试解决这个问题,但最终,对我来说最可靠的方法是将用户重定向到oath页面。

window.location.href = '/auth/logintwitter';

在后端(我使用的是Express)中执行誓言舞蹈,然后重定向回接收前端页面...
res.redirect(`/#/account/twitterReturn?userName=${userName}&token=${token}`);

我的解决方案有一些特殊之处,因为我想在客户端上只使用JsonWebToken而不考虑登录类型,但如果您感兴趣,整个解决方案在这里。

https://github.com/JavascriptMick/learntree.org


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