如何在Nuxt.js应用程序中实现身份验证

来自菜鸟教程
跳转至:导航、​搜索

介绍

在本教程中,您将使用 Auth 模块在 Nuxt.js 应用程序中实现身份验证。

出于本教程的目的,您将使用 JWT 进行身份验证。

下面是您将在本教程中构建的内容的快速演示:

您可以在 GitHub 上找到此应用程序的 源代码。

警告: 本教程中的几个包现在包含具有已知漏洞的依赖项。 在生产环境中,您可以通过升级这些软件包、寻找替代方案或创建带有补丁修复的分叉版本来解决这些问题。 但是,在教程的有限范围内,它按原样提供教育价值。


先决条件

要完成本教程,您需要:

  • Node.js 安装在本地,您可以按照【X57X】如何安装Node.js 并创建本地开发环境【X126X】进行。
  • 克隆 API 时需要安装有效的 Git,请参阅 Git 入门

熟悉 Vue.js 和 Nuxt.js 可能会有所帮助。 如果你开始使用 Nuxt.js,你可以参考这篇文章

本教程已使用 Node v13.13.0、npm v6.14.4、vue v2.6.11 和 nuxt v2.12.2 进行了验证。

第 1 步 — 启动示例 API

您可以自由使用最适合您的任何框架。 但是,为了快速开发,本教程将克隆一个使用 AdonisJs 构建的 API。

该 API 使用:

API 具有三个端点:

  • /register:用户注册端点
  • /login:用户认证端点
  • /me:用于获取当前认证用户详细信息的端点,它受 auth 中间件保护,这意味着用户必须经过认证才能访问端点

首先,在终端窗口中运行以下命令:

git clone https://github.com/do-community/jwt-auth-api.git

然后,导航到项目目录:

cd jwt-auth-api

并安装 API 依赖项:

npm install

注意:运行安装时,根据您运行的Node版本,您可能会遇到sqlite3版本4.0.1的问题。 请参阅 更改日志 以确定与您的环境的兼容性。

在最初发布时,Node 的最新版本是 10。 一种选择是将您的 Node 版本降级到 10.20.1(了解它即将停止支持)。 然后,运行 npm install

第二个选项是删除 package-lock.json 文件,这将导致系统查找 4.2.0 直到节点 13 才支持。 您可能还需要将您的 Node 版本降级为 13.13.0。 然后,运行 npm install

第三种选择是将 package.json 修改为当前 Node 版本支持的 sqlite3 版本,删除 package-lock.json,然后运行 npm install。 但是,在测试时,5.0.0 尚未发布以处理 Node 14+ 支持。

其他不兼容的症状包括以下错误:TypeError: Cannot read property 'data' of undefinedError: Cannot find module '[...]/node_modules/sqlite3/lib/binding/[...]/node_sqlite3.node'


接下来,将 .env.example 重命名为 .env

mv .env.example .env

并生成一个 APP_KEY

npx @adonisjs/cli@4.0.12 key:generate

你应该看到:

Outputgenerated: unique APP_KEY

完成后,让我们运行迁移:

npx @adonisjs/cli@4.0.12 migration:run

现在,您可以启动 API:

# ensure that you are in the `jwt-auth-api` project directory
npm start

您可以在 http://127.0.0.1:3333/api 上访问 API。 在本教程的剩余时间里,让它在终端窗口中运行。

第 2 步 — 创建 Nuxt.js 应用程序

现在,您可以创建一个 Nuxt.js 应用程序。 打开一个新的终端窗口并使用 vue-cli 使用 Nuxt 启动器模板初始化一个新的 Vue 项目:

npx vue-cli@2.9.6 init nuxt/starter nuxt-auth

注意: 在测试时,vue-cli 已被弃用。 @vue/cli 是当前 Vue 项目的命令行工具。 并且 @vue/cli-init 是遗留 vue-cli 项目的推荐方法。 但是,create-nuxt-app 是现代 Nuxt 项目的推荐方法。


接下来,您需要导航到项目目录:

cd nuxt-auth

并安装依赖项:

npm install

然后,您可以启动应用程序:

npm run dev

该应用程序应该在 http://localhost:3000 上运行。 您可以在 Web 浏览器中查看应用程序,以查看由 vue-cli 创建的默认 Vue 应用程序。

第三步——安装必要的 Nuxt.js 模块

现在,让我们安装您的应用所需的 Nuxt.js 模块。 您将使用 Nuxt Auth 模块Nuxt Axios 模块,因为 auth 模块在内部使用了 Axios:

# ensure that you are in the `nuxt-auth` project directory
npm install @nuxtjs/auth@4.5.1 @nuxtjs/axios@5.3.1 --save

完成后,打开 nuxt.config.js

nano nuxt.config.js

将以下代码添加到 nuxt.config.js

nuxt.config.js

module.exports = {
  // ...

  modules: [
    '@nuxtjs/axios',
    '@nuxtjs/auth'
  ],
}

注意: 此时,较新版本的 Nuxt 可能会遇到错误:Enable vuex store by creating 'store/index.js'。 这个错误可以通过添加一个空的index.js文件到存储目录来解决。


接下来,您需要设置模块。 将以下代码粘贴到 nuxt.config.js 中:

nuxt.config.js

module.exports = {
  // ...

  axios: {
    baseURL: 'http://127.0.0.1:3333/api'
  },

  auth: {
    strategies: {
      local: {
        endpoints: {
          login: { url: 'login', method: 'post', propertyName: 'data.token' },
          user: { url: 'me', method: 'get', propertyName: 'data' },
          logout: false
        }
      }
    }
  }
}

在这里,您设置 Axios 在发出请求时将使用的基本 URL。 在我们的例子中,我们引用了我们之前设置的示例 API。

然后,为与 API 上的策略对应的 local 策略定义身份验证端点:

  • 身份验证成功后,令牌将作为 data 对象内的 token 对象在响应中可用。
  • 同样,来自 /me 端点的响应将在 data 对象内。
  • 最后,您将 logout 设置为 false 因为您的 API 没有用于注销的端点。 当用户注销时,您只需从 localStorage 中删除令牌。

第 4 步 — 创建导航栏组件

要为您的应用设置样式,您可以使用 Bulma

打开 nuxt.config.js 并将以下代码粘贴到 head 对象内的 link 对象中:

nuxt.config.js

module.exports = {
  // ...
  head: {
    // ...
    link [
      // ...
      {
        rel: 'stylesheet',
        href: 'https://cdnjs.cloudflare.com/ajax/libs/bulma/0.7.1/css/bulma.min.css'
      }
    ]
  },
  // ...
}

现在,让我们创建导航栏组件:

nano components/Navbar.vue

并添加以下代码:

组件/Navbar.vue

<template>
  <nav class="navbar is-light">
    <div class="container">
      <div class="navbar-brand">
        <nuxt-link class="navbar-item" to="/">Nuxt Auth</nuxt-link>
        <button class="button navbar-burger">
          <span></span>
          <span></span>
          <span></span>
        </button>
      </div>
      <div class="navbar-menu">
        <div class="navbar-end">
          <div class="navbar-item has-dropdown is-hoverable">
            <a class="navbar-link">
              My Account
            </a>
            <div class="navbar-dropdown">
              <nuxt-link class="navbar-item" to="/profile">My Profile</nuxt-link>
              <hr class="navbar-divider"/>
              <a class="navbar-item">Logout</a>
            </div>
          </div>
          <template>
            <nuxt-link class="navbar-item" to="/register">Register</nuxt-link>
            <nuxt-link class="navbar-item" to="/login">Log In</nuxt-link>
          </template>
        </div>
      </div>
    </div>
  </nav>
</template>

导航栏组件包含指向 loginregisterprofilelogout 的链接。

接下来,让我们更新默认布局以使用 Navbar 组件。

打开default.vue

nano layouts/default.vue

并将内容替换为以下内容:

布局/default.vue

<template>
  <div>
    <Navbar/>
    <nuxt/>
  </div>
</template>

<script>
import Navbar from '~/components/Navbar'

export default {
  components: {
    Navbar
  }
}
</script>

另外,让我们更新主页。

打开index.vue

nano pages/index.vue

并将内容替换为以下内容:

页面/index.vue

<template>
  <section class="section">
    <div class="container">
      <h1 class="title">Nuxt Auth</h1>
    </div>
  </section>
</template>

此时,您应该有一个显示标题为 "Nuxt Auth" 的应用程序,带有带有导航链接的标题栏:

第 5 步 - 处理用户注册

pages 目录下,新建一个 register.vue 文件:

nano pages/register.vue

并添加以下代码:

页面/register.vue

<template>
  <section class="section">
    <div class="container">
      <div class="columns">
        <div class="column is-4 is-offset-4">
          <h2 class="title has-text-centered">Register!</h2>

          <Notification :message="error" v-if="error"/>

          <form method="post" @submit.prevent="register">
            <div class="field">
              <label class="label">Username</label>
              <div class="control">
                <input
                  type="text"
                  class="input"
                  name="username"
                  v-model="username"
                  required 
                />
              </div>
            </div>
            <div class="field">
              <label class="label">Email</label>
              <div class="control">
                <input
                  type="email"
                  class="input"
                  name="email"
                  v-model="email"
                  required
                />
              </div>
            </div>
            <div class="field">
              <label class="label">Password</label>
              <div class="control">
                <input
                  type="password"
                  class="input"
                  name="password"
                  v-model="password"
                  required
                />
              </div>
            </div>
            <div class="control">
              <button type="submit" class="button is-dark is-fullwidth">Register</button>
            </div>
          </form>

          <div class="has-text-centered" style="margin-top: 20px">
            Already got an account? <nuxt-link to="/login">Login</nuxt-link>
          </div>
        </div>
      </div>
    </div>
  </section>
</template>

<script>
import Notification from '~/components/Notification'

export default {
  components: {
    Notification,
  },

  data() {
    return {
      username: '',
      email: '',
      password: '',
      error: null
    }
  },

  methods: {
    async register() {
      try {
        await this.$axios.post('register', {
          username: this.username,
          email: this.email,
          password: this.password
        })

        await this.$auth.loginWith('local', {
          data: {
          email: this.email,
          password: this.password
          },
        })

        this.$router.push('/')
      } catch (e) {
        this.error = e.response.data.message
      }
    }
  }
}
</script>

这包含一个包含三个字段的表单:usernameemailpassword。 每个字段都绑定到组件上的相应数据。 提交表单时,将调用 register 方法。 使用 Axios 模块,您向 /register 端点发出发布请求,并传递用户数据。 如果注册成功,则使用 Auth 模块的 loginWith(),使用 local 策略并传递用户数据来登录用户。 然后,您将用户重定向到主页。 如果在注册过程中出现错误,则将 error 数据设置为从 API 响应中获取的错误消息。

如果有错误,错误消息将由 Notification 组件显示。

components 内新建一个 Notification.vue 文件:

nano components/Notifaction.vue

并将下面的代码粘贴到其中:

组件/Notification.vue

<template>
  <div class="notification is-danger">
    {{ message }}
  </div>
</template>

<script>
export default {
  name: 'Notification',
  props: ['message']
}
</script>

Notification 组件接受 message 道具,这是错误消息。

现在,您可以测试用户注册:

第 6 步 - 处理登录和注销的 LoggedUsers

注册成功后,用户应该已经登录,但应用程序目前无法知道用户是否登录。 因此,让我们通过更新 Navbar 组件并添加一些计算属性来解决这个问题。

在你这样做之前,让我们首先通过在 store 目录中创建一个 index.js 文件来激活 Vuex 商店。 Auth 模块将用户身份验证状态以及 Vuex 状态中的用户详细信息存储在 auth 对象中。 因此,您可以检查用户是否使用 this.$store.state.auth.loggedIn 登录,这将返回 truefalse。 类似地,您可以通过 this.$store.state.auth.user 获取用户的详细信息,如果没有用户登录,则为 null

注意:您也可以分别使用this.$auth.loggedInthis.$auth.user直接通过Auth模块访问用户认证状态以及用户详细信息。


由于您可能希望在应用程序的多个位置使用计算属性,让我们创建商店 getter。

打开index.js

nano store/index.js

并将下面的代码粘贴到其中:

存储/index.js

export const getters = {
  isAuthenticated(state) {
    return state.auth.loggedIn
  },

  loggedInUser(state) {
    return state.auth.user
  }
}

在这里,您创建了两个 getter。 第一个 (isAuthenticated) 将返回用户的身份验证状态,第二个 (loggedInUser) 将返回详细信息或登录用户。

接下来,让我们更新 Navbar 组件以使用 getter。 将 components/Navbar.vue 的内容替换为以下内容:

组件/Navbar.vue

<template>
  <nav class="navbar is-light">
    <div class="container">
      <div class="navbar-brand">
        <nuxt-link class="navbar-item" to="/">Nuxt Auth</nuxt-link>
        <button class="button navbar-burger">
          <span></span>
          <span></span>
          <span></span>
        </button>
      </div>
      <div class="navbar-menu">
        <div class="navbar-end">
          <div class="navbar-item has-dropdown is-hoverable" v-if="isAuthenticated">
            <a class="navbar-link">
              {{ loggedInUser.username }}
            </a>
            <div class="navbar-dropdown">
              <nuxt-link class="navbar-item" to="/profile">My Profile</nuxt-link>
              <hr class="navbar-divider"/>
              <a class="navbar-item">Logout</a>
            </div>
          </div>
          <template v-else>
            <nuxt-link class="navbar-item" to="/register">Register</nuxt-link>
            <nuxt-link class="navbar-item" to="/login">Log In</nuxt-link>
          </template>
        </div>
      </div>
    </div>
  </nav>
</template>

<script>
import { mapGetters } from 'vuex'

export default {
  computed: {
    ...mapGetters(['isAuthenticated', 'loggedInUser'])
  }
}
</script>

您可以通过使用扩展运算符 (...) 从 mapGetters 中提取 getter 来创建计算属性。 然后使用 isAuthenticated,根据用户是否登录显示用户菜单或指向 loginregister 的链接。 此外,您使用 loggedInUser 显示经过身份验证的用户用户名。

现在,如果您刷新应用程序,您应该会看到类似于以下内容:

第 7 步 — 处理用户登录

现在,让我们允许返回用户登录。

pages 目录下新建一个 login.vue 文件:

nano pages/login.vue

并将下面的代码粘贴到其中:

页面/login.vue

<template>
  <section class="section">
    <div class="container">
      <div class="columns">
        <div class="column is-4 is-offset-4">
          <h2 class="title has-text-centered">Welcome back!</h2>

          <Notification :message="error" v-if="error"/>

          <form method="post" @submit.prevent="login">
            <div class="field">
              <label class="label">Email</label>
              <div class="control">
                <input
                  type="email"
                  class="input"
                  name="email"
                  v-model="email"
                />
              </div>
            </div>
            <div class="field">
              <label class="label">Password</label>
              <div class="control">
                <input
                  type="password"
                  class="input"
                  name="password"
                  v-model="password"
                />
              </div>
            </div>
            <div class="control">
              <button type="submit" class="button is-dark is-fullwidth">Log In</button>
            </div>
          </form>
          <div class="has-text-centered" style="margin-top: 20px">
            <p>
              Don't have an account? <nuxt-link to="/register">Register</nuxt-link>
            </p>
          </div>
        </div>
      </div>
    </div>
  </section>
</template>

<script>
import Notification from '~/components/Notification'

export default {
  components: {
    Notification,
  },

  data() {
    return {
      email: '',
      password: '',
      error: null
    }
  },

  methods: {
    async login() {
      try {
        await this.$auth.loginWith('local', {
          data: {
          email: this.email,
          password: this.password
          }
        })

        this.$router.push('/')
      } catch (e) {
        this.error = e.response.data.message
      }
    }
  }
}
</script>

这与 register 页面非常相似。 该表单包含两个字段:emailpassword。 提交表单时,将调用 login 方法。 使用 Auth 模块 loginWith() 并传递用户数据,您可以登录用户。 如果身份验证成功,则将用户重定向到主页。 否则,将 error 设置为从 API 响应中获取的错误消息。 同样,您正在使用之前的 Notification 组件来显示错误消息。

第 8 步 — 显示用户资料

让我们允许登录的用户查看他们的个人资料。

pages 目录下新建一个 profile.vue 文件:

nano pages/profile.vue

并将下面的代码粘贴到其中:

页面/profile.vue

<template>
  <section class="section">
    <div class="container">
      <h2 class="title">My Profile</h2>
      <div class="content">
        <p>
          <strong>Username:</strong>
          {{ loggedInUser.username }}
        </p>
        <p>
          <strong>Email:</strong>
          {{ loggedInUser.email }}
        </p>
      </div>
    </div>
  </section>
</template>

<script>
import { mapGetters } from 'vuex'

export default {
  computed: {
    ...mapGetters(['loggedInUser'])
  }
}
</script>

请注意您如何使用前面的 loggedInUser getter 来显示用户详细信息。

单击 My Profile 链接应会显示 My Profile 页面。

第 9 步 — 注销用户

更新导航栏组件内的注销链接。

打开Navbar.vue

nano components/Navbar.vue

修改注销链接以使用@click="logout"

组件/Navbar.vue

// ...
<div class="navbar-dropdown">
  <nuxt-link class="navbar-item" to="/profile">My Profile</nuxt-link>
  <hr class="navbar-divider"/>
  <a class="navbar-item"  @click="logout">Logout</a>
</div>
// ...

单击注销链接时,将触发 logout 方法。

接下来,让我们在 Navbar 组件的脚本部分中添加 logout 方法:

组件/Navbar.vue

// ...

export default {
  // ...
  methods: {
    async logout() {
      await this.$auth.logout();
    },
  },
}

您调用 Auth 模块的 logout()。 这将从本地存储中删除用户的令牌并将用户重定向到主页。

第 10 步 — 限制个人资料页面

就目前而言,任何人都可以访问 profile 页面。 如果用户没有登录,就会导致错误。

要解决此问题,您需要将个人资料页面限制为仅登录用户。 幸运的是,您可以使用 Auth 模块实现这一点。 Auth 模块带有一个 auth 中间件,您可以在这种情况下使用它。

因此,让我们将 auth 中间件添加到 profile 页面。 更新 script 部分如下:

页面/profile.vue

// ...

export default {
  middleware: 'auth',
  // ...
}

现在,当未登录的用户尝试访问 profile 页面时,用户将被重定向到 login 页面。

第 11 步 — 创建访客中间件

同样,即使作为登录用户,您仍然可以访问登录和注册页面。 解决此问题的一种方法是将登录和注册页面限制为仅限未登录的用户。 您可以通过创建来宾中间件来做到这一点。

middleware 目录下,新建一个 guest.js 文件:

nano middleware/guest.js

并将下面的代码粘贴到其中:

中间件/guest.js

export default function ({ store, redirect }) {
  if (store.state.auth.loggedIn) {
    return redirect('/')
  }
}

中间件接受上下文作为它的第一个参数。 所以你从上下文中提取 storeredirect 。 然后,您检查用户是否已登录,然后将用户重定向到主页。 否则,您允许正常执行请求。

接下来,让我们使用这个中间件。 更新 loginregisterscript 部分,如下所示:

pages/login.vue 和 pages/register.vue

// ...

export default {
  middleware: 'guest',
  // ...
}

现在,一切都将按预期进行。

结论

在本教程中,您了解了如何使用 Auth 模块在 Nuxt.js 应用程序中实现身份验证。 您还了解了如何通过使用中间件来保持身份验证流程流畅。

要了解有关 Auth 模块的更多信息,请查看 文档

如果您想了解有关 Vue.js 的更多信息,请查看 我们的 Vue.js 主题页面 以获取练习和编程项目。