OAuth 弹窗跨域安全 React.js

Posted

技术标签:

【中文标题】OAuth 弹窗跨域安全 React.js【英文标题】:OAuth popup cross-domain security React.js 【发布时间】:2020-03-03 01:26:57 【问题描述】:

我对如何使用弹出窗口 (window.open) 在 React 中实现 OAuth 感兴趣。

例如我有:

    mysite.com — 这是我打开弹出窗口的地方。 passport.mysite.com/oauth/authorize — 弹出窗口。

主要问题是如何在window.open(弹出窗口)和window.opener 之间建立连接(众所周知,window.opener 由于跨域安全性为空,因此我们不能再使用它了)。

window.opener 每当您导航到不同的主机时都会被删除(出于安全原因),没有办法绕过它。如果可能的话,唯一的选择应该是在一个框架内付款。顶部文档需要保留在同一主机上。

方案:

可能的解决方案:

    使用setInterval 描述的here 检查打开的窗口。 使用cross-storage(恕我直言不值得)。

那么 2019 年最好的推荐方法是什么?

React 包装器 - https://github.com/Ramshackle-Jamathon/react-oauth-popup

【问题讨论】:

2019 年,localStorage 支持要好得多。我会使用 localStorage 方法(在***.com/questions/18625733/… 中描述),因为它似乎不是一个解决方法。父窗口不需要定期检查子窗口状态。setInterval 可以作为 localStorage 的后备 @KhanhTO,是的,我完全同意你关于 localStorage 的看法,但它只适用于同一个域,因此不适用于我的情况 完成 OAuth 后,子窗口将重定向回您的域,您现在与父窗口在同一个域中 @KhanhTO,嗯,这是个好主意!我应该知道.. 如果浏览器在重定向回我们的域后恢复window.opener会更好,但事实并非如此 【参考方案1】:

由Khanh TO 推荐。带有 localStorage 的 OAuth 弹出窗口。基于react-oauth-popup。

方案:

代码:

oauth-popup.tsx:

import React, PureComponent, ReactChild from 'react'

type Props = 
  width: number,
  height: number,
  url: string,
  title: string,
  onClose: () => any,
  onCode: (params: any) => any,
  children?: ReactChild,


export default class OauthPopup extends PureComponent<Props> 

  static defaultProps = 
    onClose: () => ,
    width: 500,
    height: 500,
    url: "",
    title: ""
  ;

  externalWindow: any;
  codeCheck: any;

  componentWillUnmount() 
    if (this.externalWindow) 
      this.externalWindow.close();
    
  

  createPopup = () => 
    const url, title, width, height, onCode = this.props;
    const left = window.screenX + (window.outerWidth - width) / 2;
    const top = window.screenY + (window.outerHeight - height) / 2.5;

    const windowFeatures = `toolbar=0,scrollbars=1,status=1,resizable=0,location=1,menuBar=0,width=$width,height=$height,top=$top,left=$left`;

    this.externalWindow = window.open(
        url,
        title,
        windowFeatures
    );

    const storageListener = () => 
      try 
        if (localStorage.getItem('code')) 
          onCode(localStorage.getItem('code'));
          this.externalWindow.close();
          window.removeEventListener('storage', storageListener);
        
       catch (e) 
        window.removeEventListener('storage', storageListener);
      
    

    window.addEventListener('storage', storageListener);

    this.externalWindow.addEventListener('beforeunload', () => 
      this.props.onClose()
    , false);
  ;

  render() 
    return (
      <div onClick=this.createPopup)>
        this.props.children
      </div>
    );
  

app.tsx

import React, FC from 'react'

const onCode = async (): Promise<undefined> => 
  try 
    const res = await <your_fetch>
   catch (e) 
    console.error(e);
   finally 
    window.localStorage.removeItem('code'); //remove code from localStorage
  


const App: FC = () => (
  <OAuthPopup
    url=<your_url>
    onCode=onCode
    onClose=() => console.log('closed')
    title="<your_title>">
    <button type="button">Enter</button>
  </OAuthPopup>
);

export default App;

【讨论】:

【参考方案2】:

我曾经在使用 window.open/window.opener bug on ms-edge 的 oauth 登录流程中遇到问题

这个问题之前我的流程是

在登录按钮上单击打开一个弹出窗口 成功登录后,oauth 应用重定向到我的域页面 然后我在弹出窗口 (window.opener.fn) 中使用来自 oauth 响应和父窗口的数据调用父窗口的函数,然后关闭子弹出窗口

这个问题之后我的流程是

在登录按钮上单击打开一个弹出窗口 创建一个 setinterval 以防万一(window.opener 未定义) 成功登录后,oauth 应用重定向到我的域页面 检查 window.opener 是否可用,然后从上述流程中执行 #3 并清除Interval 如果 window.opener 不可用,那么由于我在我的域页面上,我尝试设置 localstorage 并尝试从父窗口的 setInterval 函数内部读取 localstorage,然后清除 localstorage 和 setInterval 并继续。 (为了向后兼容)如果 localstorage 也不可用,则设置一个客户端 cookie,其数据的过期时间很短(5-10 秒),并尝试在 setInterval 函数中读取 cookie(document.cookie)父窗口并继续。

【讨论】:

以上是关于OAuth 弹窗跨域安全 React.js的主要内容,如果未能解决你的问题,请参考以下文章

Office 365 OAuth2登录认证如何实现跨域请求?

Chrome 92 破坏性功能,我这弹窗有何用?

laravel 5.5 oauth2.0 跨域问题解决方案

Google OAuth2:重定向已被 CORS 政策阻止:请求需要预检,不允许遵循跨域重定向

vue-resource 怎么解决跨域问题

enablecors-webapi跨域 为何不能实现?都有哪些注意事项