全栈开发学习(Node+Vue+Mongodb)(七)——登录页面

1 新建管理员账户模块

  • 新建管理员模型,需要提前在server中安装模块bcryptjs: npm i bcryptjs,用于密码散列
const schema=new mongoose.Schema({
    //字段以及字段类型
    username:{type:String},    
    password:{
        type:String,
        select:false,//密码默认查不出来
        set(val){
            return require('bcryptjs').hashSync(val,12)  //设置密码散列
        }
    },    
})
  • 后台管理页面添加新增/编辑管理员、管理员列表页面,这里不再赘述

2 登录页面搭建

  • 新建login路由,和Main平级
 { path: '/login', name: 'login', component: Login, meta: { isPublic: true } }
  • 新建Login组件,前端页面搭建这里不详细说明,说一下执行前端请求的login()函数
async login(){
     const res=await this.$http.post('login',this.model);
    //将后台返回的token写入浏览器的存储,下次打开浏览器仍然存在;用sessionStorage下次打开就不存在
     localStorage.token=res.data.token  
     //清空浏览器保存的token 用localStorage.clear
     this.$router.push('/')
     this.$message({
       type:'success',
       message:'登录成功'
     })
    }

3 全局捕获错误

我们登陆页面的前端发送的请求都先经过我们的http拦截器,拦截器的写法参考axios官方文档。

  • http-request拦截器

    http.interceptors.request.use(function (config) {
        // Do something before request is sent
        if (localStorage.token) {
            //请求头,格式为:Bearer+空格+token,服务端会提取这个请求的token
            config.headers.Authorization = 'Bearer ' + localStorage.token  
          }
        return config;
      }, function (error) {
        // Do something with request error
        return Promise.reject(error);
      });
  • http-response加上拦截器

    http.interceptors.response.use(res=>{
        return res
    },err => {
        if(err.response.data.message){
            Vue.prototype.$message({
                type:'error',
                message:err.response.data.message
            })
            if(err.response.status === 401){
                router.push('/login')  //有任何错误,服务端会限制它跳回登录页
            }
        }
        return Promise.reject(err)
    })

4 登录接口

  • 新建接口,并且将前端传来的用户名&密码解构赋值

    app.post('/admin/api/login',async(req,res)=>{
        const {username,password}=req.body  //解构赋值
    }
  • 根据用户名找用户

     const user=await AdminUser.findOne({username}).select('+password')
     assert(user,422,'用户不存在') //确保用户存在
  • 校验密码

    const isValid=require('bcryptjs').compareSync(password,user.password)
    assert(isValid,422,'密码错误')//确保密码正确
  • 返回token 安装npm i jsonwebtoken 前端登录页面获取后台返回的token

    //生成token 给密钥secret防止客户端篡改信息
    const token=jwt.sign({id:user._id},app.get('secret'))   
    res.send({token})  //发送token给客户端

5 服务端登录校验

  • 针对登录校验过程中可能需要频繁判断是否存在这样一个数据的问题,我们引入http-assert包:

    npm install http-assert

    用法:

    assert(xxx,statusCode,message)

    参数解释:确保xxx存在;如果不存在,抛出状态码statusCode,返回错误信息message。之后我们想要使用assert替换里面所有需要判断状态码的地方

  • 添加错误处理函数,用于接收抛出的异常,并且返回提示信息

    app.use(async(err,req,res,next)=>{
            res.status(err.statusCode || 500).send({
                message:err.message
            })
        })
  • 修改资源的相关路由,使得未登录的管理员不可以获取到后台的资源。

    具体实现方法:在资源路由处理函数之前添加中间件authMiddleware(),在该中间件中获取请求头中的token,并解析查询得到用户名对象,由此可以判别是否可以将资源列表的数据展示给该用户查看

    • 添加中间件到资源路由

      app.use('/admin/api/rest/:resource',authMiddleware(),router)  
    • 中间件写法

      module.exports=options=>{
          const jwt=require('jsonwebtoken')
          const assert=require('http-assert')
          const AdminUser=require('../models/AdminUser')
          return async (req, res, next) => {
              //获取请求头
              const token = String(req.headers.authorization || '').split(' ').pop() 
              assert(token, 401, '请先登录') 
              //通过token解析得到用户名的id
              const { id } = jwt.verify(token, req.app.get('secret')) 
              assert(id, 401, '请先登录')   
              //通过id查找用户对象
              req.user = await AdminUser.findById(id) 
              assert(req.user, 401, '请先登录')
              await next()
            }
      }

6 客户端路由限制

完成了上述的步骤后我们发现,若管理员未登录,我们在访问资源列表时才会发起请求,返回401强制跳转到登录页面提醒用户登录;但是当我们点击编辑页面时将不会发起请求,也就无法强制跳转到登录页面,说明编辑页面即使没有管理员登录我们也可以访问。为了解决这个问题,我们需要对前端进行路由限制。

  • Login路由,给登录页面公开授权: isPublic: true

    { path: '/login', name: 'login', component: Login, meta: { isPublic: true } }
  • 导航守卫

    /*每次切换路由时的处理办法*/
    router.beforeEach((to, from ,next) => {
      //如果不是公开访问的页面并且不存在用户token时跳转到登录页
      if (!to.meta.isPublic && !localStorage.token) {  
        return next('/login')
      }
      next()
    })

7 上传文件的登录校验

  • 在图片上传的组件中添加:headers

    <el-upload
             :headers="getAuthHeaders()">
    el-upload>
  • 给全局定义方法 getAuthHeaders() ,获取token

    getAuthHeaders(){
          return {
            Authorization:`Bearer ${localStorage.token || ''}`
          }
        }
  • 在上传路由中添加中间件

    app.post('/admin/api/upload',authMiddleware(),upload.single('file'), async(req,res)=>{...})

你可能感兴趣的:(全栈学习,vue,nodejs,jwt,web,javascript)