登录鉴权——node(express框架)

问题引入

当用户想要访问主界面是,必须要判断用户是否登录,如果用户已经登陆就返回主界面,如果用户未登录就返回登录界面。不能说无论用户是否登录都可以通过修改请求路径直接跳转到主页面,这显然是错误的。解决该问题就是需要登录鉴权。

验证用户的身份我们一般有两种方式,一种是采用cookie和Session ,另一种是采用JWT(token)方法。

案例准备

登陆页面,主页面,用户登陆后可以跳转到主页面,现在还没有进行登录鉴权,所以用户直接访问请求地址也可以跳转到主页面。

项目是通过express生成的框架.
项目目录:
在这里插入图片描述

  • config/db.config.js
// 连接数据库
const mongoose = require("mongoose")

mongoose.connect("mongodb://127.0.0.1:27017/yang_project")
// 插入集合和数据,yang_project会自动创建

  • controller/UserController.js
const UserService = require('../services/UserService');
const UserController = {
  addUser: async (req, res) => {
  console.log(req.body)
  // 插入数据库
  const { username, password, age } = req.body
    
  await UserService.addUser( username, password, age)
  res.send({ok:1})
  },
  updateUser:async (req, res) => {
        const {username,password,age} = req.body
        await UserService.updateUser(req.query.id,username,password,age)
        res.send({
            ok:1
            })
  },
  deleteUser: async (req, res) => {
    console.log(req.query.id)
     await UserService.deleteUser(req.query.id)
     res.send({
      ok:1
    })
  },
  getUser: async (req, res) => { 
    const { page, limit } = req.query
      const data = await UserService.getUser(page,limit)
      res.send(data)
  },
  login: async (req, res) => {
    const { username, password } = req.body
    const data = await UserService.login(username, password)
    if (data.length == 0) {
      res.send({ok:0})
    } else {
      res.send({ok:1})
    }
    
  }
}

module.exports = UserController
  • model/UserModel.js
 // 使用mongos模块连接mongoDB
const mongoose = require("mongoose")
// 限制模型,必须和数据库一致
const UserType = {
    username: String,
    password: String,
    age:Number
}

// 创建user模型
const UserModel = mongoose.model("user",new mongoose.Schema(UserType))
// 模型user将会对应 users集合(自动创建users集合)

module.exports = UserModel
  • route/login.js
var express = require('express');
var router = express.Router();

router.get('/', function (req, res, next) {
    console.log("hello")
   res.render("login");
});

module.exports = router;

routers/users.js

var express = require('express');
const UserModel = require('../model/UserModel');
const UserController = require('../controller/UserController');
var router = express.Router();

/* GET users listing. */
router.get('/', function(req, res, next) {
  res.send('respond with a resource');
});
// 响应post请求,增加用户
router.post("/user",UserController.addUser)

// 更新updateMany:修改多个,updateOne修改一个
// 动态路由获取id
router.put("/user",UserController.updateUser)

// 删除
router.delete("/user", UserController.deleteUser)

// 查询
router.get("/user", UserController.getUser)

// 登录校验
router.post("/login",UserController.login)

module.exports = router;

services/UserService.js

const UserModel = require('../model/UserModel');
const UserService = {
    addUser: (username, password, age ) => {
        // .create就相当于插入
        return UserModel.create({
            username: username,
            password: password,
            age:age
        }).then(data => {
            console.log(data)
        })
    },
    updateUser: (id,username,password,age) => {
        return UserModel.updateOne({ _id: id }, {
            username,password,age
        })
    },
    deleteUser: (id) => {
        return UserModel.deleteOne({ _id: id})
    },
    getUser: (page,limit) => {
        return UserModel.find ({},["username","age"]).sort({age:1}).skip((page-1)*limit).limit(limit)
    },
    login: (username, password) => {
        return UserModel.find({username,password})
    }

}

module.exports = UserService

index.ejs

<!DOCTYPE html>
<html>
  <head>
    <title><%= title %></title>
    <link rel="stylesheet" href="/stylesheets/style.css" />
  </head>
  <body>
    <div>
      <h1>后台管理系统</h1>
      <div>用户名:<input id="username" /></div>
      <div>密码:<input type="password" id="password" /></div>
      <div>年龄:<input type="number" id="age" /></div>
      <div><button id="register">注册</button></div>
      <hr />
      <div>
        <button class="update" id="update" value="62c7c98e8e9c28077a53d4a7">
          更新
        </button>
        <button class="delete" id="delete">删除</button>
      </div>
      <br />
      <table border="1">
        <thead>
          <tr>
            <td>id</td>
            <td>用户</td>
            <td>年龄</td>
            <td>操作</td>
          </tr>
        </thead>
        <tbody></tbody>
      </table>
    </div>
    <script type="text/javascript">
      var register = document.querySelector("#register");
      var username = document.querySelector("#username");
      var password = document.querySelector("#password");
      // var update = document.getElementsByClassName("update");
      // var mydelete = document.querySelector("#delete");

      function updateFun() {
        console.log(this.value);
        fetch(`/api/user?id=${this.value}`, {
          method: "PUT",
          headers: {
            "Content-Type": "application/json",
          },
          body: JSON.stringify({
            username: "名字",
            password: "密码",
            age: 1,
          }),
        })
          .then((res) => res.json())
          .then((res) => {
            console.log(res);
          });
      }
      function deleteFun() {
        console.log(this.value);
        fetch(`/api/user?id=${this.value}`, {
          method: "DELETE",
        })
          .then((res) => res.json())
          .then((res) => {
            console.log(res);
          });
      }

      register.onclick = () => {
        console.log(username.value, password.value, age.value);
        fetch("/api/user", {
          method: "POST",
          headers: {
            "Content-Type": "application/json",
          },
          body: JSON.stringify({
            username: username.value,
            password: password.value,
            age: age.value,
          }),
        })
          .then((res) => res.json())
          .then((res) => {
            console.log(res);
          });
      };

      var updates;
      async function getButton() {
        await fetch("/api/user?page=1&limit=2")
          .then((res) => res.json())
          .then((res) => {
            console.log(res);
            var tbody = document.querySelector("tbody");
            // map映射
            tbody.innerHTML = res
              .map(
                (item) => `
              <tr>
                <td>${item._id}</td>
                <td>${item.username}</td>
                <td>${item.age}</td>
                <td> <button class="update" value=${item._id}>更新</button><button class="delete" value=${item._id}>删除</button> </td>
              </tr>
              `
              )
              .join("");
          });

        // 更新操作
        updates = document.getElementsByClassName("update");
        var i = 0;
        for (i; i < updates.length; i++) {
          updates[i].onclick = updateFun;
        }

        // 删除操作
        mydeletes = document.getElementsByClassName("delete");
        var i = 0;
        for (i; i < mydeletes.length; i++) {
          mydeletes[i].onclick = deleteFun;
        }
      }

      getButton();
    </script>
  </body>
</html>

login.ejs

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <h1>登录界面</h1>
    <div>
        <div>用户名:<input id="username" /></div>
        <div>密码:<input type="password" id="password" /></div>
        <div><button id="login">登录</button></div>
    </div>

    <script type='text/javascript'>
        var login = document.querySelector("#login");
        var username = document.querySelector("#username");
        var password = document.querySelector("#password");

        login.onclick = () => {
        console.log(username.value, password.value);
        fetch("/api/login", {
          method: "POST",
          headers: {
            "Content-Type": "application/json",
          },
          body: JSON.stringify({
            username: username.value,
            password: password.value
          }),
        })
          .then((res) => res.json())
          .then((res) => {
            // console.log(res);
            if(res.ok==1){
                location.href="/"
            }else{
                alert("用户名密码错误")
            }
          });
      };

    </script>
</body>
</html>

app.js

var createError = require('http-errors');
var express = require('express');
var path = require('path');
var cookieParser = require('cookie-parser');
var logger = require('morgan');

var indexRouter = require('./routes/index');
var usersRouter = require('./routes/users');
var loginRouter = require('./routes/login');

var app = express();

// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');

app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));

app.use('/', indexRouter);
app.use('/api', usersRouter);
app.use('/login', loginRouter);

// catch 404 and forward to error handler
app.use(function(req, res, next) {
  next(createError(404));
});

// error handler
app.use(function(err, req, res, next) {
  // set locals, only providing error in development
  res.locals.message = err.message;
  res.locals.error = req.app.get('env') === 'development' ? err : {};

  // render the error page
  res.status(err.status || 500);
  res.render('error');
});

module.exports = app;

package.js

{
  "name": "server",
  "version": "0.0.0",
  "private": true,
  "scripts": {
    "start": "nodemon ./bin/www"
  },
  "dependencies": {
    "cookie-parser": "~1.4.4",
    "debug": "~2.6.9",
    "ejs": "~2.6.1",
    "express": "~4.16.1",
    "http-errors": "~1.6.3",
    "mongoose": "^6.4.3",
    "morgan": "~1.9.1"
  }
}

效果:
在这里插入图片描述
直接访问http://localhost:3000/也可以进入主页面
在这里插入图片描述

cookie和Session

cookie和Session的使用

  • 下载
    npm i express-session
  • 设置session
    app.js
// 引入session模块
var session = require("express-session")

// 注册session
app.use(session({
  name: "yang123456",
  secret: "1234567890",//密钥
  cookie: {
    maxAge: 1000 * 60 * 60,//过期时间
    secure:false,//为true时候表示只有https协议才能访问cookie
  },
  resave: true,//每次访问接口之后,计时重新计算
  saveUninitialized: this.trace,//开始访问就设置cookie,但是未登录之前是无效的
}))

访问浏览器生成如下cookie
在这里插入图片描述

  • 登录成功之后,设置session对象,即添加自己的属性。
    user Controller.js
 login: async (req, res) => {
    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]
      res.send({ok:1})
    }
    
  }
  • 加载主页面的时候验证身份
    index.js:
router.get('/', function (req, res, next) {
  // 判断req.session.user
  if (req.session.user) {
     res.render('index', { title: 'Express' });
  } else {
    res.redirect("/login")
  }
 
});

这样可以达到当cookie过期的时候再次访问home页面就会跳转到登陆页面,但是我们想要达到的效果是,cookie过期之后应该不能执行所有操作,所以在执行任何操作之前都应该判断一下cookie是否过期或存在。
所以我们可以写一个中间件。

  • app.js
// 设置中间件,验证session是否过期
app.use((req, res, next) => {
  // 排除login相关的接口
  if (req.url.includes("login")) {
    next();
    return
  }
  if (req.session.user) {
    next()
  } else {
    // ajax请求返回的是json数据,所以ajax请求不可以在后端跳转,需要传回数据在前端进行跳转
    // 是接口返回错误码(此项目中是接口说明是ajax请求)
    // 不是接口,直接重定向
    if (req.url.includes("/api")) {
      res.status(401).send({ok:0})
    } else {
       res.redirect("/login")
    }
  }
})

app.use('/', indexRouter);
app.use('/api', usersRouter);
app.use('/login', loginRouter);
  • 前端
    index.ejs
<!DOCTYPE html>
<html>
  <head>
    <title><%= title %></title>
    <link rel="stylesheet" href="/stylesheets/style.css" />
  </head>
  <body>
    <div>
      <h1>后台管理系统</h1>
      <div>用户名:<input id="username" /></div>
      <div>密码:<input type="password" id="password" /></div>
      <div>年龄:<input type="number" id="age" /></div>
      <div><button id="register">注册</button></div>
      <hr />
      <div>
        <button class="update" id="update" value="62c7c98e8e9c28077a53d4a7">
          更新
        </button>
        <button class="delete" id="delete">删除</button>
      </div>
      <br />
      <table border="1">
        <thead>
          <tr>
            <td>id</td>
            <td>用户</td>
            <td>年龄</td>
            <td>操作</td>
          </tr>
        </thead>
        <tbody></tbody>
      </table>
    </div>
    <script type="text/javascript">
      var register = document.querySelector("#register");
      var username = document.querySelector("#username");
      var password = document.querySelector("#password");
      // var update = document.getElementsByClassName("update");
      // var mydelete = document.querySelector("#delete");

      function updateFun() {
        console.log(this.value);
        fetch(`/api/user?id=${this.value}`, {
          method: "PUT",
          headers: {
            "Content-Type": "application/json",
          },
          body: JSON.stringify({
            username: "名字",
            password: "密码",
            age: 1,
          }),
        })
          .then((res) => res.json())
          .then((res) => {
            if(res.ok==0){
              location.href="/login"
            }
            console.log(res);
          });
      }
      function deleteFun() {
        console.log(this.value);
        fetch(`/api/user?id=${this.value}`, {
          method: "DELETE",
        })
          .then((res) => res.json())
          .then((res) => {
            console.log(res);
            if(res.ok==0){
              location.href="/login"
            }
          });
      }

      register.onclick = () => {
        console.log(username.value, password.value, age.value);
        fetch("/api/user", {
          method: "POST",
          headers: {
            "Content-Type": "application/json",
          },
          body: JSON.stringify({
            username: username.value,
            password: password.value,
            age: age.value,
          }),
        })
          .then((res) => res.json())
          .then((res) => {
            console.log(res);
            if(res.ok==0){
              location.href="/login"
            }
          });
      };
      var updates;
      async function getButton() {
        await fetch("/api/user?page=1&limit=2")
          .then((res) => res.json())
          .then((res) => {
            console.log(res);
            var tbody = document.querySelector("tbody");
            // map映射
            tbody.innerHTML = res
              .map(
                (item) => `
              <tr>
                <td>${item._id}</td>
                <td>${item.username}</td>
                <td>${item.age}</td>
                <td> <button class="update" value=${item._id}>更新</button><button class="delete" value=${item._id}>删除</button> </td>
              </tr>
              `
              )
              .join("");
          });

        // 更新操作
        updates = document.getElementsByClassName("update");
        var i = 0;
        for (i; i < updates.length; i++) {
          updates[i].onclick = updateFun;
        }

        // 删除操作
        mydeletes = document.getElementsByClassName("delete");
        var i = 0;
        for (i; i < mydeletes.length; i++) {
          mydeletes[i].onclick = deleteFun;
        }
      }

      getButton();
    </script>
  </body>
</html>

cookie和Session的销毁

在服务器端进行销毁:eq.session.destroy()方法

 logout:(req, res) => {
    req.session.destroy(() => {
      res.send({ok:1})
    })
  }

index.ejs:

<!DOCTYPE html>
<html>
  <head>
    <title><%= title %></title>
    <link rel="stylesheet" href="/stylesheets/style.css" />
  </head>
  <body>
    <div>
      <h1>后台管理系统
        <button id="exit">退出登录</button>
        </h1>
      <div>用户名:<input id="username" /></div>
      <div>密码:<input type="password" id="password" /></div>
      <div>年龄:<input type="number" id="age" /></div>
      <div><button id="register">注册</button></div>
      <hr />
      <div>
        <button class="update" id="update" value="62c7c98e8e9c28077a53d4a7">
          更新
        </button>
        <button class="delete" id="delete">删除</button>
      </div>
      <br />
      <table border="1">
        <thead>
          <tr>
            <td>id</td>
            <td>用户</td>
            <td>年龄</td>
            <td>操作</td>
          </tr>
        </thead>
        <tbody></tbody>
      </table>
    </div>
    <script type="text/javascript">
      var register = document.querySelector("#register");
      var username = document.querySelector("#username");
      var password = document.querySelector("#password");
      // var update = document.getElementsByClassName("update");
      // var mydelete = document.querySelector("#delete");
      var exit = document.querySelector("#exit");

      function updateFun() {
        console.log(this.value);
        fetch(`/api/user?id=${this.value}`, {
          method: "PUT",
          headers: {
            "Content-Type": "application/json",
          },
          body: JSON.stringify({
            username: "名字",
            password: "密码",
            age: 1,
          }),
        })
          .then((res) => res.json())
          .then((res) => {
            if(res.ok==0){
              location.href="/login"
            }
            console.log(res);
          });
      }
      function deleteFun() {
        console.log(this.value);
        fetch(`/api/user?id=${this.value}`, {
          method: "DELETE",
        })
          .then((res) => res.json())
          .then((res) => {
            console.log(res);
            if(res.ok==0){
              location.href="/login"
            }
          });
      }

      register.onclick = () => {
        console.log(username.value, password.value, age.value);
        fetch("/api/user", {
          method: "POST",
          headers: {
            "Content-Type": "application/json",
          },
          body: JSON.stringify({
            username: username.value,
            password: password.value,
            age: age.value,
          }),
        })
          .then((res) => res.json())
          .then((res) => {
            console.log(res);
            if(res.ok==0){
              location.href="/login"
            }
          });
      };

      // 第一页数据,要两条
      //   fetch("/api/user?page=1&limit=2")
      //     .then((res) => res.json())
      //     .then((res) => {
      //       console.log(res);
      //       var tbody = document.querySelector("tbody");
      //       // map映射
      //       tbody.innerHTML = res
      //         .map(
      //           (item) => `
      //     <tr>
      //       <td>${item._id}</td>
      //       <td>${item.username}</td>
      //       <td>${item.age}</td>
      //       <td> <button class="update" value="${item._id}"">更新</button><button class="delete">删除</button> </td>
      //     </tr>
      //     `
      //         )
      //         .join("");
      //     });

      var updates;
      async function getButton() {
        await fetch("/api/user?page=1&limit=2")
          .then((res) => res.json())
          .then((res) => {
            console.log(res);
            var tbody = document.querySelector("tbody");
            // map映射
            tbody.innerHTML = res
              .map(
                (item) => `
              <tr>
                <td>${item._id}</td>
                <td>${item.username}</td>
                <td>${item.age}</td>
                <td> <button class="update" value=${item._id}>更新</button><button class="delete" value=${item._id}>删除</button> </td>
              </tr>
              `
              )
              .join("");
          });

        // 更新操作
        updates = document.getElementsByClassName("update");
        var i = 0;
        for (i; i < updates.length; i++) {
          updates[i].onclick = updateFun;
        }

        // 删除操作
        mydeletes = document.getElementsByClassName("delete");
        var i = 0;
        for (i; i < mydeletes.length; i++) {
          mydeletes[i].onclick = deleteFun;
        }
      }

      getButton();

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

users.js:

var express = require('express');
const UserModel = require('../model/UserModel');
const UserController = require('../controller/UserController');
var router = express.Router();

/* GET users listing. */
router.get('/', function(req, res, next) {
  res.send('respond with a resource');
});
// 响应post请求,增加用户
router.post("/user",UserController.addUser)

// 更新updateMany:修改多个,updateOne修改一个
// 动态路由获取id
router.put("/user",UserController.updateUser)

// 删除
router.delete("/user", UserController.deleteUser)

// 查询
router.get("/user", UserController.getUser)

// 登录校验
router.post("/login", UserController.login)

router.get("/logout",UserController.logout)

module.exports = router;

userController.js

const UserService = require('../services/UserService');
const UserController = {
  addUser: async (req, res) => {
  console.log(req.body)
  // 插入数据库
  const { username, password, age } = req.body
    
  await UserService.addUser( username, password, age)
  res.send({ok:1})
  },
  updateUser:async (req, res) => {
        const {username,password,age} = req.body
        await UserService.updateUser(req.query.id,username,password,age)
        res.send({
            ok:1
            })
  },
  deleteUser: async (req, res) => {
    console.log(req.query.id)
     await UserService.deleteUser(req.query.id)
     res.send({
      ok:1
    })
  },
  getUser: async (req, res) => { 
    const { page, limit } = req.query
      const data = await UserService.getUser(page,limit)
      res.send(data)
  },
  login: async (req, res) => {
    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]
      res.send({ok:1})
    }
    
  },
  logout:(req, res) => {
    req.session.destroy(() => {
      res.send({ok:1})
    })
  }
}

module.exports = UserController

每次访问重新设置过期事件

resave: true,表示重新设置session,计时重新计算。
所以每次访问前重新设置session的时间:
req.session.date = Date.now()

app.js

var createError = require('http-errors');
var express = require('express');
var path = require('path');
var cookieParser = require('cookie-parser');
var logger = require('morgan');

var indexRouter = require('./routes/index');
var usersRouter = require('./routes/users');
var loginRouter = require('./routes/login');

// 引入session模块
var session = require("express-session")
var app = express();

// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');

app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));

// 注册session
app.use(session({
  name: "yang123456",
  secret: "1234567890",//密钥
  cookie: {
    maxAge: 1000 * 60 * 60,//过期时间
    secure:false,//为true时候表示只有https协议才能访问cookie
  },
  resave: true,//重新设置session,计时重新计算
  saveUninitialized: this.trace,//开始访问就设置cookie,但是未登录之前是无效的
}))

// 设置中间件,验证session是否过期
app.use((req, res, next) => {
  // 排除login相关的接口
  if (req.url.includes("login")) {
    next();
    return
  }
  if (req.session.user) {
    // 重新设置时间
    req.session.date = Date.now()
    next()
  } else {
    // ajax请求返回的是json数据,所以ajax请求不可以在后端跳转,需要传回数据在前端进行跳转
    // 是接口返回错误码(此项目中是接口说明是ajax请求)
    // 不是接口,直接重定向
    if (req.url.includes("/api")) {
      res.status(401).send({ok:0})
    } else {
       res.redirect("/login")
    }
  }
})

app.use('/', indexRouter);
app.use('/api', usersRouter);
app.use('/login', loginRouter);

// catch 404 and forward to error handler
app.use(function(req, res, next) {
  next(createError(404));
});

// error handler
app.use(function(err, req, res, next) {
  // set locals, only providing error in development
  res.locals.message = err.message;
  res.locals.error = req.app.get('env') === 'development' ? err : {};

  // render the error page
  res.status(err.status || 500);
  res.render('error');
});

module.exports = app;

每次重启服务器,用户需要重新登陆

解决:这是因为session数据存储在了内存中,内存中的数据不能永久存储,所以我们可以把session数据存贮在硬盘中。如存储在数据库中,过期后会自动销毁。

可以通过设置中间件完成。

  • npm i connect-mongo
  • 配置
    app.js
const Mongostore = require("connect-mongo");

// 注册session
app.use(session({
  name: "yang123456",
  secret: "1234567890",//密钥
  cookie: {
    maxAge: 1000 * 60 * 60,//过期时间
    secure:false,//为true时候表示只有https协议才能访问cookie
  },
  resave: true,//重新设置session,计时重新计算
  saveUninitialized: true,//开始访问就设置cookie,但是未登录之前是无效的
  store: MongoStore.create({
    // 将session放入到yang_session数据库中(新建的)
    mongoUrl:'mongodb://127.0.0.1:27017/yang_session',
    ttl:1000*60*10//过期时间
  })
}))

JWT

cookie和Session缺点

  • cookie和Session缺点:占用大量服务器资源。
  • CSRF攻击
    在这里插入图片描述
    假设你正在访问a网站,完成了登录,此时浏览器就会有cookie信息。这时你去访问b网站,恰好b网站有一个http://a.com?from=aaaa&to=vvv&money=100 a网站可以相应的请求,此时又携带着a网站的cookie信息,就会自动执行该链接,这就是CSRF攻击

JSON Web Token(JWT)产生和优点

解决CSRF攻击

  • 使用localStorage存储信息。localStorage只有用户主动访问才会将数据给后端,而只有在本网页才会访问localStorage
  • 使用localStorage存储信息,也可以解决在服务器端存储信息占用资源的问题
  • 但是localStorage很容易被篡改,使用明文存储很危险,所以我们加密存储。加密签名——token

(1)token的生成
在这里插入图片描述
密钥存放在后端。
后端将token生成之后传递给前端。
前端发送请求携带token。
(2)后端token的验证
取出token的Header和UserID,进行HMAC- SHA256加密和密钥加密,生成的签名和token中的签名进行对比,判断是否相同,相同说明是同一个用户。

在这里插入图片描述

但是token被偷走也没办法。

CSRF攻击——浏览器会自动带上cookie,而不会带上token;

JSON Web Token(JWT)缺点

  1. 占带宽,正常情况下要比session. _id更大,需要消耗更多流量,挤占更多带宽,
  2. 无法在服务端注销,那么久很难解决劫持问题;
  3. 性能问题,JWT的卖点之一就是加密签名, 由于这个特性,接收方得以验证JWT是否有效且被信任。对于有着严格性能要求的Web应用,这并不理想,尤其对于单线程环境。

JSON Web Token(JWT)的使用

JWT一般用于前后端分别开发的模式,因为发送ajax请求时才会携带token,而ajax请求的返回值应该是数据,不能直接在后端跳转页面,应该把数据待会到前端在前端进行判断跳转。

JWT的加密

  • 安装 npm i jsonwebtoken
  • 使用:
var jwt = require("jsonwebtoken")
var token = jwt.sign({data:'yang'},'secret',{expiresIn:60*60})
console.log(token)

{data:‘yang’}:表示加密的数据
‘secret’:表示密匙
{expiresIn:60 * 60}:表示过期时间,60*60以s为单位所以表示1h。支持:10s,10h,10d 这样的写法。

输出:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJkYXRhIjoieWFuZyIsImlhdCI6MTY1NzMzMTA3NiwiZXhwIjoxNjU3MzM0Njc2fQ.J8W10qalKUqjm9UR_-E3vwMzXXmSDZO3vNgyNM651-Y

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 :header
eyJkYXRhIjoieWFuZyIsImlhdCI6MTY1NzMzMTA3NiwiZXhwIjoxNjU3MzM0Njc2fQ : 数据
J8W10qalKUqjm9UR_-E3vwMzXXmSDZO3vNgyNM651-Y :生成的签名

JWT的校验

jwt.verify(token, 'secret')

var jwt = require("jsonwebtoken")
var token = jwt.sign({data:'yang'},'secret',{expiresIn:60*60})

var decode = jwt.verify(token, 'secret')
console.log(decode)

输出:

{ data: 'yang', iat: 1657331285, exp: 1657334885 }

JWT的封装

我们一般直接将jwt的加密解密直接封装起来
until/jwt.js

var jwt = require("jsonwebtoken")
const secret ='yang-secret'

const JWT = {
    // 加密
    generate(value,expires) {
        return jwt.sign(value,secret,{expiresIn:expires})
    },
    // 解密
    verify(token) {
        try {
            return jwt.verify(token, secret)
        } catch (error) {
            return false
        }
    }
}


测试使用:

const token = JWT.generate({ name: 'yang' }, "10s")
console.log(JWT.verify(token))

setTimeout(() => {
  console.log(JWT.verify(token))
}, 11000);

输出:

{ name: 'yang', iat: 1657332126, exp: 1657332136 }
false

JWT实现登录鉴权

  • UserController.js
    在登陆后生成token
login: async (req, res) => {
    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("Authorization",token)
      res.send({ok:1})
    }
    
  },

点击登陆之后,响应头中携带token

Authorization: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2MmM2ZmU3NzMzMzJkYzMxNGY5NjRhZmEiLCJ1c2VybmFtZSI6InlhbmciLCJpYXQiOjE2NTczMzM2MDcsImV4cCI6MTY1NzMzNzIwN30.W9S39A-CbyBg-5o7yG9BKXurdAZLSpKI8FPbcuLgoaY
  • 登陆成功后应该设置token为简化写法我们可以直接使用axios拦截器进行拦截。

引入:
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>

直接使用axios发送ajax请求,同时设置拦截器。

Login.ejs:
axios发送ajax请求:

login.onclick = () => {
          axios.post("/api/login",{
            username: username.value,
            password: password.value
          }).then(res=>{
            if(res.data.ok==1){
              // 存储token
              location.href="/"
            }else{
                alert("用户名密码错误")
            }
          })
      };

设置拦截器:(在请求后即登陆成功后进行token设置)

<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
    <script type='text/javascript'>
        // 拦截器
        // 请求发出前执行的方法
      axios.interceptors.request.use(function (config) {
          console.log("请求发出前执行的方法")
          return config;
        }, function (error) {
         
          return Promise.reject(error);
      });
        // 请求成功之后第一个调用的方法
      axios.interceptors.response.use(function (response) {
            console.log("请求成功之后第一个调用的方法")
            const {authorization} = response.headers
            authorization && localStorage.setItem("token",authorization)
            return response;
          }, function (error) {
            
            return Promise.reject(error);
      });
</script>

登陆后查看localStroage:
在这里插入图片描述

index.ejs:

<!DOCTYPE html>
<html>
  <head>
    <title><%= title %></title>
    <link rel="stylesheet" href="/stylesheets/style.css" />
    <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
    <script type='text/javascript'>
        // 拦截器
        // 请求发出前执行的方法
      axios.interceptors.request.use(function (config) {
          console.log("请求发出前执行的方法")
          // 将token给后端
          const token = localStorage.getItem("token")
          // config是请求对象,可以传递给后端,Bearer 是规范
          config.headers.Authorization = `Bearer ${token}`
          return config;
        }, function (error) {
         
          return Promise.reject(error);
      });
        // 请求成功之后第一个调用的方法
      axios.interceptors.response.use(function (response) {
            console.log("请求成功之后第一个调用的方法")
            const {authorization} = response.headers
            authorization && localStorage.setItem("token",authorization)
            return response;

            if(response.data.ok==0){
              location.href="/login"
            }
          }, function (error) {
            
            return Promise.reject(error);
      });
      </script>
  </head>
  <body>
    <div>
      <h1>后台管理系统
        <button id="exit">退出登录</button>
        </h1>
      <div>用户名:<input id="username" /></div>
      <div>密码:<input type="password" id="password" /></div>
      <div>年龄:<input type="number" id="age" /></div>
      <div><button id="register">注册</button></div>
      <hr />
      <div>
        <button class="update" id="update" value="62c7c98e8e9c28077a53d4a7">
          更新
        </button>
        <button class="delete" id="delete">删除</button>
      </div>
      <br />
      <table border="1">
        <thead>
          <tr>
            <td>id</td>
            <td>用户</td>
            <td>年龄</td>
            <td>操作</td>
          </tr>
        </thead>
        <tbody></tbody>
      </table>
    </div>
    <script type="text/javascript">
      var register = document.querySelector("#register");
      var username = document.querySelector("#username");
      var password = document.querySelector("#password");
      // var update = document.getElementsByClassName("update");
      // var mydelete = document.querySelector("#delete");
      var exit = document.querySelector("#exit");

      function updateFun() {
        console.log(this.value);
        axios.put(`/api/user?id=${this.value}`,{
            username: "名字",
            password: "密码",
            age: 1,
          }).then((res) => {
            console.log(res);
          });
      }
      function deleteFun() {
        console.log(this.value);
        axios.delete(`/api/user?id=${this.value}`).then((res) => {
            console.log(res.data);
          });
        }


      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);
          });
        }
        

      var updates;
      async function getButton() {
        await axios.get("/api/user?page=1&limit=2").then((res) => {
           res = res.data
            console.log(res);
            var tbody = document.querySelector("tbody");
            // map映射
            tbody.innerHTML = res
              .map(
                (item) => `
              <tr>
                <td>${item._id}</td>
                <td>${item.username}</td>
                <td>${item.age}</td>
                <td> <button class="update" value=${item._id}>更新</button><button class="delete" value=${item._id}>删除</button> </td>
              </tr>
              `
              )
              .join("");
          })

        // 更新操作
        updates = document.getElementsByClassName("update");
        var i = 0;
        for (i; i < updates.length; i++) {
          updates[i].onclick = updateFun;
        }

        // 删除操作
        mydeletes = document.getElementsByClassName("delete");
        var i = 0;
        for (i; i < mydeletes.length; i++) {
          mydeletes[i].onclick = deleteFun;
        }
      }

      getButton();

      exit.onclick = ()=>{
        // 直接移除前端的token
       localStorage.removeItem("token") 
        location.href="/login"
     
      }
    </script>
  </body>
</html>

  • app.js进行判断
var createError = require('http-errors');
var express = require('express');
var path = require('path');
var cookieParser = require('cookie-parser');
var logger = require('morgan');

var indexRouter = require('./routes/index');
var usersRouter = require('./routes/users');
var loginRouter = require('./routes/login');
var JWT = require('./utils/jwt')

// 引入session模块
var session = require("express-session")
var app = express();
const MongoStore = require("connect-mongo");

// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');

app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));

// 注册session
app.use(session({
  name: "yang123456",
  secret: "1234567890",//密钥
  cookie: {
    maxAge: 1000 * 60 * 60,//过期时间
    secure:false,//为true时候表示只有https协议才能访问cookie
  },
  resave: true,//重新设置session,计时重新计算
  saveUninitialized: true,//开始访问就设置cookie,但是未登录之前是无效的
  store: MongoStore.create({
    // 将session放入到yang_session数据库中(新创建的)
    mongoUrl:'mongodb://127.0.0.1:27017/yang_session',
    ttl:1000*60*10//过期时间
  })
}))

// 设置中间件,验证session是否过期
app.use((req, res, next) => {
  // 排除login相关的接口
  if (req.url.includes("login")) {
    next();
    return
  }
  const token = req.headers["authorization"]?.split(" ")[1]
  // token为null可以进入if(token)语句,但是为undefine就不可以了
  // if语句null会判断为真?
  if (token) {
    console.log(token)
    const payload = JWT.verify(token)
    if (payload) {
      next()
    } else {
      res.status(401).send({ errCose: -1, errInfo: "token过期" })
    }
  } else {
    console.log(token)
    next()
    // 当token=undefined时(即token没有时)会跳转到这里
    // 这里为什么直接放过去而不直接返回401,是因为放过去之后跳转到index页面
    // 跳转到index页面需要请求数据,index页面的拦截器就会封装一个token(但是这个token是null),再次执行到该函数,会跳转到if (token) 中,但是解析后返回false,进而跳转到res.status(401)。

    // res.status(401).send({ errCose: -1, errInfo: "token不存在" })
  }
  
})

app.use('/', indexRouter);
app.use('/api', usersRouter);
app.use('/login', loginRouter);

// catch 404 and forward to error handler
app.use(function(req, res, next) {
  next(createError(404));
});

// error handler
app.use(function(err, req, res, next) {
  // set locals, only providing error in development
  res.locals.message = err.message;
  res.locals.error = req.app.get('env') === 'development' ? err : {};

  // render the error page
  res.status(err.status || 500);
  res.render('error');
});

module.exports = app;

  • 每次成功获取token后都应该更新token的时间
    app.js:
var createError = require('http-errors');
var express = require('express');
var path = require('path');
var cookieParser = require('cookie-parser');
var logger = require('morgan');

var indexRouter = require('./routes/index');
var usersRouter = require('./routes/users');
var loginRouter = require('./routes/login');
var JWT = require('./utils/jwt')

// 引入session模块
var session = require("express-session")
var app = express();
const MongoStore = require("connect-mongo");

// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');

app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));

// 注册session
app.use(session({
  name: "yang123456",
  secret: "1234567890",//密钥
  cookie: {
    maxAge: 1000 * 60 * 60,//过期时间
    secure:false,//为true时候表示只有https协议才能访问cookie
  },
  resave: true,//重新设置session,计时重新计算
  saveUninitialized: true,//开始访问就设置cookie,但是未登录之前是无效的
  store: MongoStore.create({
    // 将session放入到yang_session数据库中(新创建的)
    mongoUrl:'mongodb://127.0.0.1:27017/yang_session',
    ttl:1000*60*10//过期时间
  })
}))

// 设置中间件,验证session是否过期
app.use((req, res, next) => {
  // 排除login相关的接口
  if (req.url.includes("login")) {
    next();
    return
  }
  const token = req.headers["authorization"]?.split(" ")[1]
  // token为null可以进入if(token)语句,但是为undefine就不可以了
  // if语句null会判断为真?
  if (token) {
    console.log(token)
    const payload = JWT.verify(token)
    if (payload) {
      // 重新计算过期时间
      const newToken = JWT.generate({
        _id: payload._id,
        username:payload.username
      }, "1h")
      // 返回给前端,让前端更新
      res.header("Authorization",newToken)
      next()
    } else {
      res.status(401).send({ errCose: -1, errInfo: "token过期" })
    }
  } else {
    console.log(token)
    next()
    // 当token=undefined时(即token没有时)会跳转到这里
    // 这里为什么直接放过去而不直接返回401,是因为放过去之后跳转到index页面
    // 跳转到index页面需要请求数据,index页面的拦截器就会封装一个token(但是这个token是null),再次执行到该函数,会跳转到if (token) 中,但是解析后返回false,进而跳转到res.status(401)。

    // res.status(401).send({ errCose: -1, errInfo: "token不存在" })
  }
  
})

app.use('/', indexRouter);
app.use('/api', usersRouter);
app.use('/login', loginRouter);

// catch 404 and forward to error handler
app.use(function(req, res, next) {
  next(createError(404));
});

// error handler
app.use(function(err, req, res, next) {
  // set locals, only providing error in development
  res.locals.message = err.message;
  res.locals.error = req.app.get('env') === 'development' ? err : {};

  // render the error page
  res.status(err.status || 500);
  res.render('error');
});

module.exports = app;

这样就完成了JWT实现登录鉴权。

完成代码获取:
链接:https://pan.baidu.com/s/1NDwgX2pdBRyUJRd5Blayfg?pwd=xtfx
提取码:xtfx

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值