运行时导入 Vue 组件的 webpack 块时如何添加授权标头

Posted

技术标签:

【中文标题】运行时导入 Vue 组件的 webpack 块时如何添加授权标头【英文标题】:How to add authorization header when runtime import webpack chunks of Vue components 【发布时间】:2018-09-18 18:20:54 【问题描述】:

这个任务的目的是让知道组件地址但没有访问令牌的情况下无法下载 Vue 组件包(*.js 文件)。

我正在开发一个访问控制系统和一个用户界面,其中可用组件的集合取决于用户的访问级别。

系统使用JSON API and JWT authorization。为此,客户端使用了 Axios。要构建应用程序,我们使用 Webpack 4,要加载组件,我们使用 vue-loader

用户获得授权后,应用程序向服务器请求一组可用的路由和元数据,然后将动态构造的菜单和路由添加到 VueRouter 对象中。

下面我给出了一个简化的代码。

            import axios from 'axios'
            import router from 'router'

            let API = axios.create(
              baseURL: '/api/v1/',
              headers: 
                Authorization: 'Bearer mySecretToken12345'
              
            )

            let buildRoutesRecursive = jsonRoutes => 
              let routes = []
              jsonRoutes.forEach(r => 
                let path = r.path.slice(1)
                let route = 
                  path: r.path,
                  component: () => import(/* webpackChunkName: "restricted/[request]" */ 'views/restricted/' + path)
                  //example path: 'dashboard/users.vue', 'dashboard/reports.vue', etc...
                
                if (r.children)
                  route.children = buildRoutesRecursive(r.children)
                routes.push(route)
              )
              return routes
            

            API.get('user/routes').then(
              response => 

                /*
                  response.data = 
                        [
                      "path": "/dashboard",
                      "icon": "fas fa-sliders-h",
                              "children": [
                        "path": "/dashboard/users",
                        "icon": "fa fa-users",
                                , 
                        "path": "/dashboard/reports",
                        "icon": "fa fa-indent"
                                
                            ]
                        
                    ]
                */

                let vueRoutes = buildRoutesRecursive(response.data)
                router.addRoutes(vueRoutes)   
              ,
              error => console.log(error)
            )

我遇到的问题是因为 Webpack 通过添加“脚本”元素而不是通过 AJAX 请求加载组件。因此,我不知道如何在此下载中添加授权标头。因此,任何没有令牌的用户都可以通过简单地将他的地址插入浏览器的导航栏来下载私有组件的代码。

理想情况下,我想知道如何使用 Axios 导入 vue 组件。

或者,如何向 HTTP 请求添加授权标头。

【问题讨论】:

在密码后面保护客户端代码并不常见。通常只保护数据就足够了。 【参考方案1】:

我需要类似的东西并想出了以下解决方案。首先,我们引入了一个 webpack 插件,它使我们能够在脚本元素添加到 DOM 之前访问它。然后我们可以调整元素以使用 fetch() 来获取脚本源,您可以根据需要制作 fetch(例如添加请求标头)。

在 webpack.config.js 中:

/*
 * This plugin will call dynamicImportScriptHook() just before
 * the script element is added to the DOM. The script object is
 * passed to dynamicImportScriptHook(), and it should return
 * the script object or a replacement.
 */
class DynamicImportScriptHookPlugin 
  apply(compiler) 
    compiler.hooks.compilation.tap(
      "DynamicImportScriptHookPlugin", (compilation) =>
        compilation.mainTemplate.hooks.jsonpScript.tap(
          "DynamicImportScriptHookPlugin", (source) => [
            source,
            "if (typeof dynamicImportScriptHook === 'function') ",
            "  script = dynamicImportScriptHook(script);",
            ""
          ].join("\n")
        )
    );
  


/* now add the plugin to the existing config: */
module.exports = 
   ...
   plugins: [
     new DynamicImportScriptHookPlugin()
   ]

现在,在您的应用程序 js 中方便的地方:

/*
 * With the above plugin, this function will get called just
 * before the script element is added to the DOM. It is passed
 * the script element object and should return either the same
 * script element object or a replacement (which is what we do
 * here).
 */
window.dynamicImportScriptHook = (script) => 
  const onerror, onload = script;
  var emptyScript = document.createElement('script');
  /*
   * Here is the fetch(). You can control the fetch as needed,
   * add request headers, etc. We wrap webpack's original
   * onerror and onload handlers so that we can clean up the
   * object URL.
   *
   * Note that you'll probably want to handle errors from fetch()
   * in some way (invoke webpack's onerror or some such).
   */
  fetch(script.src)
    .then(response => response.blob())
    .then(blob => 
      script.src = URL.createObjectURL(blob);
      script.onerror = (event) => 
        URL.revokeObjectURL(script.src);
        onerror(event);
      ;
      script.onload = (event) => 
        URL.revokeObjectURL(script.src);
        onload(event);
      ;
      emptyScript.remove();
      document.head.appendChild(script);
    );
  /* Here we return an empty script element back to webpack.
   * webpack will add this to document.head immediately.  We
   * can't let webpack add the real script object because the
   * fetch isn't done yet. We add it ourselves above after
   * the fetch is done.
   */
  return emptyScript;
;

【讨论】:

我只想跟进这个。我们有一个问题,我们需要这样的东西。我们尝试了您添加的这段代码,但它没有用。这有什么更新吗?您是否制作了一个实际使用的 webpack 插件,该插件使用 fetch 来获取 js 模块? 我没有分发插件,主要是因为我没有花时间弄清楚如何,但我需要的所有东西都在这里发布,并且这段代码对我有用(不完全是因为我的 fetch是为我的应用量身定制的)。如果有帮助,我正在使用 webpack 4.16.1。它以什么方式不适合您?我也有一个使用 Web Service Worker 的解决方案,但它看起来很挑剔。 哦,另外,我的 js 分为一些不需要这个钩子来加载的公共模块和一些确实需要这个钩子的非公共模块。 'public' 模块首先在没有钩子的情况下加载,然后我使用 react-loadable 来触发需要钩子的非公共模块的动态导入。 与 Neriesta 的回答一起,这很好用。我遇到的唯一问题是 onload 属性对我来说始终为空。不知道为什么,但我只是添加了另一个检查。【参考方案2】:

虽然sspiff 的回答看起来很有希望,但它并没有直接对我有用。

经过一番调查,这主要是由于我使用了 Vue CLI 3 以及更新版本的 webpack。 (这有点奇怪,因为 sspiff 提到使用 webpack 4.16.1)。

无论如何要解决它,我使用了以下来源:medium.com, 这给了我编辑给定代码的知识。

这个新代码位于 vue.config.js 文件中:

/*
 * This plugin will call dynamicImportScriptHook() just before
 * the script element is added to the DOM. The script object is
 * passed to dynamicImportScriptHook(), and it should return
 * the script object or a replacement.
 */
class DynamicImportScriptHookPlugin 
  apply(compiler) 
    compiler.hooks.compilation.tap(
      "DynamicImportScriptHookPlugin", (compilation) =>
        compilation.mainTemplate.hooks.render.tap(
          
            name: "DynamicImportScriptHookPlugin",
            stage: Infinity
          ,
          rawSource => 
          const sourceString = rawSource.source()

          if (!sourceString.includes('jsonpScriptSrc')) 
            return sourceString;
           else 
            const sourceArray = sourceString.split('script.src = jsonpScriptSrc(chunkId);')

            const newArray = [
              sourceArray[0],
              'script.src = jsonpScriptSrc(chunkId);',
              "\n\nif (typeof dynamicImportScriptHook === 'function') \n",
              "  script = dynamicImportScriptHook(script);\n",
              "\n",
              sourceArray[1]
            ]

            return newArray.join("")
          
        
      )
    );
  


module.exports = 
  chainWebpack: (config) => 
    config.plugins.delete('prefetch')
  ,
  configureWebpack: 
    plugins: [
      new DynamicImportScriptHookPlugin()
    ]
  

sspiff提供的第二段代码保持不变,可以放在App.vue文件或者脚本标签之间的index.html中。

为了进一步改进这个答案,我现在将解释如何在 Vue CLI 3 中为此特定目的拆分块。

如您所见,我还将chainWebpack 字段添加到配置中。这确保 webpack 不会在 index.html 中添加预取标签。 (例如,它现在只会在需要时加载惰性块)

为了进一步改进拆分,我建议将所有导入更改为:

component: () => import(/* webpackChunkName: "public/componentName" */ /* webpackPrefetch: true */'@/components/yourpubliccomponent')

component: () => import(/* webpackChunkName: "private/componentName" */ /* webpackPrefetch: false */'@/components/yourprivatecomponent')

这将确保您的所有私有块最终都位于私有文件夹中,并且不会被预取。 公共块最终将位于公共文件夹中并被预取。

欲了解更多信息,请使用以下来源how-to-make-lazy-loading-actually-work-in-vue-cli-3

希望这可以帮助解决此问题的任何人!

【讨论】:

【参考方案3】:

要使用访问令牌执行简单的组件下载,您可以执行以下操作...

1) 使用带有文件提取的异步组件加载。使用webpackChunkName 选项分隔文件或目录/文件,如:

components: 
    ProtectedComp: () => import(/* webpackChunkName: "someFolder/someName" */ './components/protected/componentA.vue')
  

2) 为受保护的文件或目录配置服务器重定向。例如 Apache htaccess 配置:

RewriteRule ^js/protected/(.+)$ /js-provider.php?r=$1 [L]

3) 编写一个服务器端脚本,检查标头或 cookie 中的令牌,并给出 .js 的内容或 403 错误。

【讨论】:

当然,但这错过了您实际插入访问令牌的步骤,正如其他答案概述的那样

以上是关于运行时导入 Vue 组件的 webpack 块时如何添加授权标头的主要内容,如果未能解决你的问题,请参考以下文章

使用 Webpack 导入 Vue 组件

vue + webpack:动态组件导入是如何工作的?

webpack 导入 App.vue 或其他组件 vue 失败

Vue webpack动态导入需要不起作用

Vue JS 路由器组件 - 在路由之间导航后无法重用使用 webpack 导入的 JS 文件

测试时 Vue JS 组件模板渲染问题