2

Laravel教程:使用Laravel Sanctum 作为 API 认证来构建 Vue.js 应用

 3 years ago
source link: https://zhuanlan.zhihu.com/p/130226534
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Laravel教程:使用Laravel Sanctum 作为 API 认证来构建 Vue.js 应用

摈弃世俗浮躁,追求技术精湛

原文链接:https://learnku.com/laravel/t/43077
讨论请前往专业的 Laravel 开发者论坛:https://learnku.com/Laravel

身份验证系统是大多数现代应用程序的重要组成部分,因此应适当实施。

在本文中,您将学习到如何使用 Vue.jsLaravel Sanctum (以前的 Airlock )构建身份验证系统。

我们会创建一个前后端分离的项目,前后端将通过 REST API 相互交互。

让我开始吧!

后端 (Laravel)

有关 Laravel 安装说明,请移步 官方文档

在终端中创建一个新的 Laravel 项目

laravel new my-app
composer create-project --prefer-dist laravel/laravel my-app

我正在使用 Laravel Valet,它会自动将应用创建在 http://my-app.test 域名下。

你可以根据你本地的开发环境来配置并访问它。

创建一个名为 my-app 的数据库,并在 .env 文件中设置数据库连接,DB_DATABASE=my-app

安装 Laravel Sanctum

composer require laravel/sanctum

使用 vendor:publish 命令发布 Sanctum 配置文件和数据库迁移文件。sanctum 配置文件会创建在 config 目录中。

php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider"

运行数据库迁移,以创建用于存储API令牌的数据库表:

php artisan migrate

将 Sanctum 中间件添加到 app/Http/Kernel.php 中的 api 中间件组中

../app/Http/Kernel.php

use Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful;

...

    protected $middlewareGroups = [
        ...

        'api' => [
            EnsureFrontendRequestsAreStateful::class,
            'throttle:60,1',
            \Illuminate\Routing\Middleware\SubstituteBindings::class,
        ],
    ];

    ...
],

要为用户使用令牌,我们必须在 app/User.phpUser 模型中添加 HasApiTokens

../app/User.php

use Laravel\Sanctum\HasApiTokens;

class User extends Authenticatable
{
    use HasApiTokens, Notifiable;
}

第5步 #5

让我们为 User 模型创建一个 Seeder。我们稍后会需要它来测试登录过程。

php artisan make:seeder UsersTableSeeder

现在,让我们在database/seeds/UsersTableSeeder.phprun()方法中,插入以下代码:

DB::table('users')->insert([
    'name' => 'John Doe',
    'email' => '[email protected]',
    'password' => Hash::make('password')
]);

users 表生成 user, 让我们运行:

php artisan db:seed --class=UsersTableSeeder

现在我们的数据库里有一个新用户叫 "John Doe",邮箱是 "[email protected]",密码是 "password"。

第6步 #6

让我们在routes/api.php文件中创建一个/login路由:

../routes/api.php

use App\User;
use Illuminate\Support\Facades\Hash;

Route::post('/login', function (Request $request) {
    $data = $request->validate([
        'email' => 'required|email',
        'password' => 'required'
    ]);

    $user = User::where('email', $request->email)->first();

    if (!$user || !Hash::check($request->password, $user->password)) {
        return response([
            'message' => ['These credentials do not match our records.']
        ], 404);
    }

    $token = $user->createToken('my-app-token')->plainTextToken;

    $response = [
        'user' => $user,
        'token' => $token
    ];

    return response($response, 201);
});

第7步 #7

让我们发送一个以邮件[email protected]和密码password为参数的POST请求到http://my-app.test/api/login路由。你可以使用PostmanInsomnia软件包来完成。

如果一切顺利,我们会收到一个JSON对象作为对我们请求的响应:

{
    "user": {
        "id": 1,
        "name": "John Doe",
        "email": "[email protected]",
        "email_verified_at": null,
        "created_at": null,
        "updated_at": null
    },
    "token": "AbQzDgXa..."
}

第8步 #8

接下来,我们需要改变一些中间件。我们在/routes/api.php文件中把auth:api替换成auth:sanctum

../routes/api.php

Route::middleware('auth:sanctum')->get('/user', function (Request $request) {
    return $request->user();
});

第9步 #9

在我们开发前端之前,我们必须先设置跨源请求CORS处理。

../config/cors.php

    'paths' => ['api/*', 'login', 'logout'],

    'allowed_methods' => ['*'],

    'allowed_origins' => ['*'],

    'allowed_origins_patterns' => [],

    'allowed_headers' => ['*'],

    'exposed_headers' => [],

    'max_age' => 0,

    'supports_credentials' => true,
../.env

SANCTUM_STATEFUL_DOMAINS=127.0.0.1

前端 (Vue.js)

我们将使用 Vuex来进行状态管理, 用Vue Router 进行路由以及 axios 来进行HTTP请求

我们将使用 Vue CLI 来创建一个新的Vue项目。 如果你不熟悉这个标准的Vue.js开发工具,请阅读这个 指南.

在我们项目的目录中,让我们运行以下命令

vue create my-vue-app

选择 Manually select features ,然后选择RouterVuex

在成功创建了my-vue-app项目后,运行以下命令:

cd my-vue-app
npm run serve

现在我们的应用程序应该可以在http://localhost:8080/域名中被访问。

让我们创建一个新的Login视图文件

..src/views/Login.vue

<template>
  <div>
    <h1>Login</h1>
    <form @submit.prevent="login">
      <input type="email" name="email" v-model="email">
      <input type="password" name="password" v-model="password">
      <button type="submit">Login</button>
    </form>
  </div>
</template>

<script>
export default {
  data () {
    return {
      email: '',
      password: ''
    }
  },

  methods: {
    login () {
      this.$store
        .dispatch('login', {
          email: this.email,
          password: this.password
        })
        .then(() => {
          this.$router.push({ name: 'About' })
        })
        .catch(err => {
          console.log(err)
        })
    }
  }
}
</script>

在 "Vue Router "中,我们必须为 "Login "视图实现一个路由。

../src/router/index.js

import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from '../views/Home.vue'

Vue.use(VueRouter)

const routes = [
  {
    path: '/',
    name: 'Home',
    component: Home
  },
  {
    path: '/about',
    name: 'About',
    // 路由级别代码分割
    // 这会为这个路由生成一个单独的块(about.[hash].js)。
    // 当路由被访问的时候进行懒加载.
    component: () => import(/* webpackChunkName: "about" */ '../views/About.vue')
  },
  {
    path: '/login',
    name: 'Login',
    component: () => import(/* webpackChunkName: "login" */ '../views/Login.vue')
  }
]

const router = new VueRouter({
  mode: 'history',
  base: process.env.BASE_URL,
  routes
})

export default router

现在,如果我们在浏览器中导航到http://localhost:8080/login,我们可以看到一个登录页面。

我们必须在前端目录下安装axios来进行HTTP请求:

npm install axios

让我们在Vuex中实现一些用户认证操作(login/logout)

../src/store/index.js

import Vue from 'vue'
import Vuex from 'vuex'
import axios from 'axios'

Vue.use(Vuex)

axios.defaults.baseURL = 'http://app-backend.test/api'

export default new Vuex.Store({
  state: {
    user: null
  },

  mutations: {
    setUserData (state, userData) {
      state.user = userData
      localStorage.setItem('user', JSON.stringify(userData))
      axios.defaults.headers.common.Authorization = `Bearer ${userData.token}`
    },

    clearUserData () {
      localStorage.removeItem('user')
      location.reload()
    }
  },

  actions: {
    login ({ commit }, credentials) {
      return axios
        .post('/login', credentials)
        .then(({ data }) => {
          commit('setUserData', data)
        })
    },

    logout ({ commit }) {
      commit('clearUserData')
    }
  },

  getters : {
    isLogged: state => !!state.user
  }
})

登录成功后,我们要在user变量和localStorage中存储一些用户数据。

让我们来定义已认证和未认证页面的路由。

我们可以让 About 页面只对认证过的用户开放。

为了这个目的,让我们把 meta字段添加到 About 路径中。

让我们使用Vue Router的beforeEach方法来检查用户是否登录。如果用户没有通过认证,我们将把他们重定向回登录页面。

../src/router.index.js

import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from '../views/Home.vue'

Vue.use(VueRouter)

const routes = [
  {
    path: '/',
    name: 'Home',
    component: Home
  },
  {
    path: '/about',
    name: 'About',
    meta: {
      auth: true
    },
    component: () => import(/* webpackChunkName: "about" */ '../views/About.vue')
  },
  {
    path: '/login',
    name: 'Login',
    component: () => import(/* webpackChunkName: "login" */ '../views/Login.vue')
  }
]

const router = new VueRouter({
  mode: 'history',
  base: process.env.BASE_URL,
  routes
})

router.beforeEach((to, from, next) => {
  const loggedIn = localStorage.getItem('user')

  if (to.matched.some(record => record.meta.auth) && !loggedIn) {
    next('/login')
    return
  }
  next()
})

export default router

如果用户刷新了一个页面怎么办?要不要让他重新登录?

当然不是!

让我们在 Vue 实例中添加一个 created()方法来处理这种情况。

created () {
  const userInfo = localStorage.getItem('user')
  if (userInfo) {
    const userData = JSON.parse(userInfo)
    this.$store.commit('setUserData', userData)
  }
}

我们还需要处理令牌过期或用户未授权的情况。

让我们在created()方法中通过使用拦截器来实现。

然后我们修改后的 main.js文件看起来是这样的:

../src/main.js

import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'

Vue.config.productionTip = false

new Vue({
  router,
  store,
  created () {
    const userInfo = localStorage.getItem('user')
    if (userInfo) {
      const userData = JSON.parse(userInfo)
      this.$store.commit('setUserData', userData)
    }
    axios.interceptors.response.use(
      response => response,
      error => {
        if (error.response.status === 401) {
          this.$store.dispatch('logout')
        }
        return Promise.reject(error)
      }
    )
  },
  render: h => h(App)
}).$mount('#app')

我们还没有实现 Logout功能。让我们在 App.vue文件中实现这个功能。

另外,让我们只在用户登录时显示AboutLogout按钮。

../src/App.vue

<template>
  <div id="app">
    <div id="nav">
      <router-link to="/">Home</router-link> |
      <router-link to="/about" v-if="isLogged">About</router-link>
      <router-link to="/login" v-else>Login</router-link>
      <button type="button" @click="logout()" v-if="isLogged">
        Logout
      </button>
    </div>
    <router-view/>
  </div>
</template>

<script>
import { mapGetters } from 'vuex'

export default {
  computed: {
    ...mapGetters([
      'isLogged'
    ])
  },

  methods: {
    logout () {
      this.$store.dispatch('logout')
    }
  }
}
</script>

好了,我们的教程结束了。

希望这些信息对你有所帮助!

原文链接:https://learnku.com/laravel/t/43077
讨论请前往专业的 Laravel 开发者论坛:https://learnku.com/Laravel


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK