代码之家  ›  专栏  ›  技术社区  ›  TommyF

VueJS+Firebase身份验证+路由保护-同步问题/竞争条件

  •  1
  • TommyF  · 技术社区  · 6 年前

    在将Firebase Auth集成到我的Vue应用程序中时,我最近偶然发现了一个问题,页面刷新时登录的用户将不会被识别为已登录(因此在尝试访问受保护的路由时会重定向到登录页面)。
    这是因为 firebase.auth().onAuthStateChanged(...) 在路由守卫评估状态之前未返回。

    有几个提议的解决方案,但在我看来没有一个是干净的。最突出的问题似乎是通过移动来延迟应用程序的实际安装 new Vue(...).$mount('#app') 内部 onAuthStateChanged()
    因为在大多数情况下,初始视图不需要用户登录(或者可能是登录路径本身),所以不需要延迟整个应用程序的安装,直到firebase回调返回。

    我想我可能已经找到了一个更好的解决方案,但如果您能看到这种方法的任何问题,或者如果可以进一步改进,我将非常感谢您的反馈。

    注意:我使用Vuex全局状态存储来管理应用程序状态。

    我初始化firebase onAuthStateChanged() 回拨 main.js created 应用程序组件的生命周期挂钩:

    new Vue({
      router,
      store,
      render: h => h(App),
      created() {
        firebaseApp.auth().onAuthStateChanged((user) => {
          if(!this.$store.getters.firebaseInitialized)
            this.$store.commit('firebaseInitialized')
          if (user) {
            if(!this.$store.authenticated || this.$store.getters.loggedInUser.uid != user.uid)
              this.$store.dispatch('logIn', user)
          }
        })
      }
    }).$mount('#app')
    

    这个 firebaseInitialized 状态中的布尔值跟踪firebase的init状态,即 false 默认设置为 true 在那之前 onAuthStateChanged 回拨。

    router.js 我现在创建了一个“投票承诺”,上面说 firebaseInitialized的 并等待初始化完成(或超时),然后再检查用户的身份验证状态:

    function ensureFirebaseIsInitialized() {
      let timeout = 5000;
      let start = Date.now();
      return new Promise(function (resolve, reject) {
        (function waitForFirebaseInit(){
          if (store.getters.firebaseInitialized) 
            return resolve();
          else if((Date.now() - start) >= timeout) 
            return reject(new Error('Firebase initialize timeout'))
          setTimeout(waitForFirebaseInit, 30);
        })();
      });
    }
    

    这个承诺现在可以在 router.beforeEach :

    router.beforeEach((to, from, next) => {
      if (to.matched.some(record => record.meta.requiresAuth)) {
        ensureFirebaseIsInitialized().then(() => {
          if(store.getters.authenticated) {
            next()
          }
          else {
            next('/login')
          }
        }).catch((error) => {
          console.log('firebase not initialized')
          next('/login')
        })
      } 
      else {
        next()
      }
    })
    

    这种方法是否有我不知道的问题,或者可以进一步改进?

    1 回复  |  直到 6 年前
        1
  •  0
  •   niclas_4    6 年前

    在你的路由器.js文件只需添加

    router.beforeEach((to,from,next) => {
      if(to.matched.some(record => record.meta.auth)){
      firebase.auth().onAuthStateChanged((user) => {
        if(!user)
        {
          next({
            path: '/'
    
          })
        } else {
          next() 
        }
      })}else next()    
    })
    

    这既可以防止页面访问,也可以在用户未经授权时将其踢出页面。我自己在我的一个网站上用过这个,到目前为止没有任何问题