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

介绍

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

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

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

Animated gif of the app showing a user signing in

您可以在GitHub上找到此应用程序的源代码(https://github.com/do-community/nuxt-auth-app)。

<$>[警告] **警告:**本教程中的几个包现在包含具有已知漏洞的依赖性。在生产设置中,你会通过升级这些包,找到替代品或创建修补版本来解决这些问题。

前提条件

要完成本教程,您将需要:

一些熟悉 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有三个终点:

  • /register:用户注册的终端点
  • /login:用户身份验证的终端点
  • /me:获取当前身份验证的用户的详细信息的终端点,并且由auth中间软件保护,这意味着用户必须进行身份验证才能访问终端点

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

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

然后,导航到项目目录:

1cd jwt-auth-api

并安装 API 依赖:

1npm install

<$>[注] :在运行安装时,您可能会遇到与 sqlite3 版本 4.0.1 的问题,取决于您正在运行的 Node 版本。

在原来的发布时,Node 的最新版本是 10. 一个选项是将您的 Node 版本降级到 10.20.1(了解到它正在接近终止支持)。

第二种选择是删除package-lock.json文件,这将导致系统搜索4.2.0,直到Node 13得到支持,您可能还需要将Node版本降级到13.13.0

第三种选择是将「package.json」修改为您当前版本的 Node 支持的「sqlite3」版本,删除「package-lock.json」并运行「npm install」。

其他不兼容性症状包括以下错误: TypeError: 无法读取 undefined 的属性 'data' 和 Error: 无法找到模块 '[...]/node_modules/sqlite3/lib/binding/[...]/node_sqlite3.node'。

然后将.env.example重命名为.env:

1mv .env.example .env

然后创建一个APP_KEY:

1npx @adonisjs/[email protected] key:generate

你应该看到:

1[secondary_label Output]
2generated: unique APP_KEY

一旦完成,让我们运行迁移:

1npx @adonisjs/[email protected] migration:run

现在,您可以启动 API:

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

您可以在「http://127.0.0.1:3333/api」上访问API。 让此操作在终端窗口中运行,直到本教程的剩余时间。

步骤 2 — 创建 Nuxt.js 应用

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

1npx [email protected] init nuxt/starter nuxt-auth

<$>[注] **注:**在测试时,‘vue-cli’被贬值,‘@vue/cli’是Vue项目的当前命令行工具,而‘@vue/cli-init’是传统‘vue-cli’项目的推荐方法,然而,‘create-nuxt-app’是现代Nuxt项目的推荐方法。

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

1cd nuxt-auth

安装依赖性:

1npm install

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

1npm run dev

該應用程式應在「http://localhost:3000」上執行,您可以在網頁瀏覽器中查看由「vue-cli」創建的默認 Vue 應用程式。

步骤 3 — 安装必要的 Nuxt.js 模块

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

1# ensure that you are in the `nuxt-auth` project directory
2npm install @nuxtjs/[email protected] @nuxtjs/[email protected] --save

一旦完成,打开nuxt.config.js:

1nano nuxt.config.js

将下面的代码添加到 nuxt.config.js:

1[label nuxt.config.js]
2module.exports = {
3  // ...
4
5  modules: [
6    '@nuxtjs/axios',
7    '@nuxtjs/auth'
8  ],
9}

<$>[注] **注:**此时,新版本的Nuxt可能会遇到错误:‘通过创建‘store/index.js’来启用 vuex store。

接下来,您需要设置模块,然后将下面的代码插入到 nuxt.config.js:

 1[label nuxt.config.js]
 2module.exports = {
 3  // ...
 4
 5  axios: {
 6    baseURL: 'http://127.0.0.1:3333/api'
 7  },
 8
 9  auth: {
10    strategies: {
11      local: {
12        endpoints: {
13          login: { url: 'login', method: 'post', propertyName: 'data.token' },
14          user: { url: 'me', method: 'get', propertyName: 'data' },
15          logout: false
16        }
17      }
18    }
19  }
20}

在这里,您设置了Axios在提出请求时使用的基本URL,在我们的情况下,我们正在引用我们之前设置的样本API。

然后,您定义本地策略的身份验证终端,与您的 API 中的终端相匹配:

  • 成功身份验证后,代币将在响应中作为一个数据对象中的代币对象提供
  • 同样,来自/me终端的响应将在一个数据对象中提供
  • 最后,您将登录设置为,因为您的 API 没有登录终端。

步骤 4 – 创建 Navbar 组件

要用你的心,你可以用你的心,你可以用你的心。

打开nuxt.config.js,并将下面的代码粘贴到位于头部对象内的链接对象中:

 1[label nuxt.config.js]
 2module.exports = {
 3  // ...
 4  head: {
 5    // ...
 6    link [
 7      // ...
 8      {
 9        rel: 'stylesheet',
10        href: 'https://cdnjs.cloudflare.com/ajax/libs/bulma/0.7.1/css/bulma.min.css'
11      }
12    ]
13  },
14  // ...
15}

现在,让我们创建Navbar组件:

1nano components/Navbar.vue

然后添加以下代码:

 1[label components/Navbar.vue]
 2<template>
 3  <nav class="navbar is-light">
 4    <div class="container">
 5      <div class="navbar-brand">
 6        <nuxt-link class="navbar-item" to="/">Nuxt Auth</nuxt-link>
 7        <button class="button navbar-burger">
 8          <span></span>
 9          <span></span>
10          <span></span>
11        </button>
12      </div>
13      <div class="navbar-menu">
14        <div class="navbar-end">
15          <div class="navbar-item has-dropdown is-hoverable">
16            <a class="navbar-link">
17              My Account
18            </a>
19            <div class="navbar-dropdown">
20              <nuxt-link class="navbar-item" to="/profile">My Profile</nuxt-link>
21              <hr class="navbar-divider"/>
22              <a class="navbar-item">Logout</a>
23            </div>
24          </div>
25          <template>
26            <nuxt-link class="navbar-item" to="/register">Register</nuxt-link>
27            <nuxt-link class="navbar-item" to="/login">Log In</nuxt-link>
28          </template>
29        </div>
30      </div>
31    </div>
32  </nav>
33</template>

Navbar组件包含链接到登录,注册,配置文件登录

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

打开Default.vue:

1nano layouts/default.vue

将内容替换为以下内容:

 1[label layouts/default.vue]
 2<template>
 3  <div>
 4    <Navbar/>
 5    <nuxt/>
 6  </div>
 7</template>
 8
 9<script>
10import Navbar from '~/components/Navbar'
11
12export default {
13  components: {
14    Navbar
15  }
16}
17</script>

接下来,让我们更新主页。

打开index.vue:

1nano pages/index.vue

将内容替换为以下内容:

1[label pages/index.vue]
2<template>
3  <section class="section">
4    <div class="container">
5      <h1 class="title">Nuxt Auth</h1>
6    </div>
7  </section>
8</template>

在此时刻,你应该有一个应用程序,显示一个标题Nuxt Auth与标题栏与导航链接:

App page with title and header bar

步骤 5 – 处理用户注册

页面目录中,创建一个新的register.vue文件:

1nano pages/register.vue

然后添加以下代码:

  1[label pages/register.vue]
  2<template>
  3  <section class="section">
  4    <div class="container">
  5      <div class="columns">
  6        <div class="column is-4 is-offset-4">
  7          <h2 class="title has-text-centered">Register!</h2>
  8
  9          <Notification :message="error" v-if="error"/>
 10
 11          <form method="post" @submit.prevent="register">
 12            <div class="field">
 13              <label class="label">Username</label>
 14              <div class="control">
 15                <input
 16                  type="text"
 17                  class="input"
 18                  name="username"
 19                  v-model="username"
 20                  required 
 21                />
 22              </div>
 23            </div>
 24            <div class="field">
 25              <label class="label">Email</label>
 26              <div class="control">
 27                <input
 28                  type="email"
 29                  class="input"
 30                  name="email"
 31                  v-model="email"
 32                  required
 33                />
 34              </div>
 35            </div>
 36            <div class="field">
 37              <label class="label">Password</label>
 38              <div class="control">
 39                <input
 40                  type="password"
 41                  class="input"
 42                  name="password"
 43                  v-model="password"
 44                  required
 45                />
 46              </div>
 47            </div>
 48            <div class="control">
 49              <button type="submit" class="button is-dark is-fullwidth">Register</button>
 50            </div>
 51          </form>
 52
 53          <div class="has-text-centered" style="margin-top: 20px">
 54            Already got an account? <nuxt-link to="/login">Login</nuxt-link>
 55          </div>
 56        </div>
 57      </div>
 58    </div>
 59  </section>
 60</template>
 61
 62<script>
 63import Notification from '~/components/Notification'
 64
 65export default {
 66  components: {
 67    Notification,
 68  },
 69
 70  data() {
 71    return {
 72      username: '',
 73      email: '',
 74      password: '',
 75      error: null
 76    }
 77  },
 78
 79  methods: {
 80    async register() {
 81      try {
 82        await this.$axios.post('register', {
 83          username: this.username,
 84          email: this.email,
 85          password: this.password
 86        })
 87
 88        await this.$auth.loginWith('local', {
 89          data: {
 90          email: this.email,
 91          password: this.password
 92          },
 93        })
 94
 95        this.$router.push('/')
 96      } catch (e) {
 97        this.error = e.response.data.message
 98      }
 99    }
100  }
101}
102</script>

此表格包含三个字段:用户名,电子邮件密码。每个字段都与相应的组件数据相关联。在提交表单时,会被调用一个注册方法。使用 Axios 模块,您将邮件请求发送到/register终端,通过用户数据。如果注册成功,则使用 Auth 模块的loginWith(),使用本地策略并传输用户数据来登录用户。然后,您将用户重定向到主页。

如果出现错误,通知组件会显示错误消息。

组件中创建一个新的Notification.vue文件:

1nano components/Notifaction.vue

然后将下面的代码插入其中:

 1[label components/Notification.vue]
 2<template>
 3  <div class="notification is-danger">
 4    {{ message }}
 5  </div>
 6</template>
 7
 8<script>
 9export default {
10  name: 'Notification',
11  props: ['message']
12}
13</script>

通知组件接受一个消息附件,即错误消息。

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

Register page with Username, Email, and Password fields

Register page but with a notification message to the user that there was an error

步骤 6 – 处理已登录的用户登录和退出

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

在你做这件事之前,让我们先通过在商店目录中创建一个index.js文件来激活Vuex商店。Auth模块将用户身份验证状态以及Vuex状态中的用户详细信息存储在一个auth对象中,这样你就可以检查用户是否已登录到this.$store.state.auth.loggedIn,如果没有用户登录,用户的详细信息也会变为null

<$>[注] **注:**您还可以通过使用this.$auth.loggedInthis.$auth.user的Auth模块直接访问用户身份验证状态和用户详细信息。

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

打开index.js:

1nano store/index.js

然后将下面的代码插入其中:

 1[label store/index.js]
 2export const getters = {
 3  isAuthenticated(state) {
 4    return state.auth.loggedIn
 5  },
 6
 7  loggedInUser(state) {
 8    return state.auth.user
 9  }
10}

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

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

 1[label components/Navbar.vue]
 2<template>
 3  <nav class="navbar is-light">
 4    <div class="container">
 5      <div class="navbar-brand">
 6        <nuxt-link class="navbar-item" to="/">Nuxt Auth</nuxt-link>
 7        <button class="button navbar-burger">
 8          <span></span>
 9          <span></span>
10          <span></span>
11        </button>
12      </div>
13      <div class="navbar-menu">
14        <div class="navbar-end">
15          <div class="navbar-item has-dropdown is-hoverable" v-if="isAuthenticated">
16            <a class="navbar-link">
17              {{ loggedInUser.username }}
18            </a>
19            <div class="navbar-dropdown">
20              <nuxt-link class="navbar-item" to="/profile">My Profile</nuxt-link>
21              <hr class="navbar-divider"/>
22              <a class="navbar-item">Logout</a>
23            </div>
24          </div>
25          <template v-else>
26            <nuxt-link class="navbar-item" to="/register">Register</nuxt-link>
27            <nuxt-link class="navbar-item" to="/login">Log In</nuxt-link>
28          </template>
29        </div>
30      </div>
31    </div>
32  </nav>
33</template>
34
35<script>
36import { mapGetters } from 'vuex'
37
38export default {
39  computed: {
40    ...mapGetters(['isAuthenticated', 'loggedInUser'])
41  }
42}
43</script>

您使用扩散运算器(...)创建计算属性,从mapGetters中提取输出器,然后使用isAuthenticated显示用户菜单或链接到loginregister,取决于用户是否已登录。

现在,如果你给你的应用程序一个更新,你应该看到类似于下面的东西:

App page with the user's username in the header

步骤 7 – 处理用户登录

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

页面目录中创建一个新的login.vue文件:

1nano pages/login.vue

然后将下面的代码插入其中:

 1[label pages/login.vue]
 2<template>
 3  <section class="section">
 4    <div class="container">
 5      <div class="columns">
 6        <div class="column is-4 is-offset-4">
 7          <h2 class="title has-text-centered">Welcome back!</h2>
 8
 9          <Notification :message="error" v-if="error"/>
10
11          <form method="post" @submit.prevent="login">
12            <div class="field">
13              <label class="label">Email</label>
14              <div class="control">
15                <input
16                  type="email"
17                  class="input"
18                  name="email"
19                  v-model="email"
20                />
21              </div>
22            </div>
23            <div class="field">
24              <label class="label">Password</label>
25              <div class="control">
26                <input
27                  type="password"
28                  class="input"
29                  name="password"
30                  v-model="password"
31                />
32              </div>
33            </div>
34            <div class="control">
35              <button type="submit" class="button is-dark is-fullwidth">Log In</button>
36            </div>
37          </form>
38          <div class="has-text-centered" style="margin-top: 20px">
39            <p>
40              Don't have an account? <nuxt-link to="/register">Register</nuxt-link>
41            </p>
42          </div>
43        </div>
44      </div>
45    </div>
46  </section>
47</template>
48
49<script>
50import Notification from '~/components/Notification'
51
52export default {
53  components: {
54    Notification,
55  },
56
57  data() {
58    return {
59      email: '',
60      password: '',
61      error: null
62    }
63  },
64
65  methods: {
66    async login() {
67      try {
68        await this.$auth.loginWith('local', {
69          data: {
70          email: this.email,
71          password: this.password
72          }
73        })
74
75        this.$router.push('/')
76      } catch (e) {
77        this.error = e.response.data.message
78      }
79    }
80  }
81}
82</script>

这与注册页面非常相似。表单中包含两个字段:电子邮件密码。在提交表单时,会被调用一个登录方法。使用Auth模块loginWith()并通过用户数据,您会登录用户。如果验证成功,您会将用户重定向到主页。否则,将错误设置为来自API响应的错误消息。再次,您正在使用先前的通知组件来显示错误消息。

App Welcome Back page containing two fields: email and password

步骤 8 – 显示用户配置文件

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

页面目录中创建一个新的profile.vue文件:

1nano pages/profile.vue

然后将下面的代码插入其中:

 1[label pages/profile.vue]
 2<template>
 3  <section class="section">
 4    <div class="container">
 5      <h2 class="title">My Profile</h2>
 6      <div class="content">
 7        <p>
 8          <strong>Username:</strong>
 9          {{ loggedInUser.username }}
10        </p>
11        <p>
12          <strong>Email:</strong>
13          {{ loggedInUser.email }}
14        </p>
15      </div>
16    </div>
17  </section>
18</template>
19
20<script>
21import { mapGetters } from 'vuex'
22
23export default {
24  computed: {
25    ...mapGetters(['loggedInUser'])
26  }
27}
28</script>

注意您如何使用loggedInUser接入器以显示用户详细信息。

点击我的个人资料链接应显示一个我的个人资料页面。

My Profile page displaying username and email

步骤 9 - 登录用户

更新 Navbar 组件内部的登录链接。

打开Navbar.vue:

1nano components/Navbar.vue

更改登录链接以使用 @click="logout":

1[label components/Navbar.vue]
2// ...
3<div class="navbar-dropdown">
4  <nuxt-link class="navbar-item" to="/profile">My Profile</nuxt-link>
5  <hr class="navbar-divider"/>
6  <a class="navbar-item"  @click="logout">Logout</a>
7</div>
8// ...

当点击logout链接时,它会触发一个logout方法。

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

 1[label components/Navbar.vue]
 2// ...
 3
 4export default {
 5  // ...
 6  methods: {
 7    async logout() {
 8      await this.$auth.logout();
 9    },
10  },
11}

您将调用Auth模块的logout()。这将从 localstorage中删除用户的代币,并将用户重定向到主页。

步骤 10 – 限制个人资料页面

现在,任何人都可以访问个人资料页面,如果用户未登录,则会导致错误。

TypeError on app page

要解决此问题,您需要将个人资料页面限制在仅限于已登录的用户中。 幸运的是,我们可以通过Auth模块实现这一目标。

因此,让我们将auth中间软件添加到profile页面上,以以下方式更新script部分:

1[label pages/profile.vue]
2// ...
3
4export default {
5  middleware: 'auth',
6  // ...
7}

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

步骤 11 – 创建客人中间件

再一次,即使作为一个登录的用户,你仍然可以访问登录和注册页面。解决这一问题的一种方法是将登录和注册页面限制在没有登录的用户身上。

middleware目录中,创建一个新的guest.js文件:

1nano middleware/guest.js

然后将下面的代码插入其中:

1[label middleware/guest.js]
2export default function ({ store, redirect }) {
3  if (store.state.auth.loggedIn) {
4    return redirect('/')
5  }
6}

一个中间件接受文本为其第一个论点,所以你从文本中提取商店重定向。然后,你检查用户是否已登录,然后将用户重定向到主页。

接下来,让我们利用这个中间软件更新登录注册脚本部分如下:

1[label pages/login.vue and pages/register.vue]
2// ...
3
4export default {
5  middleware: 'guest',
6  // ...
7}

现在,一切都将按预期运作。

结论

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

要了解有关 Auth 模块的更多信息,请参阅 docs

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

Published At
Categories with 技术
comments powered by Disqus