【五种路由守卫以及登录拦截】

289 阅读8分钟

什么是路由守卫

路由守卫(Route Guard)是在基于前端路由的框架(如 Vue Router、Angular 等)中,用来控制路由导航的工具。它们可以在路由发生变化时,对用户访问的路径进行验证和控制,比如是否允许访问某个页面、是否需要身份验证等。

本章内容主要从作用、场景、案例、对比进行描述路由守卫。

以登录拦截为例,代码展示怎么使用路由守卫实现登录拦截

什么是登录拦截

登录拦截是Web应用中的一种安全机制,用于确保只有经过身份验证的用户才能访问某些受限的页面或资源。这种机制通常通过检查用户的登录状态来实现,如果用户未登录或登录状态无效,则会被重定向到登录页面或其他指定页面,以提示用户进行登录。

在前端开发中,登录拦截通常与路由守卫(Route Guards)结合使用,以在路由层面控制用户的访问权限。路由守卫允许开发者在路由即将发生变化之前或之后执行一些自定义的逻辑,例如检查用户的登录状态、验证权限等。

以Vue.js为例,登录拦截可以通过Vue Router的路由守卫来实现。开发者可以在全局前置守卫(router.beforeEach)中检查用户试图访问的路由是否需要登录权限,并根据用户的登录状态来决定是否允许访问。如果用户未登录,则可以通过next函数重定向到登录页面。

登录拦截的目的在于保护应用的安全性和数据的完整性,防止未授权用户访问敏感信息或执行敏感操作。同时,它也可以提升用户体验,通过友好的提示和引导,帮助用户快速完成登录并访问所需资源。

需要注意的是,登录拦截只是Web应用安全机制的一部分,它通常需要与后端验证、会话管理、权限控制等其他安全措施结合使用,以确保应用的全面安全性。

三个参数 to、from、next以及其他解释

to:表示目标路由对象,即用户即将要进入的路由。

from:表示来源路由对象,即用户当前所在的路由。

next:是一个函数,用于控制路由的流程。调用next()表示放行,允许路由继续跳转;调用next('/login')则表示取消当前路由跳转并重定向到登录页面。

登录拦截的场景中,to参数被用来判断用户试图访问的路由是否需要登录权限。通常,这通过在路由配置中添加自定义字段(如meta.authrequireAuth)来实现,该字段用于标记哪些路由需要登录权限才能访问。在router.beforeEach守卫函数中,通过检查to.meta.authto.meta.requireAuth的值,可以确定是否需要对用户进行登录拦截。

简单来说

beforeEach((to, from, next) => { 
    to // 要去的路由 
    from // 当前路由 
    next() // 放行的意思 
}

可能有些人还是不理解next()的具体使用方法,下面我以案例进行解析

router.beforeEach((to, from, next) => {
  if (用户已登录) {
    next(); // 放行
  } else {
    next('/login'); // 重定向到登录页面
  }
});
router.beforeResolve((to, from, next) => {
  if (满足某些条件) {
    next(); // 放行
  } else {
    next(false); // 取消导航
  }
});

路由守卫的分类

全局前置守卫

  • 作用:在路由跳转开始之前触发,决定是否允许导航到下一个页面。它可以在整个应用级别进行身份验证或权限检查。

  • 常用场景:检查用户是否登录,或者是否有权限访问某个页面。

  • 案例

(1)你可以使用 router.beforeEach 注册一个全局前置守卫:

const router = createRouter({ ... })
router.beforeEach((to, from) => { 
// ...
// 返回 false 以取消导航 
return false 
})

(2)当一个导航触发时,全局前置守卫按照创建顺序调用。 守卫是异步解析执行,此时导航在所有守卫 resolve 完之前一直处于等待中。

  • 每个守卫方法接收两个参数:to、from
  • //通常账号通过之后会获取会在本来保存一个token,
    //在使用网页的时候,如果获取得到token则可以通过权限使用其他的网页
      router.beforeEach(async (to, from) => {
       if (
         // 检查用户是否已登录
         !isAuthenticated &&
         // ❗️ 避免无限重定向
         to.name !== 'Login'
       ) {
         // (如果获取token不正确)将用户重定向到登录页面
         return { name: 'Login' }
       }
     })
    

    在之前的 Vue Router 版本中,还可以使用 第三个参数 next 。这是一个常见的错误来源,我们经过 RFC 讨论将其移除。然而,它仍然是被支持的,这意味着你可以向任何导航守卫传递第三个参数。在这种情况下,确保 next 在任何给定的导航守卫中都被严格调用一次。它可以出现多于一次,但是只能在所有的逻辑路径都不重叠的情况下,否则钩子永远都不会被解析或报错。

    router.beforeEach((to, from, next) => {
    if (to.name !== 'Login' && !isAuthenticated) next({ name: 'Login' }) 
    else next()
    })
    

    全局后置守卫

    • 作用 :在每次导航后(路由已经生效)执行的操作,但不会改变导航的结果。

    • 常用场景:可以用于页面的状态更新、记录用户访问日志等。

    • 案例

    router.afterEach((to, from) => { 
    console.log(`导航到 ${to.path} 成功`); 
    });
    

    路由独享守卫

    • 作用:用于定义在特定路由中的守卫,仅影响该路由及其子路由的导航。

    • 常用场景:为某个特定路由添加访问限制。

    • 案例

      javascript
      复制代码
      const route = {
        path: '/admin',
        beforeEnter: (to, from, next) => {
          if (isAdmin()) {
            next();
          } else {
            next('/not-authorized');
          }
        }
      };
      

      为了简化说明,我将展示如何使用路由元信息和 beforeEach 守卫来实现对特定路由及其子路由的访问限制:

    const router = new VueRouter({  
      routes: [  
        {  
          path: '/admin',  
          component: AdminComponent,  
          meta: { requiresAuth: true }, // 路由元信息,用于标记需要认证的路由  
          children: [  
            {  
              path: 'dashboard',  
              component: DashboardComponent,  
              // 子路由也可以继承父路由的meta信息,除非它们自己定义了不同的meta  
            },  
            // 其他子路由...  
          ],  
        },  
        // 其他路由...  
      ],  
      beforeEach: (to, from, next) => {  
        // 检查路由是否需要认证  
        if (to.matched.some(record => record.meta.requiresAuth)) {  
          // 这里是认证逻辑  
          if (isAdmin()) { // 假设isAdmin()是检查用户是否为管理员的函数  
            next(); // 认证成功,继续路由  
          } else {  
            next('/not-authorized'); // 认证失败,重定向到未授权页面  
          }  
        } else {  
          next(); // 不需要认证的路由,直接继续  
        }  
      },  
    });  
    function isAdmin() {  
      // 这里是检查用户是否为管理员的逻辑  
      // 返回一个布尔值  
      return false; // 示例中假设用户不是管理员  
    }
    

    组件内守卫

    组件内守卫(In-Component Guard)

    • 作用:在组件内的 beforeRouteEnterbeforeRouteUpdatebeforeRouteLeave 生命周期钩子中定义,控制组件在路由变化时的行为。

    • 常用场景:针对某个组件的加载行为、数据预加载、资源释放等。

    • 案例

      export default {
        beforeRouteEnter(to, from, next) {
          // 在路由进入时执行
          next(vm => {
            vm.loadData(); // 访问组件实例的方法
          });
        },
        beforeRouteUpdate(to, from, next) {
          // 在路由更新时执行
          this.loadData();
          next();
        },
        beforeRouteLeave(to, from, next) {
          // 在路由离开时执行
          if (this.hasUnsavedChanges) {
            if (confirm('你有未保存的更改,确定要离开吗?')) {
              next();
            } else {
              next(false); // 阻止导航
            }
          } else {
            next();
          }
        }
      };
      

    异步守卫

    • 作用:允许在路由守卫中执行异步操作,比如等待 API 请求结果后再决定是否继续导航。

    • 常用场景:当需要从服务器获取用户权限或其他数据时,等待异步请求完成后再决定导航逻辑。

    • 案例

      router.beforeEach(async (to, from, next) => {
        try {
          const user = await fetchUserData();
          if (user.hasAccess(to.path)) {
            next();
          } else {
            next('/no-access');
          }
        } catch (error) {
          next('/error');
        }
      });
      

    五种路由守卫的对比

    1、全局前置守卫:在每次导航开始前触发,控制访问权限或身份验证。

    2、全局后置守卫:在每次导航后执行,通常用于记录日志或更新页面状态。

    3、路由独享守卫:仅影响特定路由,适合为单独的路由设置限制条件。

    4、组件内守卫:在特定组件内控制路由行为,允许对组件生命周期与路由变化进行更精细的控制。

    5、异步守卫:可以在守卫中执行异步操作,等待数据返回后再决定是否允许导航。

    6、全局前置路由守卫和全局后置路由守卫

    (一)、作用时机

    (1) 全局前置守卫

    • 调用时机:在路由即将改变前调用。具体来说,当用户点击链接、在Vue组件内调用router.pushrouter.replace方法时,全局前置守卫会在路由实际跳转之前被触发。
    • 作用:用于在路由跳转前进行权限验证、登录状态检查、页面跳转前的数据预加载等操作。

    (2) 全局后置守卫

    • 调用时机:在路由跳转完成后调用。即路由已经成功跳转到目标页面,并且DOM已经更新后,全局后置守卫才会被触发。
    • 作用:用于在路由跳转完成后执行一些操作,如页面统计、日志记录、页面标题修改等。这些操作不需要影响路由的跳转流程。

    (二)、功能差异

    (1)全局前置守卫

    • 可以通过调用next()函数来控制路由的跳转流程。如果next()没有被调用,则路由跳转会被中断。
    • 可以根据条件动态地修改目标路由(to)或取消路由跳转(通过重定向到另一个路由)。
    • 常用于实现权限控制、登录验证等逻辑。

    (2) 全局后置守卫

    • 不需要调用next()函数,因为它是在路由跳转完成后被调用的。
    • 主要用于执行与路由跳转结果相关的操作,如页面统计、日志记录等。
    • 不能改变路由的跳转结果或中断路由跳转。