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

时间:2018-04-09 11:05:06

标签: javascript node.js webpack vue.js

此任务的目的是使得无法下载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请求。因此,我不知道如何为此下载添加授权标头。因此,任何没有令牌的用户都可以通过简单地将其地址插入浏览器的导航栏来下载私有组件的代码。

import dashboard-reports.vue

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

import using ajax

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

Auth header in script request

3 个答案:

答案 0 :(得分:1)

我需要类似的东西,并提出以下解决方案。首先,我们介绍一个webpack插件,该插件可让我们在将脚本元素添加到DOM之前对其进行访问。然后我们可以对元素进行修改以使用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;
};

答案 1 :(得分:1)

尽管sspiff的答案看起来很有希望,但它对我来说并不直接有效。

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

无论如何解决,我都使用以下来源: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

希望这可以帮助任何遇到此问题的人!

答案 2 :(得分:0)

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

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错误的内容。