node+mysql+vue-element-admin,前后端联动,md5加密,jwt验证,登录登出

后端登录

搭建http服务

搭建https服务的话:搭建https服务

http服务我们前面其实已经搭好了

现在把端口换一下 18082

在这里插入图片描述

因为访问47.103.29.206:18082的话会报错,因为已经注册过了?? 所以我后面加了个a

const express=require('express')
const router=require('./router')
const app=express()
app.use('/',router)

app.listen(18082,()=>{
  console.log("http://localhost:18082");
})

登录api开发

在user里创建一个api

随便写点测试一下

router.post('/login',(req,res)=>{
  console.log(req.body);
  res.json({
    code:0,
    msg:'登录成功'
  })
})

因为浏览器不好做post请求测试

这里可以用postman

也可以下载一个curl在dos窗口

或者git bash里面输入也可以

curl http://47.103.29.206a:18082/user/login -X POST

输出req.body为undifined 说明并没有传入参数

可以通过-d来指定body里的参数

curl http://47.103.29.206a:18082/user/login -X POST -d 'username=admin&password=1234'

但是body里面的参数仍然没有被解析 需要用到body-parser中间件来解决这个问题

安装body-parser cnpm i -S body-parser

关于body-parser的用法 :https://juejin.cn/post/6844903478830055431

不用下这个包了 已经被弃用了

在这里插入图片描述

直接用express调用bodyParser的方法就可以了

发的请求可以简化一下 ,加上-d以后他默认是post

curl http://47.103.29.206a:18082/user/login -d 'username=admin&password=1234'

记得把前端的baseAPI也改了

在这里插入图片描述

然后运行一下前端项目

到时候上线了就把a给去掉

在这里插入图片描述

跨域问题

所以在服务端写个解决跨域的方案

在这里插入图片描述

1111

可以看到参数已经传进来了

同时还收到了返回值

在这里插入图片描述

这时候前端后端已经发生了联动了 可以互通了

这里我们在 Network 中会发现发起了两次 https 请求,这是因为由于触发跨域,所以会首先进行 OPTIONS 请求,判断服务端是否允许跨域请求,如果允许才能实际进行请求

也就是说跨域请求的访问是由服务端控制的,服务端下发了这样的response header之后,

在这里插入图片描述

浏览器就知道是可以发送请求的,如果跨域条件不满足的时候,浏览器就直接抛出异常

响应结果封装

const {
  CODE_ERROR,
  CODE_SUCCESS
} = require('../utils/constant')

// new Result  
class Result {
  // data:向前端返回的数据 msg:向前端返回的信息
  constructor(data, msg = '操作成功', options) {
    this.data = null
    // 一个都没传
    if (arguments.length === 0) {
      this.msg = '操作成功'
      // 传入一个参数 认为他是个msg
    } else if (arguments.length === 1) {
      this.msg = data
    } else {
      // 传入2/3个参数
      this.data = data
      this.msg = msg
      if (options) {
        this.options = options
      }
    }
  }

  createResult() {
    //没有code默认code为success 
    if (!this.code) {
      this.code = CODE_SUCCESS
    }
    // 基础的结构是一个code 一个msg
    let base = {
      code: this.code,
      msg: this.msg
    }
    // data存在就增加一个data
    if (this.data) {
      base.data = this.data
    }
    // 如果还有额外的options 就加到base里
    if (this.options) {
      base = { ...base, ...this.options }
    }
    console.log(base)
    return base
  }

  json(res) {
    //通过res.json返回给前端
    res.json(this.createResult())
  }

  success(res) {
    this.code = CODE_SUCCESS
    this.json(res)
  }

  fail(res) {
    this.code = CODE_ERROR
    this.json(res)
  }
}

module.exports = Result

还要来改造一下user.js

router.post('/login',(req,res)=>{
  console.log(req.body);
  new Result('登录成功').success(res)
})

做失效功能的时候只需要增加一个方法就行 扩展能力很好

如果success里面出现了异常,可以在index.js的异常处理语句中被处理

mysql

文档

在这里插入图片描述

密码是用的md5加盐

我们需要在node中安装mysql数据库

建一个db文件夹 专门存储db的相关操作

里面包含两个文件 config.js index.js

config.js:

module.exports={
  host:'localhost',
  user:'root',
  password:'111111',
  database:'book'
}

index.js

const mysql = require('mysql')
const config = require('./config')
function connect() {
  return mysql.createConnection({
    ...config
  })
}
function querySql(sql) {
  const conn = connect()
  return new Promise((resolve, reject) => {
    try {
      conn.query(sql, (err, results) => {
        if (err) {
          reject(err)
        }
        resolve(results)
      })
    }
    catch (e) {
      reject(e)
    }
    finally {
      conn.end()
    }
  })

}
module.exports=querySql

user.js引入

const querySql=require('../db/index')
 querySql('select * from admin_user').then((res)=>{
    console.log(res);
  }).catch((err)=>{
    console.log(err);
  })

有个技巧,我们可以在关键的地方打印一些参数, 在constant里面写个debug为true

然后在需要输出的页面里引入debug

debug&&console.log(xxxx)

在上线的时候我们就可以把debug改成false 就不会再打印了

在这里插入图片描述

这是在db/index.js里面写的, 就不用再业务逻辑里面打印了

这个配置文件可以从代码中抽离出来,变成一个单独的配置文件,这样就可以在不启动代码的情况下实现debug的切换

有了这个查询语句 ,就可以对用户查询进一步封装

我们是直接将查询语句写在user.js里了

更好的做法是再建一个service层 把业务逻辑全放到service层

创建service 里面包含了个user.js

创建一个function 叫login

const querySql=require('../db/index')
function login(username,password){
  return querySql(`select * from admin_user where username='${username}' and password='${password}'`)
}
module.exports=login

在router/user.js

 const { username, password } = req.body
  login(username, password).then((user) => {
    if (!user || user.length == 0) {
      new Result('登录失败').fail(res)
    } else {
      new Result('登录成功').success(res)
    }
  })

在这里插入图片描述

这时,即使我们输入正确的用户名密码也会出现无法登陆的情况

这是因为密码采用了MD5+SALT加密,所以需要对密码进行对等加密。

去constant文件

加上这句话

PWD_SALT:'admin_imooc_node'

这个就相当于一个秘钥,通过秘钥和密码混合之后生成新的密码

不知道秘钥的话外界要破解这个密码是很难破解的

接着对密码进行MD5加密

cnpm i -S crypto

新建文件utils/index.js 并写入

const crypto=require('crypto')
function md5(s){
  return crypto.createHash('md5').update(String(s)).digest('hex')
}
module.exports=md5

接着要对password进行改造

password=md5(` p a s s w o r d {password} password{PWD_SALT}`)

express-validator

https://express-validator.github.io/docs/validation-chain-api.html

https://blog.csdn.net/cuk0051/article/details/108343329

用处:可以简化对post请求的校验

可以帮助我们快速的验证表单当中的参数

安装依赖

使用它的body方法,在post请求当中,需要在第二个参数当中传入一个数组,通过数组来进行校验,

如何接受到参数呢

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

这里的err有个isEmpty方法,这个方法可以去判断数组是否为空

如果isEmpty为false表示出现了验证错误,我们就可以从errors里面拿到msg然后返回给前端抛出异常

然后通过第三个参数next来继续传递,传递给下一个中间件来执行

这里的[ ]=err.errors代表取出第一个元素

在这里插入图片描述

去前端测试一下

在这里插入图片描述

注意next 传递给下一个中间件,下一个中间件是

在这里插入图片描述

express-validator 使用技巧:

  • router.post 方法中使用 body 方法判断参数类型,并指定出错时的提示信息
  • 使用 const err = validationResult(req) 获取错误信息,err.errors 是一个数组,包含所有错误信息,如果 err.errors 为空则表示校验成功,没有参数错误
  • 如果发现错误我们可以使用 next(boom.badRequest(msg)) 抛出异常,交给我们自定义的异常处理方法进行处理

jwt

https://www.youbaobao.xyz/admin-docs/guide/extra/jwt.html

https://www.ruanyifeng.com/blog/2018/07/json_web_token-tutorial.html

https://github.com/dwyl/learn-json-web-tokens/blob/master/README.md

基本概念

Token 的用途主要有三点:

  • 拦截无效请求,降低服务器处理压力;
  • 实现第三方 API 授权,无需每次都输入用户名密码鉴权;
  • 身份校验,防止 CSRF 攻击

可以在https://jwt.io/ 调试jwt字符串

有三段,第一段是加密的算法和token的类型,第二段是具体数据,第三段是签名部分,使用加密算法

私钥是存在服务端的,别人不知道私钥的时候是无法破解里面的信息的,最后解开的时候是要用到私钥的。

生成jwt token

npm i -S jsonwebtoken

需要一个私钥和一个过期时间,过期时间不宜过短,也不宜过长,课程里设置为 1 小时,实际业务中可根据场景来判断,通常建议不超过 24 小时,保密性要求高的业务可以设置为 1-2 小时:

const jwt = require('jsonwebtoken')
const { PRIVATE_KEY, JWT_EXPIRED } = require('../utils/constant')
login(username, password).then(user => {
    if (!user || user.length === 0) {
      new Result('登录失败').fail(res)
    } else {
      const token = jwt.sign(
        { username },   //传入的数据
        PRIVATE_KEY,    //秘钥
        { expiresIn: JWT_EXPIRED } //过期时间
      )
      new Result({ token }, '登录成功').success(res)
    }
})

前端代码改造

utils/require.js

对响应拦截器做修改

在这里插入图片描述

这里的message也改成msg return Promise.reject(new Error(res.msg || 'Error'))

然后是views/login/index.vue

找到具体做登录动作的方法handleLogin

他会进入到user/login这个action

看一下这里的data 后面serToken让token保存起来了

在这里插入图片描述

token是保存起来了,但是运行起来报错,这都是因为/info接口还没有开发

来分析一下

在这里插入图片描述

进入到permission.js 全局守卫

因为去的是dashboard所以走else

之后进入到user/getInfo

在这里插入图片描述

又调用了一个getInfo方法

在这里插入图片描述

请求了后端的/user/info接口

在这里插入图片描述

我们还没有开发,所以后面的路都走不通了。 我们看看他的/info的框架是怎么实现的

在这里插入图片描述

他是直接带上token参数,我们后面要改成request里面包含token信息,拿到token信息,服务端就可以解析出用户信息,因为token信息当中有个username,我们把username解出来之后根据username查到用户信息,再返回给前端,整个实现流程是这样的状态,所以我们下面要在服务端添加一个jwt的认证,我们刚刚是生成了token,现在要来验证一下token是否在有效期范围内。

jwt认证

https://www.cnblogs.com/zkqiang/p/11810203.html

安装:npm i -S express-jwt

他的主要功能是检查所有的路由,判断当前时间是否在过期时间内,当路由中包含了没有过期的token,就可以判定为通过,

新建一个中间件router/jwt.js

这个中间件的主要用途就是做验证

这个地方卡了我好久 不是user/login而是/user/login

const expressJwt = require('express-jwt');
const { PRIVATE_KEY } = require('../utils/constant');

const jwtAuth = expressJwt({
  secret: PRIVATE_KEY,
  credentialsRequired: true // 设置为false就不进行校验了,游客也可以访问
}).unless({
  path: [ 
    '/',
    '/user/login'   
  ], // 设置 jwt 认证白名单
});

module.exports = jwtAuth;

在index.js中加入这个中间件

const jwtAuth = require('./jwt')

// 注册路由
const router = express.Router()

// 对所有路由进行 jwt 认证
router.use(jwtAuth)

之后去浏览器看看

有个报错 algorithms should be set

解决:https://iseeu.blog.csdn.net/article/details/108641110

algorithms:[‘HS256’]

在这里插入图片描述

抛出500错误,说明拦截已经生效了 ,因为在header里面他没有找到token,所以报错了

现在去服务端做一些处理 现在发现错误以后返回的都是-1,但是我们希望token的错误会有另外的值比如-2

在constant.js 增加:CODE_TOKEN_EXPIRED:-2

当出现token错误的时候会打印出这个

在这里插入图片描述

在这里插入图片描述

老师的是这样的,他是用name标识token错误的,我这儿没有,就用code标识吧

在这里插入图片描述

在这里插入图片描述

还有,我们以前封装了Result,可以改造一下,通过Result快速生成错误信息

在这里插入图片描述

在Result里新建一个方法

在这里插入图片描述

点击登录

在这里插入图片描述

在这里插入图片描述

对前端代码再进行改造

request-相应拦截器-error

这里有个技巧 如何看到error的详细信息

在这里插入图片描述

在这里插入图片描述

这样就能弹出自定义的msg

在这里插入图片描述

服务端else这里的代码可以用Result改造一下

在这里插入图片描述

在这里插入图片描述

目前,会出现token验证的错误,为了解决这个错误,我们需要将header里增加一个authorization(jwt规定的)

去request的请求拦截器:

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

这里的user/info其实不需要带参数

在这里插入图片描述

在这里插入图片描述

开发user/info接口

在服务端部分,在数据库index.js里增加一个queryOne的方法

function queryOne(sql){
  return new Promise((resolve,reject)=>{
    querySql(sql).then((res)=>{
      if(res&&res.length>0){
        resolve(res[0])
      }else{
        resolve(null)
      }
    }).catch(err=>{
      reject(err)
    })
  })
}

service/user.js(把login.js重命名了 变成user.js):

function findUser(username){
  return queryOne(`select * from admin_user where username=${username}`)
}

router/user.js

router.get('/info', (req, res, next) => {
  // res.send('user/info')
  findUser('admin').then((user) => {
    console.log(user);  //不需要处理异常,因为默认情况会抛给自定义异常处理
    if (user) {
      new Result(user, '用户信息查询成功').success(res)
    } else {
      new Result('用户信息查询失败').fail(res)
    }
  })
})

在这里插入图片描述

因为不想让他出现password 所以查询语句要修改一下

function findUser(username){
  return queryOne(`select id,username,nickname,role,avatar from admin_user where username='${username}'`)
}

在这里插入图片描述

因为前端获取的是roles不是role

所以在查询成功前面需要加上user.roles=[user.role]

router.get('/info', (req, res, next) => {
  // res.send('user/info')
  findUser('admin').then((user) => {
    console.log(user);  //不需要处理异常,因为默认情况会抛给自定义异常处理
    if (user) {
      user.roles=[user.role]
      new Result(user, '用户信息查询成功').success(res)
    } else {
      new Result('用户信息查询失败').fail(res)
    }
  })

})

这样就可以进去了

继续对user/info接口进行改造 username不应该写死

username需要从token里拿,这就用到了对jwt进行解析

所以,我们要在header中拿到jwt并且对他进行解析

前端仅在 Http Header 中传入了 Token,如果通过 Token 获取 username 呢?这里就需要通过对 JWT Token 进行解析了,

需要用到jsonwebtoken里的verify方法

需要从http header里拿到authorization

/utils/index.js 中添加 decode 方法:

function decode(req){
  const token=req.get('authorization')
  return token
}
function decode(req){
  let token=req.get('authorization')
  if(token.indexOf('Bearer')===0){
    token=token.replace('Bearer ','')
  }
  return jwt.verify(token,PRIVATE_KEY)
}

在这里插入图片描述

router.get('/info', (req, res, next) => {
  const decoded=decode(req)
  console.log({decoded});
  // res.send('user/info')
  if(decoded&&decoded.username){
    findUser(decoded.username).then((user) => {
      console.log(user);  //不需要处理异常,因为默认情况会抛给自定义异常处理
      if (user) {
        user.roles=[user.role]
        new Result(user, '用户信息查询成功').success(res)
      } else {
        new Result('用户信息查询失败').fail(res)
      }
    })
  }else{
    new Result('用户信息查询失败').fail(res)
  }
})

登出

登出的时候显示接口不存在,其实登出的时候是不需要调用任何接口的

只需要将token清空,然后重定向一下

如何解决

在这里插入图片描述

用try-catch改造 这里不走他的接口了

  logout({ commit, state, dispatch }) {
    return new Promise((resolve, reject) => {
      try {
        commit('SET_TOKEN', '')
        commit('SET_ROLES', [])
        removeToken()
        resetRouter()
        // reset visited views and cached views
        // to fixed https://github.com/PanJiaChen/vue-element-admin/issues/2485
        dispatch('tagsView/delAllViews', null, { root: true })
        resolve()
      } catch (error) {
        reject(error)
      }
    })
  },

查询一下,发现是在NavBar里面被用到了

在这里插入图片描述

logout执行完以后他会跳到login页面

回顾

主要开发了两个api一个是登录api ,一个是获取用户信息api

前端请求api到服务端以后,服务端会检查jwt的白名单,在白名单的话直接调用controller,如果不在,进行jwt token的认证,是通过 jwt库 express-jwt来进行实现的,如果验证失败将返回401错误,验证成功将调用controller

在登录的时候会判断body参数,通过express-validator来进行判断,如果验证失败会调用boom.badRequest返回验证失败

之后会调用mysql数据库,写了两个方法,一个是login服务,一个是findUser服务,他们都是查询admin_User这张表去判定用户是否存在,如果是登录场景的话,会通过jwt生成一个token,将token返回给前端用户

  • 1
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值