Vue3官网-规模化(二十)Vue Router路由、Vuex状态管理模式、服务端渲染(SSR指南)、安全

Vue3官网-规模化(二十)Vue Router路由、Vuex状态管理模式、服务端渲染(SSR指南)、安全

总结:

  • Vue Router
    • https://next.router.vuejs.org/zh/introduction.html
    • 在web开发中,“route”是指根据url分配到对应的处理程序
    • 不同的请求地址会交给路由处理来转发给相应的控制器处理,所以说路由就可以在转发前修改转发地址,你可以在这上面大作文章。
    • 为什么要使用路由?
      • 传统web开发是每一个请求地址都会请求服务器来进行处理,但是用户有些操作则无需请求服务器,直接页面端修改下逻辑就能达到目的,这种最好使用路由,也许题主会有疑问:直接使用js处理下不就行了。使用js直接处理这些是可以的,事实上以前我们也这么做,但是这样做不便于用户收藏当前页,因为使用js时并不更新url,但是使用路由时,url也是随着改变的,用户浏览到一个网页时可以直接复制或收藏当前页的url给别人,这种方式对于搜索引擎和用户来说都是友好的。
  • Vuex
    • Vuex 是什么?
      • Vuex 是一个专为 Vue.js 应用程序开发的**状态管理模式 + 库**。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
    • 状态管理模式
      • 状态驱动应用的数据源
      • 视图,以声明方式将状态映射到视图;
      • 操作,响应在视图上的用户输入导致的状态变化。
    • 什么情况下我应该使用 Vuex?
      • 您不打算开发大型单页应用,使用 Vuex 可能是繁琐冗余的。确实是如此——如果您的应用够简单,您最好不要使用 Vuex。一个简单的 store 模式就足够您所需了。但是,如果您需要构建一个中大型单页应用,您很可能会考虑如何更好地在组件外部管理状态,Vuex 将会成为自然而然的选择。
  • 服务端渲染(SSR指南)
    • SSR 完全指南
      • 我们创建了一份完整的构建 Vue 服务端渲染应用的指南。这份指南非常深入,适合已经熟悉 Vue、webpack 和 Node.js 开发的开发者阅读。请移步这里

1. 路由(vue-router)

官方 Router

对于大多数单页面应用,都推荐使用官方支持的 vue-router 库。更多细节可以移步 vue-router 文档

从零开始简单的路由

如果你只需要非常简单的路由而不想引入一个功能完整的路由库,可以像这样动态渲染一个页面级的组件:

const { createApp, h } = Vue

const NotFoundComponent = { template: '<p>Page not found</p>' }
const HomeComponent = { template: '<p>Home page</p>' }
const AboutComponent = { template: '<p>About page</p>' }

const routes = {
  '/': HomeComponent,
  '/about': AboutComponent
}

const SimpleRouter = {
  data: () => ({
    currentRoute: window.location.pathname
  }),

  computed: {
    CurrentComponent() {
      return routes[this.currentRoute] || NotFoundComponent
    }
  },

  render() {
    return h(this.CurrentComponent)
  }
}

createApp(SimpleRouter).mount('#app')

结合 HTML5 History API,你可以建立一个麻雀虽小但是五脏俱全的客户端路由器。为了获得更多的实践,可以直接查看实例应用

整合第三方路由

如果你有更加偏爱的第三方路由,如 Page.js 或者 Director,整合起来也一样简单。这里有一个使用了 Page.js 的完整示例

2. 状态管理(Vuex)

类 Flux 状态管理的官方实现

由于状态零散地分布在许多组件和组件之间的交互中,大型应用复杂度也经常逐渐增长。为了解决这个问题,Vue 提供 Vuex:我们有受到 Elm 启发的状态管理库。vuex 甚至集成到 vue-devtools,无需配置即可进行时光旅行调试 (time travel debugging)

给 React 开发者的参考信息

如果你是来自 React 的开发者,可能会对 Vuex 和 Redux 间的差异表示关注,Redux 是 React 生态环境中最流行的 Flux 实现。Redux 事实上无法感知视图层,所以它能够轻松的通过一些[简单绑定](https://classic.yarnpkg.com/en/packages?q=redux vue&p=1)和 Vue 一起使用。Vuex 区别在于它是一个专门为 Vue 应用所设计。这使得它能够更好地和 Vue 进行整合,同时提供简洁的 API 和更好的开发体验。

从零打造简单状态管理

经常被忽略的是,Vue 应用中响应式 data 对象的实际来源——当访问数据对象时,一个组件实例只是简单的代理访问。所以,如果你有一处需要被多个实例间共享的状态,你可以使用一个 reactive 方法让对象作为响应式对象。

const { createApp, reactive } = Vue
const sourceOfTruth = reactive({
  message: 'Hello'
})

const appA = createApp({
  data() {
    return sourceOfTruth
  }
}).mount('#app-a')

const appB = createApp({
  data() {
    return sourceOfTruth
  }
}).mount('#app-b')
<div id="app-a">App A: {{ message }}</div>

<div id="app-b">App B: {{ message }}</div>

现在当 sourceOfTruth 发生变更,appAappB 都将自动地更新它们的视图。虽然现在我们有了一个真实数据来源,但调试将是一场噩梦。应用的任何部分都可以随时更改任何数据,而不会留下变更过的记录。

const appB = createApp({
  data() {
    return sourceOfTruth
  },
  mounted() {
    sourceOfTruth.message = 'Goodbye' // both apps will render 'Goodbye' message now
  }
}).mount('#app-b')

为了解决这个问题,可以采用一个简单的 store 模式

const store = {
  debug: true,

  state: reactive({
    message: 'Hello!'
  }),

  setMessageAction(newValue) {
    if (this.debug) {
      console.log('setMessageAction triggered with', newValue)
    }

    this.state.message = newValue
  },

  clearMessageAction() {
    if (this.debug) {
      console.log('clearMessageAction triggered')
    }

    this.state.message = ''
  }
}

需要注意,所有 store 中 state 的变更,都放置在 store 自身的 action 中去管理。这种集中式状态管理能够被更容易地理解哪种类型的变更将会发生,以及它们是如何被触发。当错误出现时,现在也会有一个 log 记录 bug 之前发生了什么。

此外,每个实例/组件仍然可以拥有和管理自己的私有状态:

<div id="app-a">{{sharedState.message}}</div>

<div id="app-b">{{sharedState.message}}</div>
const appA = createApp({
  data() {
    return {
      privateState: {},
      sharedState: store.state
    }
  },
  mounted() {
    store.setMessageAction('Goodbye!')
  }
}).mount('#app-a')

const appB = createApp({
  data() {
    return {
      privateState: {},
      sharedState: store.state
    }
  }
}).mount('#app-b')

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dW7FZdzP-1633660702051)(https://v3.cn.vuejs.org/images/state.png)]

TIP

重要的是,注意你不应该在 action 中替换原始的状态对象——组件和 store 需要引用同一个共享对象,变更才能够被观察到。

随着我们进一步扩展约定,即组件不允许直接变更属于 store 实例的 state,而应执行 action 来分发 (dispatch) 事件通知 store 去改变,最终达成了 Flux 架构。这样约定的好处是,能够记录所有 store 中发生的 state 变更,同时实现能做到记录变更、保存状态快照、历史回滚/时光旅行的先进的调试工具。

说了一圈其实又回到了 Vuex,如果已经读到这儿,不妨可以去尝试一下!

3. 服务端渲染

SSR 完全指南

我们创建了一份完整的构建 Vue 服务端渲染应用的指南。这份指南非常深入,适合已经熟悉 Vue、webpack 和 Node.js 开发的开发者阅读。请移步这里

Nuxt.js

从头搭建一个服务端渲染的应用是相当复杂的。幸运的是,我们有一个优秀的社区项目 Nuxt.js 让这一切变得非常简单。Nuxt.js 是一个基于 Vue 生态的更高层次的框架,为开发服务端渲染的 Vue 应用提供了极其便利的开发体验。更酷的是,你甚至可以用它来做为静态站生成器。我们强烈建议尝试一下。

Quasar Framework SSR + PWA

Quasar Framework 可以通过其一流的构建系统、合理的配置和开发者扩展性生成 (可选地和 PWA 互通的) SSR 应用,让你的想法的设计和构建变得轻而易举。你可以在服务端挑选执行超过上百款遵循“Material Design 2.0”的组件,并且在浏览器端可用。你甚至可以管理网站的 <meta> 标签。Quasar 是一个基于 Node.js 和 webpack 的开发环境,它可以通过一套代码完成 SPA、PWA、SSR、Electron、Capacitor 和 Cordova 应用的快速开发。

Vite SSR

Vite 是一个新推出的前端构建工具,极大地改善了前端开发体验。它由两大部分组成:

  • 一个开发服务器,基于原生的 ES modules 提供源代码,并带有丰富的内置功能和极快的模块热替换 (HMR)。
  • 一个构建命令行,用来通过 Rollup 打包你的代码,并预置为生产环节输出高度优化的静态资源。

Vite 也提供了内置的服务端渲染支持。你可以在这里找到一个 Vue 的示例项目。

4. 安全

报告漏洞

如果有漏洞被报告,那么它将立刻成为我们最关心的问题,全职贡献者会放下一切来处理它。如需报告漏洞,请发送电子邮件至 security@vuejs.org

虽然很少发现有新的漏洞,但我们也建议始终使用最新版本的 Vue 及其官方配套库,以确保你的应用尽可能地安全。

首要规则:永远不要使用不受信任的模板

使用 Vue 时最基本的安全规则是永远不要使用不受信任的内容作为组件模板。这样做相当于允许在你的应用程序中执行任意 JavaScript。更糟糕的是,如果在服务器端渲染时执行了这些代码,可能会导致服务器漏洞被恶意利用。例如:

Vue.createApp({
  template: `<div>` + userProvidedString + `</div>` // 永远不要这么做
}).mount('#app')

Vue 模板会被编译成 JavaScript,模板内的表达式将作为渲染过程的一部分执行。尽管表达式会针对特定的渲染场景进行评估,但由于潜在全局执行环境的复杂性,要求 Vue 这样的框架完全屏蔽潜在的恶意代码执行而不产生不切实际的性能开销非常不切实际。完全避免此类问题的最直接方法是确保 Vue 模板的内容始终受你信任并完全由你控制。

Vue 如何保护你

HTML 内容

无论是使用模板还是渲染函数,内容都会自动转义。因此在这个模板中:

<h1>{{ userProvidedString }}</h1>

如果 userProvidedString 包含:

'<script>alert("hi")</script>'

那么它将被转义为以下 HTML:

&lt;script&gt;alert(&quot;hi&quot;)&lt;/script&gt;

从而防止脚本注入。这种转义是使用原生浏览器 API (例如 textContent) 完成的,因此只有当浏览器本身易受攻击时才会存在漏洞。

attribute 绑定

同样,动态 attribute 绑定也会自动转义。因此在这个模板中:

<h1 :title="userProvidedString">
  hello
</h1>

如果 userProvidedString 包含:

'" οnclick="alert(\'hi\')'

那么它将被转义为以下 HTML:

&quot; οnclick=&quot;alert('hi')

从而防止 title attribute 被提前关闭以注入新的、任意的 HTML。这种转义是使用原生浏览器 API (例如 setAttribute) 完成的,因此只有当浏览器本身易受攻击时才会存在漏洞。

潜在的安全隐患

在任何 Web 应用程序中,允许以 HTML、CSS 或 JavaScript 形式执行未经处理的、用户提供的内容都有潜在的安全隐患,因此应尽可能避免。不过有些时候部分风险或许是可以接受的。

例如,CodePen 和 JSFiddle 之类的服务允许执行用户提供的内容,但这是在预期的上下文中,并且使用了 iframe 包裹来进行一定程度的沙盒处理。如果某个重要功能本质上需要某种程度的漏洞,则由你的团队根据漏洞导致的最坏情况来权衡该功能的重要性。

注入 HTML

正如你之前所看到的,Vue 会自动转义 HTML 内容,防止你意外地将可执行的 HTML 注入到你的应用程序中。但是,如果你确信某个 HTML 是安全的,你可以显式呈现 HTML 内容:

  • 使用模板:

    <div v-html="userProvidedHtml"></div>
    

    1

  • 使用渲染函数:

    h('div', {
      innerHTML: this.userProvidedHtml
    })
    
  • 在 JSX 中使用渲染函数:

    <div innerHTML={this.userProvidedHtml}></div>
    

TIP

请注意,永远不要 100% 信任用户提供的 HTML,除非它位于 iframe 沙盒中,或位于仅有编写该 HTML 的用户才能接触到它的部分中。此外,允许用户编写自己的 Vue 模板也会带来类似的危险。

注入 URL

在这样的 URL 中:

<a :href="userProvidedUrl">
  click me
</a>

如果 URL 未经过“净化”处理来防止其通过 javascript: 执行 JavaScript,则会有潜在的安全隐患。有一些库例如 sanitize-url 可以帮助解决这个问题,但请注意:

TIP

如果你只在前端进行 URL 净化,那么你已经遇到了安全问题。在保存用户提供的 URL 到数据库之前,你的后端必须对其进行净化。只有这么做,才能避免 每个 连接到你 API 的客户端 (包括原生移动应用程序) 出现此类问题。另请注意,即便使用了经过净化的 URL,Vue 也无法保证它们指向安全的目标。

注入样式

考虑下列示例:

<a
  :href="sanitizedUrl"
  :style="userProvidedStyles"
>
  click me
</a>

我们假设它已经被 sanitizedUrl 净化过了,因此它确实是一个真实的 URL 而非 JavaScript。然而,恶意用户仍然可以利用 userProvidedStyles,通过提供 CSS 来实现“点击劫持 (click jack)”,例如将链接样式设置为“登录”按钮上方的透明框。假设 https://user-controlled-website.com/ 是用来为你的应用程序提供类似于登录界面功能的,那么他们可能刚刚捕获了用户的真实登录信息。

你或许能想象到了允许用户为某个 <style> 元素提供内容会产生多大的安全漏洞,因为这意味着用户拥有了整个页面样式的完整控制权。这也是为什么 Vue 会阻止在模板中渲染样式标签,比如:

<style>{{ userProvidedStyles }}</style>

为了让你的用户完全免受点击劫持,我们建议只允许用户完全控制 iframe 沙盒内的 CSS。或者,如果需要通过样式绑定来允许用户控制样式,我们建议使用它的对象语法,并且只允许用户为可以被安全控制的特定 property 提供值,如下所示:

<a
  :href="sanitizedUrl"
  :style="{
    color: userProvidedColor,
    background: userProvidedBackground
  }"
>
  click me
</a>

注入 JavaScript

我们强烈建议不要使用 Vue 渲染 <script> 元素,因为这么做的话模板和渲染函数不会有副作用。不过,如果你想在运行时引入会被作为 JavaScript 处理的字符串,还有别的办法。

每个 HTML 元素都有能接受 JavaScript 字符串的 attribute,如 onclickonfocusonmouseenter。你应当避免将用户提供的 JavaScript 绑定到任一事件 attribute,因为这会是一个潜在的安全隐患。

TIP

请注意,永远不要 100% 信任用户提供的 JavaScript,除非它位于 iframe 沙盒中,或位于仅有编写该 HTML 的用户才能接触到它的部分中。

有时我们会收到有关如何在 Vue 模板中执行跨站点脚本 (XSS) 的漏洞报告。一般来说,我们不认为这种情况是真正的漏洞,因为在下列两种允许 XSS 情况中,我们没有切实可行的方法来保护开发者免受其影响:

  1. 开发者明确要求 Vue 将用户提供的、未经净化的内容作为 Vue 模板渲染。这根本就是不安全的,并且 Vue 无法知道这些内容的来源。
  2. 开发者正在将 Vue 挂载到整个 HTML 页面,而该页面恰好同时包含服务器渲染的和用户提供的内容。这与 #1 的问题基本相同,但有时开发者可能会没有意识到这一点。这可能会导致出现潜在漏洞,攻击者也许会提供作为纯 HTML 安全但作为 Vue 模板不安全的 HTML 内容。

最佳实践

通常来说,如果你允许执行未经净化的、用户提供的内容 (作为 HTML、JavaScript 甚至 CSS),你可能会面临攻击。无论是使用 Vue、其他框架,或是不使用框架,这些建议都是适用的。

除了上面提到的针对潜在的安全隐患提出的建议之外,我们还建议你熟悉以下资源:

如果你使用了第三方组件或是会影响 DOM 渲染的第三方依赖,请使用你学到的知识检查它们的源代码以便找出潜在的危险。

后端协调

HTTP 安全漏洞主要由后端负责处理,例如跨站点请求伪造 (CSRF/XSRF) 和跨站点脚本包含 (XSSI),因此这不是 Vue 应该考虑的问题。不过,我们非常建议你去和后端团队交流以了解如何最好地与他们的 API 交互,例如通过提交表单提交的 CSRF 令牌。

服务器端渲染 (SSR)

使用 SSR 时还有一些额外的安全问题,因此请确保遵循我们在 SSR 文档中概述的最佳实践以避免漏洞。


版权声明:本文为qq_43456781原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。