前端架构之node jwt认证

6,025 阅读4分钟

前言:

这次使用node express jwt实现一个小小的认证,数据库之类的慢慢在添加上去,先跑通整个流程,基本上是可以使用到项目里面的了。这次就不用gulp编译了,添加了log4.js一个错误日志,还稍微做了一下压测。就不详细展开了,有兴趣的可以下载来看:github地址

和前端对应:前端架构之vue+axios 前端实现登录拦截(路由拦截、http拦截)

大致流程:每次请求进来,都必须经过retoken.js,验证是否带token头部,若有则next(),没有则返回错误信息。当然也可以设置不需要认证的接口,判断后直接next()。

1.目录结构:

开始吧,config:配置文件,controllers:控制路由,logs:错误日志,middleware:一些中间件,model:数据交互,router:路由分发


2.编写app.js

app.js 

import express from 'express';
import bodyParser from 'body-parser'
import cookieParser from 'cookie-parser'
import session from 'express-session'
import log4js from 'log4js';
import CONFIG from './config';
import api from './router/api'
// import errorHandler from './middleware/logger';

let app = express();

//中间件设置
app.use(cookieParser('sessionCaptcha'))
app.use(session({
    secret: 'sessionCaptcha', // 与cookieParser中的一致
    resave: true,
    saveUninitialized: true,
    name: 'USER_INFO_ID'
}))
app.use(bodyParser.urlencoded({extended: false}))
app.use(bodyParser.json())
// 路由
app.use('/', api)

//错误处理
log4js.configure({
    appenders: { cheese: { type: 'file', filename: './logs/chen.log' } },
    categories: { default: { appenders: ['cheese'], level: 'error' } }
});

const logger = log4js.getLogger('cheese');

//只会记录error级别以上的错误
logger.error('Cheese is too ripe!');
logger.fatal('Cheese was breeding ground for listeria.');

//容错处理机制
app.use(logErrors);
app.use(clientErrorHandler);
app.use(errorHandler);

function logErrors(err, req, res, next) {
    console.error('记录日志', err.stack);
    next(err);
}

function clientErrorHandler(err, req, res, next) {
    if (req.xhr) {
        res.status(500).send({ error: 'Something blew up!' });
    } else {
        next(err);
    }
}

function errorHandler(err, req, res, next) {
    res.status(500);
    res.send('你错了');
}
app.use(function (req, res, next) {
    if (res.status(404)) {
        res.send('Sorry cant find that!404')
    }else{
        next()
    }

})
var server = app.listen(CONFIG.get('port'), CONFIG.get('url'), () => {
    var host = server.address().address;
    var port = server.address().port;
    console.log('Example app listening at http://%s:%s', host, port);
});

config>index.js 配置信息

const CONFIG = new Map()
//设置端口
CONFIG.set('port', 3000)
//设置url
CONFIG.set('url', '127.0.0.1')
export default CONFIG

3.编写router api

api.js router分发

import {Router} from 'express'
import jwt from 'jsonwebtoken'
import retoken from './retoken'
import indexController from '../controllers'

const router = Router()
//设置跨域
router.all('*', function(req, res, next) {
  res.header("Access-Control-Allow-Origin", "*");
  res.header("Access-Control-Allow-Headers", "Content-Type,Content-Length, Authorization, Accept,X-Requested-With");
  res.header("Access-Control-Allow-Methods","PUT,POST,GET,DELETE,OPTIONS");
  res.header("Access-Control-Request-Headers:content-type,xfilecategory,xfilename,xfilesize");
  res.header("X-Powered-By",' 3.2.1')
  if(req.method=="OPTIONS") res.send(200);/*让options请求快速返回*/
  else  next();
});
//添加token认证
router.use(retoken)

//实现登录
router.use('/login', (req, res, next) => {
  console.log(111)
 //设置个人信息
  const userToken = {
    name: '1',
    loginAt: +new Date
  }
  //签发token 指定过期时间2h
  const token = jwt.sign(userToken, 'chen', { expiresIn: '2h' });
  res.json({
    code: 200,
    data: token
  })
})

router.use('/index', (req, res, next) => {
    res.json({
        code: 200,
        data: 'henhao'
    })
})
//路由
router.use('/user',indexController)
export default router

retoken.js token认证

import jwt from "jsonwebtoken";
import api from "./unLogin";
let unLogin = api.unLogin
export default function (req, res, next) {
  let method = req.method.toLowerCase()
  let path = req.path
    //接口不需要登陆:直接next
    //判断method类型,并且是否包含path
    if(unLogin[method] && unLogin[method].indexOf(path) !== -1){
      console.log('到这里不用验证喔')
      return  next()
    }
    const token = req.headers.authorization
    // console.log(req.headers)
    //没有token值,返回401
    if (!token) {
        return res.json({
            code: 401,
            msg: 'you need login:there is no token'
        })
    }
   //认证token
    jwt.verify(token, 'chen', (err, decoded) => {
        console.log('这边需要验证才行喔')
        if(err){
            return res.json({
                code: 401,
                msg: err.msg
            })
        } else {
            // 将携带的信息赋给req.user
            req.user = decoded
            return next()
        }
    })
}

unLogin.js 不需要登录认证的接口

export default {
  unLogin: {
    get: [
      '/index',
      '/user'
    ],
    post: [
      '/login'
    ],
    put: [],
    delete: [],
  }
}

4.编写controllers models

controllers>index.js

import express from 'express'
import indexMode from '../model'
let router = express.Router()
router
  .get('/', (req, res, next) => {
    indexMode.getData(function (data) {
      res.json(data)
    })
  })
  .put('/', (req, res, next) => {
    res.json({
      code:200,
      data: 'put'
    })
  })

export default router

models>index.js

let indexModel={
  //这里操作数据库
  getData:function (cb) {
    var data = {
      code:200,
      data:{
        username:111,
        password:222
      }
    }
    cb(data)
  }
}
module.exports=indexModel

5.需要安装的包

使用nedemon 启动node

"dependencies": {
  "body-parser": "^1.18.3",
  "cookie-parser": "^1.4.3",
  "express": "^4.16.3",
  "express-session": "^1.15.6",
  "jsonwebtoken": "^8.3.0",
  "log4js": "^3.0.2"
},
"devDependencies": {
  "babel-cli": "^6.26.0",
  "babel-preset-es2015": "^6.24.1",
  "babel-preset-stage-2": "^6.24.1",
  "nodemon": "^1.18.3"
}
"scripts": {
  "test": "echo \"Error: no test specified\" && exit 1",
  "dev": "nodemon app.js --exec babel-node --presets es2015,stage-2"
},

使用npm run dev 启动后

访问 http://127.0.0.1:3000/index


访问 http://127.0.0.1:3000/user


这样便实现了node server的jwt认证,连接数据库那方面,等下次更新,下一步需要在前端那边做相关的配置了。

6.使用wrk压测一下

安装

wrk支持大多数类UNIX系统,不支持windows。需要操作系统支持LuaJIT和OpenSSL,不过不用担心,大多数类Unix系统都支持。安装wrk非常简单,只要从github上下载wrk源码,在项目路径下执行make命令即可。

git clone https://github.com/wg/wrk

make之后,会在项目路径下生成可执行文件wrk,随后就可以用其进行HTTP压测了。

压测

在当前目录下输入 ./wrk -t8 -c200 -d30s --latency 'http://127.0.0.1:3000/index'

使用8个线程200个连接,对http://127.0.0.1:3000/index进行了30秒的压测

Running 30s test @ http://127.0.0.1:3000/index
  8 threads and 200 connections
  Thread Stats   Avg      Stdev     Max     +/- Stdev
               (平均值) (标准差) (最大值)(正负一个标准差所占比例)
    Latency    65.99ms   15.58ms 185.51ms   89.74%
    (延迟)
    Req/Sec    380.14    126.58   505.00     57.24%
 (处理中的请求数)
  Latency Distribution (延迟分布)
     50%   60.90ms
     75%   62.99ms
     90%   82.57ms
     99%  127.67ms
90428 requests in 30.06s, 93.72MB read(30.06秒内共处理完成了90428个请求,读取了93.72MB数据)
Socket errors: connect 0, read 145, write 0, timeout 0
Requests/sec:   3008.58  (平均每秒处理完成3008.58个请求)
Transfer/sec:   3.12MB  (平均每秒读取数据3.12MB)

待续......