身份验证是每个 Web 应用程序的必要组成部分。它是一种方便的方式,我们可以通过它个性化体验并加载特定于用户的內容 - 例如登录状态。它还可以用于评估权限,并防止未经授权的用户访问私密信息。
应用程序用来保护内容的一种常见做法是将它们放在特定的路由下,并构建重定向规则,根据用户的权限将用户导航到或远离资源。为了可靠地在受保护的路由后面设置内容,他们需要构建到单独的静态页面。这样,重定向规则可以正确处理重定向。
对于使用 Vue 等现代前端框架构建的单页面应用程序 (SPA),无法使用重定向规则来保护路由。因为所有页面都从单个入口文件提供服务,所以从浏览器的角度来看,只有一个页面:index.html
。在 SPA 中,路由逻辑通常源于路由文件。这就是我们将在本文中进行大部分身份验证配置的地方。我们将特别依赖 Vue 的导航守卫来处理身份验证特定的路由,因为这有助于我们在完全解析之前访问选定的路由。让我们深入了解一下它是如何工作的。
根和路由
导航守卫 是 Vue Router 中的一项特定功能,它提供了与路由如何解析相关的附加功能。它们主要用于处理错误状态并在不突然中断用户工作流的情况下无缝导航用户。
Vue Router 中有三种主要的守卫类别:全局守卫、路由守卫和组件守卫。顾名思义,全局守卫 在触发任何导航时被调用(即 URL 发生更改时),路由守卫 在调用关联路由时被调用(即 URL 与特定路由匹配时),以及组件守卫 在创建、更新或销毁路由中的组件时被调用。在每个类别中,都有一些其他方法可以让你更细粒度地控制应用程序路由。以下是 Vue Router 中每种类型的导航守卫中所有可用方法的快速分解。
全局守卫
beforeEach
:进入任何路由之前的操作(无法访问this
范围)beforeResolve
:在确认导航之前但组件守卫之后执行的操作(与 beforeEach 相同,但可以访问this
范围)afterEach
:路由解析后的操作(无法影响导航)
路由守卫
beforeEnter
:进入特定路由之前的操作(与全局守卫不同,它可以访问this
)
组件守卫
beforeRouteEnter
:在确认导航之前以及组件创建之前执行的操作(无法访问 this)beforeRouteUpdate
:在调用使用相同组件的新路由之后执行的操作beforeRouteLeave
:离开路由之前的操作
保护路由
为了有效地实现它们,了解在任何给定场景中何时使用它们会有所帮助。例如,如果你想跟踪页面浏览量以进行分析,你可能希望使用全局 afterEach
守卫,因为它在路由和相关组件完全解析后被触发。如果你想在路由解析之前预取数据以加载到 Vuex 存储中,你可以使用路由守卫中的 beforeEnter
来做到这一点。
由于我们的示例处理的是根据用户的访问权限保护特定路由,因此我们将使用组件导航守卫,即 beforeEnter
钩子。此导航守卫在解析完成之前让我们访问正确的路由;这意味着我们可以在让用户通过之前获取数据或检查数据是否已加载。在深入了解它是如何工作的实现细节之前,让我们简要了解一下我们的 beforeEnter
钩子是如何适应我们现有的路由文件的。下面,我们有我们的示例路由文件,其中包含我们受保护的路由,恰当地命名为 protected
。为此,我们将向其中添加我们的 beforeEnter
钩子,如下所示
const router = new VueRouter({
routes: [
...
{
path: "/protected",
name: "protected",
component: import(/* webpackChunkName: "protected" */ './Protected.vue'),
beforeEnter(to, from, next) {
// logic here
}
]
})
路由结构
beforeEnter
的结构与 Vue Router 中其他可用的导航守卫没有太大区别。它接受三个参数:to
,应用程序要导航到的“未来”路由;from
,应用程序要离开的“当前/即将过去”路由;以及next
,一个必须调用的函数才能使路由成功解析。
通常,在使用 Vue Router 时,next
会在没有任何参数的情况下被调用。但是,这假设了一个永久的成功状态。在我们的例子中,我们希望确保未经授权的用户无法进入受保护的资源,并且有一个备用路径可以将他们适当地重定向。为此,我们将向 next
传递一个参数。为此,我们将使用路由的名称来导航用户,如果他们未经授权,则如下所示
next({
name: "dashboard"
})
让我们假设在我们的例子中,我们有一个 Vuex 存储,我们将在其中存储用户的授权令牌。为了检查用户是否有权限,我们将检查此存储并适当地失败或通过路由。
beforeEnter(to, from, next) {
// check vuex store //
if (store.getters["auth/hasPermission"]) {
next()
} else {
next({
name: "dashboard" // back to safety route //
});
}
}
为了确保事件同步发生并且路由在 Vuex 操作完成之前不会过早加载,让我们将导航守卫转换为使用 async/await。
async beforeEnter(to, from, next) {
try {
var hasPermission = await store.dispatch("auth/hasPermission");
if (hasPermission) {
next()
}
} catch (e) {
next({
name: "dashboard" // back to safety route //
})
}
}
永远不要忘记你来自哪里
到目前为止,我们的导航守卫实现了其阻止未经授权的用户访问受保护资源的目的,方法是将他们重定向到他们可能来自的地方(即仪表板页面)。即便如此,这样的工作流程仍然具有破坏性。由于重定向是意外的,用户可能会认为是用户错误并反复尝试访问该路由,并最终认为应用程序已损坏。为了解决这个问题,让我们创建一种方法来让用户知道何时以及为何被重定向。
我们可以通过向 next
函数传递查询参数来做到这一点。这使我们能够将受保护资源路径附加到重定向 URL。因此,如果你想提示用户登录应用程序或获得适当的权限而无需记住他们离开的地方,你可以这样做。我们可以通过传递给 beforeEnter
函数的 to
路由对象访问受保护资源的路径,如下所示:to.fullPath
。
async beforeEnter(to, from, next) {
try {
var hasPermission = await store.dispatch("auth/hasPermission");
if (hasPermission) {
next()
}
} catch (e) {
next({
name: "login", // back to safety route //
query: { redirectFrom: to.fullPath }
})
}
}
通知
提升用户无法访问受保护路由的工作流程的下一步是向他们发送一条消息,让他们了解错误以及如何解决问题(通过登录或获取适当的权限)。为此,我们可以利用组件守卫,特别是beforeRouteEnter
,来检查是否发生了重定向。因为我们在路由文件中将重定向路径作为查询参数传递,所以现在可以检查路由对象以查看是否发生了重定向。
beforeRouteEnter(to, from, next) {
if (to.query.redirectFrom) {
// do something //
}
}
正如我之前提到的,所有导航守卫都必须调用next
才能解析路由。next
函数的优势,正如我们之前看到的,是我们可以向它传递一个对象。你可能不知道的是,你也可以在next
函数中访问Vue实例。哇哦!下面是它的样子
next(() => {
console.log(this) // this is the Vue instance
})
你可能已经注意到,在使用beforeEnter
时,你实际上无法访问this
作用域。尽管可能是这种情况,但你仍然可以通过像这样将vm
传递给函数来访问Vue实例
next(vm => {
console.log(vm) // this is the Vue instance
})
这尤其方便,因为当路由重定向发生时,你现在可以创建一个并适当地更新一个包含相关错误消息的数据属性。假设你有一个名为errorMsg
的数据属性。你现在可以轻松地从导航守卫中的next
函数更新此属性,而无需任何额外的配置。使用它,你最终会得到一个这样的组件
<template>
<div>
<span>{{ errorMsg }}</span>
<!-- some other fun content -->
...
<!-- some other fun content -->
</div>
</template>
<script>
export default {
name: "Error",
data() {
return {
errorMsg: null
}
},
beforeRouteEnter(to, from, next) {
if (to.query.redirectFrom) {
next(vm => {
vm.errorMsg =
"Sorry, you don't have the right access to reach the route requested"
})
} else {
next()
}
}
}
</script>
结论
将身份验证集成到应用程序中的过程可能很棘手。我们介绍了如何防止未经授权的访问路由,以及如何根据用户的权限设置将用户重定向到受保护资源或远离受保护资源的工作流程。到目前为止,我们一直假设你的应用程序中已经配置了身份验证。如果你还没有配置身份验证,并且想要快速启动和运行,我强烈建议使用身份验证即服务。有一些提供商,例如Netlify的身份验证组件或Auth0的锁。
嗨,
很棒的文章,非常感谢。我认为我在本段中发现了一个小错误
“由于我们的示例处理基于用户访问权限保护特定路由,因此我们将使用组件内导航守卫”
你说你正在使用“组件内”守卫,但随后你使用了
beforeEnter
,你之前将其指定为“路由守卫”。祝您有美好的一天。 :)
这可能是一个愚蠢的问题,但由于身份验证检查发生在前端(Vue运行的地方),因此可以在浏览器中修改代码以在任何情况下都调用next(),因此你实际上并没有真正保护路由,而更多地是为了为最终出现在错误位置的用户提供一些便利?