Gahing's blog Gahing's blog
首页
知识体系
  • 前端基础
  • 应用框架
  • 工程能力
  • 应用基础
  • 专业领域
  • 业务场景
  • 前端晋升 (opens new window)
  • Git
  • 网络基础
  • 算法
  • 数据结构
  • 编程范式
  • 编解码
  • Linux
  • AIGC
  • 其他领域

    • 客户端
    • 服务端
    • 产品设计
软素质
  • 面试经验
  • 人生总结
  • 个人简历
  • 知识卡片
  • 灵感记录
  • 实用技巧
  • 知识科普
  • 友情链接
  • 美食推荐 (opens new window)
  • 收藏夹

    • 优质前端信息源 (opens new window)
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

Gahing / francecil

To be best
首页
知识体系
  • 前端基础
  • 应用框架
  • 工程能力
  • 应用基础
  • 专业领域
  • 业务场景
  • 前端晋升 (opens new window)
  • Git
  • 网络基础
  • 算法
  • 数据结构
  • 编程范式
  • 编解码
  • Linux
  • AIGC
  • 其他领域

    • 客户端
    • 服务端
    • 产品设计
软素质
  • 面试经验
  • 人生总结
  • 个人简历
  • 知识卡片
  • 灵感记录
  • 实用技巧
  • 知识科普
  • 友情链接
  • 美食推荐 (opens new window)
  • 收藏夹

    • 优质前端信息源 (opens new window)
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 实现第三方登录无刷新更新页面

    • 需求背景
      • 首先是页面自动关闭
        • 那以上方案有什么问题么?
          • 总结
            • 参考文档
            gahing
            2020-04-12
            随笔 2020
            目录

            实现第三方登录无刷新更新页面

            # 需求背景

            最近在研究 iqiyi 的登录流程,发现他们的第三方登录体验挺好的。

            在目标页面(以下目标页面均指用户当前浏览的页面)点击登录,弹出登录弹框,选择第三方登录,比如 qq,微信,点击则新开页面。

            在新页面中登录完毕,页面会自动关闭,且刚刚那个页面不刷新且自动进行状态变更

            # 首先是页面自动关闭

            这个很简单,通过 window.close() 即可实现

            但是问题是,第三方登录页一般不会自己在代码中做这件事,毕竟一般登录后是重定向到某个页面(通过 ticket 写入 cookie )而不是关闭当前页面

            那么如何关闭呢?其实也很简单,让目标页面执行登录页的关闭即可

            newWin = window.open("//open.weixin.qq.com")
            // 在某个时机
            newWin.close()
            
            1
            2
            3

            至于这个时机,就是登录成功之后。

            那么目标页面如何知道登录成功了?

            你可能会想说可以利用轮询啊,长连接啊,再或者 websocket 咯

            这个是可以实现,但是额外浪费了性能,其实在当前页面(登录完重定向的页面)通过 window.opener 操作目标页面

            至于这东西是啥,大家应该都知道,不懂的看 MDN

            所以这就要求,登录完我们最后重定向的页面除了写入 cookie 的作用,还有就是操作 window.opener

            比如这是 iqiyi 第三方登录后重定向的页面 https://passport.iqiyi.com/oauth/closepage.php?fromurl=&from=29&isNew=0

            其页面代码就是以下简单的几行

            <html><head><script>
            
            //成功调用
            document.domain="iqiyi.com";
            window.opener.lib.__callbacks__._oAuthSuccess('http://passport.iqiyi.com/pages/user/success.action');
            
            </script>
            
            </head><body></body></html>
            
            1
            2
            3
            4
            5
            6
            7
            8
            9

            这里需要将 domain 设为一致,才能操作相同 domain 的 window

            是的, www.iqiyi.com 的 domain 也被改为了 "iqiyi.com" 了,同样的, iqiyi 的其他子域名比如 https://edu.iqiyi.com/ 等都改写了 domain,使得全站都能顺滑的实现无刷新登录

            最后看看代码中的 window.opener.lib.__callbacks__._oAuthSuccess(xxx) ,其实作用也很简单,一是关闭新开页面,二是获取用户信息并更新状态

            这样差不多就实现了我们的需求,另外还有一点没提到的是,写入 cookie 的时候,domain iqiyi 写的是二级域名 .iqiyi.com ,这样其他三级域名如 edu.iqiyi.com 才能共用 cookie

            PS:在测试过程中发现有 iqiyi 有两个bug(截止 20/04/12),当然,普通用户使用一般不会出现。。

            • 若登录弹框关闭,第三方登录成功后不会自动关闭页面且不进行用户信息请求,这个是由于 _oAuthSuccess 其实是调用的登录窗口 iframe 中的方法
            • 手动调用 _oAuthSuccess 方法,头像变成登录后的默认头像,此时其实并没有登录

            # 那以上方案有什么问题么?

            除了全站需要改造 document.domain 外,正常来说是没有问题,但是可能会遇到不能修改 domain 的场景,你要是问我有哪些场景,emmmm...反正我就是为了说我的新方案的,新方案就是 postMessage

            我现在能想到的一个,比如最后一个重定向的最后一个页面不是 .iqiyi.com域名,而是其他页面(可能是为了集团内多个产品复用吧),比如 util.baidu.com

            www.iqiyi.com

            newWin = window.open("//util.baidu.com")
            window.addEventListener("message",(evt)=>{
                // 根据信息来源进行特殊处理,避免受其他 postMessage 影响
                if(evt.origin === "https://util.baidu.com"){
                    console.log(evt.data)
                    newWin.close()
                    // or
                    // evt.source.close()
                }
            })
            
            1
            2
            3
            4
            5
            6
            7
            8
            9
            10

            util.baidu.com

            window.opener.postMessage("test","*") //所有
            
            1

            可以在 www.iqiyi.com 中看到输出,且 util.baidu.com 自动关闭

            当然该方案也有不好的地方,就是 postMessage 使用不当会导致安全问题,主要有以下2点

            1. postMessage 第二个参数 targetOrigin 设置为 * 可能导致数据泄露
              比如使用了某个不安全的第三方登录页面,其对目标网站进行了重定向,比如变成 www.ihack.com ,第三方登录后通过 postMessage 传递隐私信息,由于设置的是 * ,导致 www.ihack.com 可以收到隐私信息。当然你后面发现这个 ihack 网站不正常也没用,因为数据已经泄露了。
              解决方案就是 targetOrigin 设置为特定的地址比如 edu.iqiyi.como ,这就需要我们将当前页面作为传递到重定向页面
              举例,像第三方登录页传递这样的参数 -- &redirect_uri=https://passport.iqiyi.com?targetOrigin=edu.iqiyi.com , 第三方登录后重定向到 passport 写入 cookie ,再次重定向到 https://util.baidu.com?targetOrigin=edu.iqiyi.com ,再当前页面读取 targetOrigin 参数并传递 postMessage 。整个就是这样的一个过程
              当然在本例中不需要传递隐私信息

            2. 接收 message 时请始终使用 origin 和 source 属性验证发件人的身份
              比如点击打开了用户评论的 url(某个黑客网站),其对目标页面发起 postMessage ,而我们如果在收到 message 时不进行验证就执行危险操作时,可能就会导致安全问题
              另外,如果回调执行安全性依赖于消息的话(比如执行 eval(msg.data)),即使检查通过,还需要对消息格式再一次检查,避免因信任网站收到跨站脚本攻击而导致本网站也遭到攻击 当然在本例中只是简单的关闭新开页面及发送请求,没有什么危险操作,检查下 origin 即可

            # 总结

            domain 适用于大部分情景,有全站改造 document.domain 的成本,且最后重定向页面需要是相同的 domain

            postMessage 适用于所有情景,对于安全问题需要额外处理

            两种方案各有千秋,看就具体的场景啦~

            # 参考文档

            • postMessage (opens new window)
            编辑 (opens new window)
            #随笔
            上次更新: 2023/08/26, 10:18:07
            最近更新
            01
            我的 2024 总结
            12-31
            02
            浅谈代码质量与量化指标
            08-27
            03
            快速理解 JS 装饰器
            08-26
            更多文章>
            Theme by Vdoing | Copyright © 2016-2025 Gahing | 闽ICP备19024221号-1
            • 跟随系统
            • 浅色模式
            • 深色模式
            • 阅读模式