NodeJS

权限控制

鉴权

代码示例:

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;
// routes/login.js

var express = require('express');
var router = express.Router();

/* GET home page. */
router.get('/', function(req, res, next) {
    res.render('login');
});

module.exports = router;
var express = require('express');
var router = express.Router();

const UserController = require('../controllers/UserController')

router.post('/user', UserController.addUser)

router.put('/user/:id', UserController.updateUser)

router.delete('/user/:id', UserController.deleteUser)

router.get('/user', UserController.getUser)

router.post('/login', UserController.login)

module.exports = router;
// controllers/UserController.js

const UserService = require('../services/UserService')

const UserController = {
    addUser: (req, res) => {
        const {username, pwd, age} = req.body
        UserService.addUser(username, pwd, age)
            .then(data => {
                res.send({ok: 1})
            })
            .catch(err => console.log(err))
    },
    updateUser: (req, res) => {
        const {username, pwd, age} = req.body
        UserService.updateUser(req, username, pwd, age)
            .then(data => {
                res.send({ok: 1})
            })
            .catch(err => console.log(err))
    },
    deleteUser: (req, res) => {
        UserService.deleteUser(req)
            .then(data => {
                res.send({ok: 1})
            })
            .catch(err => console.log(err))
    },
    getUser: (req, res) => {
        const {page, limit} = req.query
        UserService.getUser(page, limit)
            .then(data => {
                res.send(data)
            })
    },
    login: (req, res) => {
        const {username, pwd} = req.body
        UserService.login(username, pwd)
            .then(data => {
                if (data.length === 0) {
                    res.send({ok: 0})
                } else {
                    res.send({ok: 1})
                }
            })
            .catch(err => console.log(err))
    }
}

module.exports = UserController
// services/UserService.js

const UserModel = require("../model/UserModel");

const UserService = {
    addUser: (username, pwd, age) => {
        return UserModel.create({
            username,
            pwd,
            age
        })
    },
    updateUser: (req, username, pwd, age) => {
        return UserModel.updateOne({_id: req.params.id}, {
            username,
            pwd,
            age
        })
    },
    deleteUser: (req) => {
        return UserModel.deleteOne({_id: req.params.id})
    },
    getUser: (page, limit) => {
        return UserModel.find({}, ['username', 'age'])
            .sort({age: 1})
            .skip((page - 1) * limit)
            .limit(limit)
    },
    login: (username, pwd) => {
        return UserModel.find({username, pwd})
    }
}

module.exports = UserService
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>Login</title>
</head>
<body>
    <div>
        <div>用户名:<input type="text" id="username"></div>
        <div>密码:<input type="password" id="pwd"></div>
        <div><button id="login">登录</button></div>
    </div>
    <script>
        const username = document.querySelector('#username')
        const pwd = document.querySelector('#pwd')
        const login = document.querySelector('#login')
        login.onclick = () => {
            fetch('/api/login', {
                method: 'POST',
                body: JSON.stringify({
                    username: username.value,
                    pwd: pwd.value
                }),
                headers: {
                    'Content-Type': 'application/json'
                }
            })
                .then(res => res.json())
                .then(res => {
                    console.log(res)
                    if (res.ok === 1){
                        location.href = '/'
                    } else {
                        alert('用户名或密码错误!')
                    }
                })
                .catch(err => console.log(err))
        }
    </script>
</body>
</html>

Cookie与Session

  1. Cookie
    • 客户端
  2. Session
    • 服务端

代码示例:

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 session = require('express-session')
var MongoStore = require('connect-mongo')

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(session({  // 注册中间件
    name: 'cookieName',  // 仅在HTTP数据交换的时候才能拿到Cookie,客户端命令行主动获取为空
    secret: 'abcdefg',  // 密钥(相当于加盐)
    cookie: {
        maxAge: 1000 * 60 * 60,  // Cookie过期时间(毫秒)
        secure: false  // 必须在HTTP协议中才能获取到(rue:HTTPS)
    },
    resave: true,  // 重新配置session后,会自动重新计算过期时间
    saveUninitialized: true,  // 是否在访问网站后登录注册之前生成一个无效Cookie(用于校验浏览器是否接受Cookie)
    store: MongoStore.create({
        mongoUrl: 'mongodb://127.0.0.1:27017/test',  // 创建一个新的数据库用于存放Session
        ttl: 1000 * 60 * 10  // 过期时间(会自动重新计算过期时间)
    })
}))

// 配置全局中间件
app.use((req, res, next) => {
    // 排除login相关的路由和接口(防止重定向次数过多)
    if (req.url.includes('login')) {
        next()
        return
    }

    if (req.session.user) {
        // 在Cookie过期时间之内请求服务端会自动重新计算过期时间
        req.session.date = Date.now()  // date并不是固定的(重新配置Session)
        next()  // 放行
    } else {
        // 接口的Ajax请求后端重定向并不会生效(浏览器限制,必须由前端主导)
        // 对应接口与路由(前后端不分离时,前端相关的拦截应该由拦截器主导)
        req.url.includes('api') ? res.status(401).json({ok: 0}) : 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;
// controllers/UserController.js

const UserService = require('../services/UserService')

const UserController = {
    addUser: (req, res) => {
        const {username, pwd, age} = req.body
        UserService.addUser(username, pwd, age)
            .then(data => {
                res.send({ok: 1})
            })
            .catch(err => console.log(err))
    },
    updateUser: (req, res) => {
        const {username, pwd, age} = req.body
        UserService.updateUser(req, username, pwd, age)
            .then(data => {
                res.send({ok: 1})
            })
            .catch(err => console.log(err))
    },
    deleteUser: (req, res) => {
        UserService.deleteUser(req)
            .then(data => {
                res.send({ok: 1})
            })
            .catch(err => console.log(err))
    },
    getUser: (req, res) => {
        const {page, limit} = req.query
        UserService.getUser(page, limit)
            .then(data => {
                res.send(data)
            })
    },
    login: (req, res) => {
        const {username, pwd} = req.body
        UserService.login(username, pwd)
            .then(data => {
                if (data.length === 0) {
                    res.send({ok: 0})
                } else {
                    // 配置Session对象(Cookie不同拿到的req.session对象也会不同)
                    // 默认存放在服务器的内存中(重启服务器后就会销毁)
                    req.session.user = data[0]
                    // Session与Cookie其中一方过期都会导致鉴权失败
                    res.send({ok: 1})
                }
            })
            .catch(err => console.log(err))
    },
    logout: (req, res) => {
        req.session.destroy(() => {  // destroy:销毁
            res.send({ok: 1})
        })
    }
}

module.exports = UserController
var express = require('express');
var router = express.Router();

/* GET home page. */
router.get('/', function (req, res, next) {
    res.render('index', {title: 'Express'});
});

module.exports = router;
var express = require('express');
var router = express.Router();

const UserController = require('../controllers/UserController')

router.post('/user', UserController.addUser)

router.put('/user/:id', UserController.updateUser)

router.delete('/user/:id', UserController.deleteUser)

router.get('/user', UserController.getUser)

router.post('/login', UserController.login)

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

module.exports = router;
<!DOCTYPE html>
<html lang="zh-CN">
  <head>
    <meta charset="UTF-8">
    <title><%= title %></title>
    <link rel='stylesheet' href='/stylesheets/style.css' />
  </head>
  <body>
    <div>
      <div>用户名:<input type="text" id="username"></div>
      <div>密码:<input type="password" id="pwd"></div>
      <div>年龄:<input type="number" id="age"></div>
      <div><button id="register">添加用户</button></div>
    </div>
    <div>
      <button id="update">更新用户</button>
      <button id="delete">删除用户</button>
    </div>
    <table>
      <thead>
      <tr>
        <td>id</td>
        <td>用户名</td>
        <td>年龄</td>
      </tr>
      </thead>
      <tbody>

      </tbody>
    </table>
    <div>
      <button id="exit">退出登录</button>
    </div>
    <script>
      const register = document.querySelector('#register')
      const username = document.querySelector('#username')
      const pwd = document.querySelector('#pwd')
      const age = document.querySelector('#age')
      const update = document.querySelector('#update')
      const deleteButton = document.querySelector('#delete')
      const exit = document.querySelector('#exit')
      register.onclick = () => {
        fetch('/api/user', {
          method: 'POST',
          body: JSON.stringify({
            username: username.value,
            pwd: pwd.value,
            age: age.value
          }),
          headers: {
            'Content-Type': 'application/json'
          }
        })
                .then(res => res.json())
                .then(res => {
                  console.log(res)
                  if (res.ok === 0) {
                    location.href = '/login'
                  }
                })
                .catch(err => console.log(err))
      }
      deleteButton.onclick = () => {
        fetch('/api/user/6317fa3d99222afe82d61af5', {
          method: 'DELETE'
        })
                .then(res => res.json())
                .then(res => {
                  console.log(res)
                  if (res.ok === 0) {
                    location.href = '/login'
                  }
                })
                .catch(err => console.log(err))
      }
      update.onclick = () => {
        fetch('/api/user/6317decdeb469ce9190d8c5c', {
          method: 'PUT',
          body: JSON.stringify({
            username: 'username',
            pwd: 'password',
            age: 24
          }),
          headers: {
            'Content-Type': 'application/json'
          }
        })
                .then(res => res.json())
                .then(res => {
                  console.log(res)
                  if (res.ok === 0) {
                    location.href = '/login'
                  }
                })
                .catch(err => console.log(err))
      }
      exit.onclick = () => {
        fetch('/api/logout')
                .then(res => res.json())
                .then(res => {
                  if (res.ok === 1) {
                    location.href = '/login'
                  }
                })
                .catch(err => console.log(err))
      }
      fetch('/api/user?page=1&limit=2')
              .then(res => res.json())
              .then(res => {
                const tbody = document.querySelector('tbody')
                tbody.innerHTML = res.map(item => `
                <tr>
                    <td>${item._id}</td>
                    <td>${item.username}</td>
                    <td>${item.age}</td>
                </tr>
                `).join('')
              })
              .catch(err => console.log(err))
    </script>
  </body>
</html>

JWT

  1. Token值存放在客户端浏览器中且独立于Cookie之外(防止CSRF)
  2. 当前端发送Ajax到后端的时候需要主动带上Token(而不是像Cookie一样自动发送)
  3. 服务器无法主动注销Token

代码示例:

// controllers/UserController.js

const UserService = require('../services/UserService')

const JWT = require('../util/JWT')

const UserController = {
    addUser: (req, res) => {
        const {username, pwd, age} = req.body
        UserService.addUser(username, pwd, age)
            .then(data => {
                res.send({ok: 1})
            })
            .catch(err => console.log(err))
    },
    updateUser: (req, res) => {
        const {username, pwd, age} = req.body
        UserService.updateUser(req, username, pwd, age)
            .then(data => {
                res.send({ok: 1})
            })
            .catch(err => console.log(err))
    },
    deleteUser: (req, res) => {
        UserService.deleteUser(req)
            .then(data => {
                res.send({ok: 1})
            })
            .catch(err => console.log(err))
    },
    getUser: (req, res) => {
        const {page, limit} = req.query
        UserService.getUser(page, limit)
            .then(data => {
                res.send(data)
            })
    },
    login: (req, res) => {
        const {username, pwd} = req.body
        UserService.login(username, pwd)
            .then(data => {
                if (data.length === 0) {
                    res.send({ok: 0})
                } else {
                    const token = JWT.generate({  // 因为data不是一个普通的对象
                        _id: data[0]._id,
                        username: data[0].username
                    }, '1h')
                    // token传输时存放在header中
                    res.header('Authorization', token)
                    res.send({ok: 1})
                }
            })
            .catch(err => console.log(err))
    },
    logout: (req, res) => {
        req.session.destroy(() => {
            res.send({ok: 1})
        })
    }
}

module.exports = UserController
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 session = require('express-session')
var MongoStore = require('connect-mongo')

var app = express();

var JWT = require('./util/JWT')

// 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(session({
    name: 'cookieName',
    secret: 'abcdefg',
    cookie: {
        maxAge: 1000 * 60 * 60,
        secure: false
    },
    resave: true,
    saveUninitialized: true,
    store: MongoStore.create({
        mongoUrl: 'mongodb://127.0.0.1:27017/test',
        ttl: 1000 * 60 * 10
    })
}))

app.use((req, res, next) => {
    if (req.url.includes('login')) {
        next()
        return
    }

    // console.log(req.headers[authorization]?.split(' ')[1])
    const token = req.headers['authorization']?.split(' ')[1]
    if (token) {
        const payload = JWT.verify(token)
        if (payload) {
            // 重新计算token过期的时间
            const newToken = JWT.generate({  // payload不是一个普通的对象
                _id: payload._id,
                username: payload.username
            }, '1h')
            res.header('Authorization', newToken)
            next()
        } else {
            res.status(401).send({errCode: -1, errInfo: 'token失效了'})
        }
    } else {
        next()
    }
})

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;
// util/JWT.js

const jwt = require('jsonwebtoken')

const secret = 'HQSY'  // 密钥

const JWT = {
    generate(value, expires) {  // 生成token
        return jwt.sign(value, secret, {expiresIn: expires})
    },
    verify(token) {  // 校验token
        try {
            return jwt.verify(token, secret)
        } catch (err) {
            return false
        }
    }
}

module.exports = JWT
<!DOCTYPE html>
<html lang="zh-CN">
  <head>
    <meta charset="UTF-8">
    <title>Login</title>
</head>
<body>
    <div>
        <div>用户名:<input type="text" id="username"></div>
        <div>密码:<input type="password" id="pwd"></div>
        <div><button id="login">登录</button></div>
    </div>
    <script src="/dist/axios.min.js"></script>
    <script>
        // 注册全局拦截器
        axios.interceptors.request.use(function (config) {
            return config
        }, function (error) {
            return Promise.reject((error))
        })

        axios.interceptors.response.use(function (response) {
            // console.log(response.headers)
            const {authorization} = response.headers
            authorization && localStorage.setItem('token', authorization)
            return response
        }, function (error) {
            return Promise.reject(error)
        })
    </script>
    <script>
        const username = document.querySelector('#username')
        const pwd = document.querySelector('#pwd')
        const login = document.querySelector('#login')
        login.onclick = () => {
            axios.post('/api/login', {
                username: username.value,
                pwd: pwd.value
            }).then(res => {
                // console.log(res.data)
                if (res.data.ok === 1){
                    location.href = '/'
                } else {
                    alert('用户名或密码错误!')
                }
            }).catch(err => console.log(err))
        }
    </script>
</body>
</html>
<!DOCTYPE html>
<html lang="zh-CN">
  <head>
    <meta charset="UTF-8">
    <title><%= title %></title>
    <link rel='stylesheet' href='/stylesheets/style.css' />
  </head>
  <body>
    <div>
      <div>用户名:<input type="text" id="username"></div>
      <div>密码:<input type="password" id="pwd"></div>
      <div>年龄:<input type="number" id="age"></div>
      <div><button id="register">添加用户</button></div>
    </div>
    <div>
      <button id="update">更新用户</button>
      <button id="delete">删除用户</button>
    </div>
    <table>
      <thead>
      <tr>
        <td>id</td>
        <td>用户名</td>
        <td>年龄</td>
      </tr>
      </thead>
      <tbody>

      </tbody>
    </table>
    <div>
      <button id="exit">退出登录</button>
    </div>
    <script src="/dist/axios.min.js"></script>
    <script>
      // 注册全局拦截器
      axios.interceptors.request.use(function (config) {
        const token = localStorage.getItem('token')
        config.headers.Authorization = `Bearer ${token}`  // Bearer为规范要求
        return config
      }, function (error) {
        return Promise.reject((error))
      })

      axios.interceptors.response.use(function (response) {
        // console.log(response.headers)
        const {authorization} = response.headers
        authorization && localStorage.setItem('token', authorization)
        return response
      }, function (error) {
        if (error.response.status === 401) {
          localStorage.removeItem('token')
          location.href = '/login'
        }
        return Promise.reject(error)
      })
    </script>
    <script>
      const register = document.querySelector('#register')
      const username = document.querySelector('#username')
      const pwd = document.querySelector('#pwd')
      const age = document.querySelector('#age')
      const update = document.querySelector('#update')
      const deleteButton = document.querySelector('#delete')
      const exit = document.querySelector('#exit')
      register.onclick = () => {
        axios.post('/api/user', {
          username: username.value,
          pwd: pwd.value,
          age: age.value
        }).then(res => console.log(res.data)).catch(err => console.log(err))
      }
      deleteButton.onclick = () => {
        axios.delete('/api/user/6317fa3d99222afe82d61af5').then(res => console.log(res.data)).catch(err => console.log(err))
      }
      update.onclick = () => {
        axios.put('/api/user/6317decdeb469ce9190d8c5c', {
          username: 'username',
          pwd: 'password',
          age: 24
        }).then(res => console.log(res.data)).catch(err => console.log(err))
      }
      exit.onclick = () => {
        localStorage.removeItem('token')
        location.href = '/login'
      }
      axios.get('/api/user?page=1&limit=2').then(res => {
        const tbody = document.querySelector('tbody')
        tbody.innerHTML = res.data.map(item => `
                <tr>
                    <td>${item._id}</td>
                    <td>${item.username}</td>
                    <td>${item.age}</td>
                </tr>
                `).join('')
      }).catch(err => console.log(err))
    </script>
  </body>
</html>