javascript --- [express+ vue2.x + elementUI]登陆的流程梳理

说明

涉及到以下知识点:

  • 登陆的具体流程
  • express、vue2.x、elementUI、axios、jwt、assert 登陆方面的API使用
  • 中间件的使用
  • 前后端通过http状态码,进行响应的操作(这里主要是401)
  • 密码验证(bcrypt的hashSync方法对明文密码进行加密,compareSync方法对加密的密码进行验证)
  • token的使用(后端jwt.sign生成token, 前端使用浏览器缓存存储token,后端验证token)

登录

界面

使用Vue2.x + elementUI实现的登录界面

<template><div class="login-container"><el-card header="请先登录" class="login-card"><!-- @submit.native.prevent阻止表单的默认提交行为,并将提交的事件处理函数,绑定到login上 --><el-form @submit.native.prevent="login"><el-form-item label="用户名"><el-input v-model="model.username"></el-input></el-form-item><el-form-item label="密码"><el-input type="password" v-model="model.password"></el-input></el-form-item><el-form-item><el-button type="primary" native-type="submit">登录</el-button></el-form-item></el-form></el-card></div>
</template>

逻辑

  • 前端传入用户名和密码
<template><el-form @submit.native.prevent="login"><el-form-item label="用户名"><el-input v-model="model.username"></el-input></el-form-item><el-form-item label="密码"><el-input v-model="model.password" type="password"></el-input></el-form-item></el-form>
</template>
<script>export default {data(){return {model: {}}},methods:{async login(){// post提交数据,返回的是一个tokenconst res = await this.$http.post('login', this.model)}}}
</script>
  • 后端得到数据后,与数据库进行对比
    • 根据用户名,查找数据库中的用户信息
    • 对密码使用bcrypt的对比操作
  • 通过验证后,返回一个token
app.post('/admin/api/login', async(req, res)=>{const { username, password } = req.bodyconst AdminUser = require('../../models/AdminUser')// 根据用户名 查找数据库中的用户信息const user = await AdminUser.findOne({username}).select('+password')if(!user) return res.status(422).send({message:'用户名不存在'})// 走到这里,用户查找到了const isValid = password && user.password && require('bcrypt').compareSync(password, user.password)if(!isValid) return res.status(422).send({ message:'密码错误'}) // 到这里就使用jwt生成token并返回了// 首先需要安装: npm i jsonwebtokenconst token = jwt.sign({id: username._id}, app.get('secret'))res.send({token})
})

注意: 上面的jwt.sign接收2个参数,第一个应该是作为区分的一个对象.如用户的id. 第二个是一个密钥字符串.在express中使用app.set(‘secret’, ‘Marron’),将密钥保存在全局app中. 之后使用app.get(‘secret’)获取该密钥.

jwt.sign返回一个散列值,就是我们需要的token

异常处理

http响应并不总是成功的,比如在登录这一块.用户名或密码很容易不成功.这个时候就会返回一个403 forbidden,其中一个比较好的解决方案是: 使用axios的拦截器,对http的返回进行拦截. 若发生错误,使用Vue原型上面的$message.error(前面已经使用elementUI挂载了)方法给出错误提示

// admin/src/http.js
import axios from 'axios'
import Vue from 'vue'const http = axios.create({baseURL: 'http://localhost:3000/admin/api'
})// 拦截返回的http
http.interceptors.response.use(res=>{// http响应是成功的return res},err=>{if(err.message.response.data.message){// http响应失败.使用Vue原型上面的方法弹出错误Vue.prototype.$message.error({type: 'error',message: err.message.response.data.message})return Promise.reject(err)}}
)

前端得到token

  1. 前端得到token中,首先将其保存在浏览器缓存中(sessionStorage或localStorage)
  2. 然后跳转到首页,在vue中使用this.$router.push('/')
  3. 弹出提示框,登录成功, 在vue2.x + elementUI中使用this.$message({ type:'success', message:'登录成功'})
<template><el-form @submit.native.prevent="login"><el-form-item label="用户名"><el-input v-model="model.username"></el-input></el-form-item><el-form-item label="密码"><el-input v-model="model.password" type="password"></el-input></el-form-item></el-form>
</template>
<script>export default {data(){return {model: {}}},methods:{async login(){// post提交数据,返回的是一个tokenconst res = await this.$http.post('login', this.model)// 将token存入localStorage中(前端磁盘),浏览器关闭了,下次还能访问到localStorage.token = res.data.token// 跳转到首页this.$router.push('/')// 弹出登录成功this.$message({type: 'success',message: '登录成功'})}}}
</script>

登录验证

前端请求添加token

如果用户登录了,那么在浏览器的localStorage中必然会保存token.在发送登录请求给后端时,需要带上这个token.在axios中使用全局的请求拦截(http.interceptors.request.use)来实现这个功能

const http = axios.create({baseURL: 'http://localhost:3000/admin/api'
})
http.interceptors.request.use(config => {if(localStorage.token) {// 给请求头部加tokenconfig.headers.Authorization = 'Bearer ' + localStorage.token}return config},err => {return Promise.reject(err)}
)
// 在使用`config.headers.Authorization`之后,每次http请求都会附带一个 Authorization 请求头.
// Authorization值的最前面加 'Bearer '的原因是为了符合规范

在添加好了对所有路由的前端请求拦截后,如果因为登录原因,出现的错误.后端一般返回的是401状态码,这个时候,需要跳转到登陆页面,下面对状态码 401 的返回值进行处理

const http = axios.create({baseURL: 'http://localhost:3000/admin/api'
})
http.interceptors.response.use(res => { return res},err => {// 这里处理报错if(err.response.data.message){Vue.prototype.$message.error({type: 'error',message: err.response.data.message})// 处理401状态码: 跳转到登陆页面if(err.response.status == 401) router.push('/login')}}
)

后端解析请求头,验证token

以上实现了前端在发送http请求时,携带token(放在Authorization请求头部中),后端需要在前端通过URL请求非登陆接口时,判断用户是否登录.可以写一个登陆验证的中间件. 如果在对正常的路径进行处理之前,先通过中间件验证.

中间件的逻辑如下:

  • 首先通过req.headers.authorization获取token,若无token则设置为 ''
    • 若token存在则继续下一步,否则使用assert抛出异常
  • 然后使用jwt验证token.得到解码后的id
    • 若存在id,则证明token没有被篡改,继续下一步,否则assert抛出异常
  • 最后根据id在数据库中查询user.并将user赋给req.user.
    • 若存在req.user则跳转到下一个中间件,否则使用assert抛出异常
// sever/middleware/auth.js
module.exports = options =>{const assert = require('http-assert') // 简化代码const jwt = require('jsonwebtoken')	// 用于将token密文解析为明文const AdminUser = require('../models/AdminUser') // 获取AdminUser模型return async(req, res, next)=>{// 获取tokenconst token = String(req.headers.authorization || '').split(' ').pop()assert(token, 401, '请先登陆')	// 这里若无token: 则代表未登陆const {id} = jwt.verify(token, req.app.get('secret'))assert(id, 401, '请先登陆')  // 这里若无id: 则代表token过期或被篡改了req.user = AdminUser.findById(id)assert(req.user, 401, '请先登录') // 这里若没用找到user, 则代表给的是假idawait next()}
}

以上提供了一个中间件: 它判断token是否存在(正确),若不正确则抛出异常.若正确则继续下一个中间件.在主路由中导入.并在需要的地方添加这个中间件

// server/router/admin/index.js
module.exports = app => {const express = require('express')const authMiddleware = require('../../middleware/auth')const router = express.router({mergeParams: true})app.use('/admin/api/rest/:resource', authMiddleware(), router)
}

上面使用了assert抛出异常,因此还需要一个错误捕捉中间件,放在所有路由的最后,用于捕捉错误,它会将捕获到的异常,作为参数传递给前端.这样前端就能根据状态码做出响应的处理了

// server/router/admin/index.js
module.exports = app => {const express = require('express')const authMiddleware = require('../../middleware/auth')const router = express.router({mergeParams: true})// 放到所有路由的后面app.use(async (err, req, res, next)=>{console.log(err)res.status(err.statesCode || 500).send({ message: err.message})})
}

前端路由校验

上面已经完成了,在发送Http请求时,进行路由校验.若401则跳转到登陆页面.但是,那些不需要HTTP请求的网页还是可以不需要登陆就能直接访问.下面有必要做前端的路由校验

【具体实现如下】:

  • 将不需要登陆的就能访问的(主要是是Login组件)路由,添加一个{isPublic: true}属性
  • 然后使用全局前置守卫,在进入路由前判断localStorage中是否存在token.若存在,则进行下一步,否则跳转到登陆页面. 而登陆页面设置了公开访问属性,因此不会触发全局前置守卫
// admin/src/router/index.js
import Vue from 'vue'
import VurRouter from 'vue-router'
const routes = [{ path: '/login',name: 'login',component: Login,meta: { isPublic: true}},{path: '/',name: 'main',component: Main,children: [ ... ]}
]
const router = new VueRouter({routes      
})router.beforeEach((to, from, next) =>{if(!to.meta.isPublic && localStorage.token){return next('/login')}next()
})
export default router

在主函数中加载路由,并渲染更新

// admin/src/main.js
import Vue from 'vue'
import App from './App.vue'
import './plugins/element.js'
import router from './router'import './style.css'Vue.config.productionTip = falsenew Vue({router,render: h => h(App)
}).$mount('#app')

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/250250.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

设计模式---装饰模式

今天学习了装饰模式&#xff0c;做个笔记。。装饰模式的基础概念可以参考&#xff1a;https://blog.csdn.net/cjjky/article/details/7478788 这里&#xff0c;就举个简单例子 孙悟空有72变&#xff0c;但是它平时是猴子&#xff0c;遇到情况下&#xff0c;它可以变成蝴蝶等等 …

springMvc 注解@JsonFormat 日期格式化

1&#xff1a;一定要加入依赖,否则不生效&#xff1a; <!--日期格式化依赖--><dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-databind</artifactId><version>${jackson.version}</version>&…

Git很简单--图解攻略

Git Git 是目前世界上最先进的分布式版本控制系统&#xff08;没有之一&#xff09;作用 源代码管理为什么要进行源代码管理? 方便多人协同开发方便版本控制Git管理源代码特点 1.Git是分布式管理.服务器和客户端都有版本控制能力,都能进行代码的提交、合并、. 2.Git会在根…

css --- 使用scss生成常用的基本css样式

"工具样式"的概念和 SASS(SCSS) 在webpack中使用sass 安装sass和sass-loader $ npm i sass sass-loader由于使用了脚手架,安装完毕后重启前端即可 样式重置 其实就是样式的初始化 // reset* {box-sizing: border-box; // 以边框为准. css3盒模型outline: none;…

vc/vs开发的应用程序添加dump崩溃日志转

原贴地址&#xff1a;https://blog.csdn.net/wangkui1331/article/details/78029940 vc/vs开发的应用程序出现崩溃的时候&#xff0c;由于没有任何记录&#xff0c;导致开发人员很难追踪&#xff0c;但是添加dump文件后&#xff0c;就可以免除这些烦恼 1.添加方法 &#xff08;…

51 nod 1127最短的包含字符串(尺取法)

1127 最短的包含字符串 收藏关注给出一个字符串&#xff0c;求该字符串的一个子串S&#xff0c;S包含A-Z中的全部字母&#xff0c;并且S是所有符合条件的子串中最短的&#xff0c;输出S的长度。如果给出的字符串中并不包括A-Z中的全部字母&#xff0c;则输出No Solution。Input…

Java --- 基础学习Ⅰ

第一章 开发前言 位、字节 位(bit): 一个数字0或一个数字1,代表一位 字节(Byte): 每逢8位是一个字节,这时数据存储的最小单位 1 Byte 8 bit 1 KB 1024 Byte 1 MB 1024 KB 1 GB 1024 MB 1 TB 1024 GB 1 PB 1024 TB MS-DOS(Microsoft Disk Operating System) 第二章 Ja…

JSON 数据重复 出现$ref

JSONArray 类型 如果我们往里面add数据的时候 如果数据相同&#xff0c;那么就会被替换成 $ref: 也就是被简化了 因为数据一样所直接 指向上一条数据 循环引用&#xff1a;当一个对象包含另一个对象时&#xff0c;fastjson就会把该对象解析成引用。引用是通过$ref标示的&am…

Java --- 基础学习Ⅱ

继承 继承概述 下面有一个学生类 public class Student{private String name;private int age;public void study(){System.out.println("努力学习了");}public String getName() {return name;}public void setName(String name) {this.name name;}public int g…

urllib库

python内置的最基本的HTTP请求库&#xff0c;有以下四个模块&#xff1a; urllib.request  请求模块 urllib.error    异常处理模块 urllib.parse   url解析模块 urllib.robotparser robots.txt解析模块 urllib.request请求模块&#xff1a; urllib.request.urlopen(u…

layer的删除询问框的使用

删除是个很需要谨慎的操作 我们需要进行确认 对了删除一般使用ajax操作 因为如果同url请求 处理 再返回 会有空白页 1.js自带的样式 <button type"button" data-toggle"tooltip" title"删除" class"btn btn-danger pull-right btn-xs&qu…

文献笔记(八)

一、基本信息 标题&#xff1a;MySQL数据库在自动测试系统中的应用 时间&#xff1a;2017 出版源&#xff1a;宁夏职业技术学院 领域分类&#xff1a;无线互联科技 二、研究背景 问题定义&#xff1a;文章介绍了MySQL数据库的特点&#xff0c;结合自动测试系统运行中的实际&…

Java --- 常用API

常用API 方法重载: 方法名相同,方法接收的参数不同 static: 修饰的类,可以直接使用类名进行调用 方法名说明public static abs(int a)返回参数的绝对值public static double ceil(double a)返回大于或等于public static double floor(double a)返回小于或等于参数的最大doubl…

9. 弹出键盘挡住input

1.) react 中 <input className"inp3" placeholder"密码" type"password" onChange{this.changepassword.bind(this)} onFocus{this.FocusFN.bind(this)} value{this.state.paswword}/> FocusFN(){ setTimeout(()>{ let pannel docume…

Linux初学时的一些常用命令(4)

1. 磁盘 查看当前磁盘使用情况 df -h查看某个文件大小 du -sh 文件名 如果不输入文件名&#xff0c;默认是当前目录的所有文件之和&#xff0c;即当前目录大小 2. 系统内存 free参数详解&#xff1a;https://blog.csdn.net/loongshawn/article/details/51758116 3. CPU CPU 使用…

小程序 --- 项目小练手Ⅰ

1. 接口文档 2. 帮助文档 小程序开发文档 mdn 阿里巴巴字体 iconfont 3. 项目搭建 3.1 新建小程序项目 填入自己的appid: wxdbf2b5e8c2f521a3 3.2 文件结构 一级目录 目录名作用styles存放公共样式components存放组件lib存放第三方库utils自己的帮助库request自己的接口…

vue aixos请求json

this.axios.get(/static/json/jinxiangZhe.json).then(res>{console.log(res);}).catch( error > {console.log(error,error)}) 转载于:https://www.cnblogs.com/SunShineM/p/9087734.html

小程序 --- Tab组件的封装

1. Tabs组件的封装 1.1 组件的引入 使用自定义的组件很简单,只需在使用组件的页面的配置文件.json文件中配置. // pages/goods_list/index.json {"usingComponents":{"Tabs": "../../components/Tabs/Tabs"} }然后再.wxml文件中使用即可 <…

爬虫之拉勾网职位获取

重点在于演示urllib.request.Request()请求中各项参数的 书写格式 譬如&#xff1a; url data headers...Demo演示&#xff08;POST请求&#xff09;:import urllib.requestimport urllib.parseimport json, jsonpath, csvurl "https://www.lagou.com/jobs/positionAjax.…

小程序 --- 点击放大功能、获取位置信息、文字样式省略、页面跳转(navigateTo)

1. 点击放大功能的实现 需求: 点击轮播图中的图片会实现放大预览的功能。首先有轮播图的样式如下 <!-- pages/goods_detail/index.wxml --> <!-- 轮播图 --> <view class"detail_swiper"><swiperautoplaycircularindicator-dots><swip…