Node.js服务端07-登录鉴权

243 阅读3分钟

cookie&session

使用cookie配合session来进行登录的鉴权

1、安装express-session

var session = require('express-session')

2、注册session中间件(由于是需要全局鉴权,中间件一定是应用级别的,需要设置在路由之前否则没有意义)

//注册session中间件
app.use(session({
  name: 'Gfred_backend',
  secret: 'gfr666555',
  cookie: {
    maxAge:1000*60*60,
    secure:false
  },
  resave:true, //重新设置session后,会重新设置过期时间
  saveUninitialized:true,
  store: MongoStore.create({
    //创建一个新的数据库存储session
    mongoUrl: 'mongodb://localhost:27017/node_session',
    ttl: 1000 * 60 * 10
  })
}))

3、使用中间件,进行具体的拦截

//设置中间件,session过期校验
app.use((req, res, next)=> {
  //排除login相关的路由和接口,否则会重定向过多
  if (req.url.includes('login')) {
    //重新设置session
    req.session.sessionDate = Date()
    next()
    return
  }
  if (req.session.user) {
    next()
  } else {
    //是接口,返回错误码
    
    //不是接口,重定向
    req.url.includes('api')?
    res.status(401).json({ok:0})
    :res.redirect('/login')
  }
})

4、在Controller中设置session

login: async(req, res, next) => {
    const {username, password} = req.body
    //等待服务执行完毕
    const data = await UserService.login(username, password)
    
    if (data.length === 0) {
      res.send({
        ok:0
      })
    } else {
      //设置session
      req.session.user = data[0] //设置session对象
      //默认存在内存中
      res.send({
        ok:1,
        userinfo:data
      })
    }
  },

JWT(JSON Web Token)

(Header + 信息 )加密(加密SHA 265 +秘钥(防止反推))

加密后生成签名,Header + 信息 + 签名 = token

用签名匹配 token 核对信息,如下图所示

实操

1、安装jsonwebtoken

2、封装JWT工具


const jwt = require('jsonwebtoken')
const secret = 'gfred666-anydata'

const JWT = {
  generate(secretValue, expires) {
    //加密数据,秘钥,过期时间
    return jwt.sign(secretValue, secret, {
      expiresIn: expires
    })
  },
  verify(token) {
    try {
      return jwt.verify(token, secret)
    } catch (error) {
      return false
    }
  }
}

module.exports = JWT

后端部分,中间件

判断是否有token

刷新操作重新生成token

发送给前端,过期发送错误401

//设置中间件,token
app.use((req, res, next)=> {
  //排除login相关的路由和接口,否则会重定向过多
  if (req.url.includes('login')) {
    
    next()
    return
  }
  //判断是否有token
  const token = req.headers['authorzation']?.split(' ')[1]
  if (token) {
    console.log(token);
    const payload = JWT.verify(token)
    if (payload) {
      //重新计算token过期时间
      const newToken = JWT.generate({
        _id:payload._id,
        username:payload.username
      }, '1h')
      //发给前端新的token
      res.header('Authorzation',newToken)
      next()
    } else {
      res.status(401).send({errInfo:'token过期'})
    }
  } else {
    next()
  }
})

后端首次调用登录接口,生成token发送给前端

login: async(req, res, next) => {
    const {username, password} = req.body
    //等待服务执行完毕
    const data = await UserService.login(username, password)
    
    if (data.length === 0) {
      res.send({
        ok:0
      })
    } else {
      //设置token
      const token = JWT.generate({
        _id:data[0]._id,
        username:data[0].username
      }, '1h')
      //token返回在header中
      res.header('Authorzation',token)
      
      res.send({
        ok:1,
        userinfo:data
      })
    }
  },

前端部分

登录响应后,响应拦截器保存token

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <script src="https://cdn.jsdelivr.net/npm/axios@1.1.2/dist/axios.min.js"></script>
  <script>
    //设置拦截器
    // Add a request interceptor
    axios.interceptors.request.use(function (config) {
      // Do something before request is sent
      console.log('请求发出前,执行的方法')
      return config;
    }, function (error) {
      // Do something with request error
      return Promise.reject(error);
    });

    // Add a response interceptor
    axios.interceptors.response.use(function (response) {
      
      const {authorzation} = response.headers
      console.log(authorzation)
      authorzation && localStorage.setItem('token', authorzation)
      // Any status code that lie within the range of 2xx cause this function to trigger
      // Do something with response data
      return response;
    }, function (error) {
      // Any status codes that falls outside the range of 2xx cause this function to trigger
      // Do something with response error
      return Promise.reject(error);
    });
  </script>
</head>

<body>
  <h1>登录页面</h1>
  <div>
    <div>用户名:<input type="text" id="username"></div>
    <div>密码:<input type="password" id="password"></div>
    <div><button id="login">登录</button></div>
  </div>

  <script>

    var username = document.querySelector('#username')
    var password = document.querySelector('#password')

    var login = document.querySelector('#login')

    login.onclick = () => {
      console.log(username.value, password.value)

      axios.post('/api/login', {
        username: username.value,
        password: password.value,

      }).then(res => {
        if (res.data.ok === 1) {
          console.log(res.data)
          location.href = '/'
        } else {
          alert('用户名密码不匹配')
        }

      })
    }
  </script>
</body>

</html>

前端index部分,请求拦截器在每次请求,需要添加token,响应拦截器保存token,如果响应获取到错误则转回login页面

<!DOCTYPE html>
<html>
  <head>
    <title><%= title %></title>
    <link rel='stylesheet' href='/stylesheets/style.css' />
    <script src="https://cdn.jsdelivr.net/npm/axios@1.1.2/dist/axios.min.js"></script>
  <script>
    //设置拦截器
    // Add a request interceptor
    axios.interceptors.request.use(function (config) {
      // Do something before request is sent
      const token = localStorage.getItem('token')
      config.headers.Authorzation = `Bearer ${token}`
      return config;
    }, function (error) {
      // Do something with request error
      return Promise.reject(error);
    });

    // Add a response interceptor
    axios.interceptors.response.use(function (response) {
      
      const {authorzation} = response.headers
      console.log(authorzation)
      authorzation && localStorage.setItem('token', authorzation)
      // Any status code that lie within the range of 2xx cause this function to trigger
      // Do something with response data
      return response;
    }, function (error) {
      if (error.response.status === 401) {
        location.href = '/login'
      }
      // Any status codes that falls outside the range of 2xx cause this function to trigger
      // Do something with response error
      return Promise.reject(error);
    });
  </script>
  </head>
  <body>
    <h1><%= title %></h1>
    <p>Welcome to <%= title %></p>
    <h1>
      <button id="exit">退出登录</button>
    </h1>
    <div>
      <div>用户名:<input type="text" id="username"></div>
      <div>密码:&nbsp;&nbsp;<input type="password" id="password"></div>
      <div>年龄:&nbsp;&nbsp;<input type="number" id="age"></div>
      <div><button id="register">注册</button></div>
    </div>
    <div>
      <button id="update">更新</button>
      <button id="delete">删除</button>
    </div>

    <hr>
    <script>
      var register = document.querySelector('#register')
      var update = document.querySelector('#update')
      var deleteAct = document.querySelector('#delete')
      var exit = document.querySelector('#exit')

      var username = document.querySelector('#username')
      var password = document.querySelector('#password')
      var age = document.querySelector('#age')

      register.onclick = () => {
        console.log(username.value, password.value, age.value)
        axios.post('/api/user', {
          username:username.value,
          password:password.value,
          age:age.value
        }).then(res => {
          console.log(res.data)
        })
        
      }

      update.onclick = () => {
        console.log(username.value, password.value, age.value)
        

        axios.put('/api/user/64c1d7852d94d7678e8c2d14', {
          username:'gfr',
          password:'aga',
            age:33
        }).then(res => {
          console.log(res.data)
        })
      }

      deleteAct.onclick = () => {
        
        axios.delete('/api/user/64c1d7852d94d7678e8c2d14', {
        }).then(res => {
          console.log(res.data)
        })
      }

      
      axios.get('/api/user?page=1&limit=2').then(res => {
        let resd = res.data
        console.log(resd)
      })

      exit.onclick = () => {
        fetch('/api/logout').then(res => res.json())
        .then(res => {
          if (res.ok === 1) {
            location.href = '/login'
          }
          
        })
      }
    </script>
  </body>
</html>

文件上传

1、安装multer

2、引入并设置上传路径

//multer上传文件使用
const multer = require('multer')
const upload = multer({dest:'public/uploads/'})

3、设置中间件

//upload.single('avator')中间件接收文件
router.post('/user', upload.single('avator'), UserContrller.addUser)

4、前端form的设置文件类型enctype="multipart/form-data"

<form action="/api/user" method="post" enctype="multipart/form-data">
    <div>
      用户名:<input type="text" name="username">
    </div>
    <div>
      密码:<input type="password" name="password">
    </div>
    <div>
      年龄:<input type="number" name="age">
    </div>
    <div>
      头像:<input type="file" name="avator">
    </div>
    <div>
      <input type="submit" value="添加用户">
    </div>
  </form>

(对应模型也需要注意修改)

addUser:(username, password, age, avator) => {
    //插入数据库
    // 1.创建一个模型(user),一一对应数据库的集合(users)
    return UserModel.create({
      username, password, age, avator
    }).then(data => {
      console.log(data);
    })
  },