当前位置: 首页 > 工具软件 > koa-hwtm > 使用案例 >

koa2中使用jwt

姬烨磊
2023-12-01

随着技术的发展,分布式web应用的普及,通过session管理用户登录状态成本越来越高,因此慢慢发展成为token的方式做登录身份校验,然后通过token去取redis中的缓存的用户信息。随着之后jwt的出现,校验方式更加简单便捷化,无需通过redis缓存,而是直接根据token取出保存的用户信息,以及对token可用性校验,单点登录更为简单

JSON Web Token由三部分组成,它们之间用圆点(.)连接,header.payload.signature

header存token类型和签名算法
payload存携带的数据,比如用户信息{id:1,name:‘txj’…},其中一些是: iss(发行人)、 exp(到期时间)、 sub(主题)、 aud(受众)
signature存签名,默认使用HMACSHA256(base64UrlEncode(header) + “.” + base64UrlEncode(payload), secret)

1、创建

$ npm install jsonwebtoken --save

jsonwebtoken用于创建和验证token

使用默认的HMAC SHA256生成

var jwt = require('jsonwebtoken');
var token = jwt.sign({ foo: 'bar' }, 'secret_shhhhh');

设置过期时间(1h过期)

// 方式一
jwt.sign({
  exp: Math.floor(Date.now() / 1000) + (60 * 60),
  data: 'foobar'
}, 'secret');

// 方式二 推荐使用
jwt.sign({data: 'foobar'}, 'secret', { expiresIn: '1h' });
'1h’表示1小时过期
'7d’表示7天过期
数字1000表示1000毫秒过期

如果没有配置过期时间,则默认永不过期

eg:登录获取token

router.post('/token', async (ctx, next) => {
		
    let columnNames = ['admin_id','role_id','user_name','real_name','nick_name','portrait']
	
	let sql = `SELECT ${columnNames.join()} FROM admin WHERE user_name = ? AND password = ?`
	
	const md5Password = md5(password).toString()
	// 查不到数据时rows=[]
	const [rows,fields] = await mysqlPool.execute(sql, [userName,md5Password]);
	
	if(rows[0]){
		console.log('login success')
		// 创建token
			const token = jwt.sign(rows[0], 'secret', { expiresIn: '2h' });
			ctx.body = {success: true, data: token}
	}else{
		// 账号或密码错误
		console.log('login failed')
        ctx.body = {success: false, message: '账号或密码错误'}
	}
	});	


2、校验并解码

校验的同时会返回解码后的payload

// verify a token symmetric - synchronous
var decoded = jwt.verify(token, 'secret_shhhhh');
console.log(decoded) // { foo: 'bar', iat: 1644312929 }

// 如果想要把3段都输出来,即{ header, payload, signature }
var decoded1 = jwt.verify(token, 'secret_shhhhh', {complete: true});
/* {
  header: { alg: 'HS256', typ: 'JWT' },
  payload: { foo: 'bar', iat: 1644312990 },
  signature: 'hTf9bM3S4Y9XaoRRr9LVWecDnPHZ7n4Ma9CrnmjRqSE'
}*/

如果解码失败

// invalid token - synchronous
try {
  var decoded = jwt.verify(token, 'wrong-secret');
} catch(err) {
  	// err 
  	// 如果超时 err.message = 'jwt expired'
    // 如果token解析不了 err.message = 'invalid token'
    // 如果签名错误 err.message = 'invalid signature'
    // 其他错误查看GitHub
    // ctx.throw(401, 'token error');
}

github:https://github.com/auth0/node-jsonwebtoken

3、koa-jwt(jwt中间件、拦截器)

用于在所有请求的前置做token的校验,并自动解码token里的payload,然后放在ctx.state.user对象里。

当然也可以自己写个中间件,然后在中间件里使用jwt.verify来校验

npm install koa-jwt --save

GitHub:https://github.com/koajs/jwt

由于koa-jwt依赖了jsonwebtoken,所以安装了koa-jwt之后可以不用安装jsonwebtoken,可以直接var jwt = require(‘jsonwebtoken’);

eg:

var Koa = require('koa');
var jwt = require('koa-jwt');

var app = new Koa();
// 自定义401错误 start 【可以不要,必须写在前面;洋葱模型,发生错误后是不会继续往下执行的】
// 如果token没有经过验证中间件会返回401错误,可以通过下面的中间件自定义处理这个错误
// Custom 401 handling if you don't want to expose koa-jwt errors to users
app.use(function(ctx, next){
  return next().catch((err) => {
    if (401 == err.status) {
      ctx.status = 401;
      ctx.body = 'Protected resource, use Authorization header to get access\n';
      // ctx.body = {error: err.originalError ? err.originalError.message : err.message};
    } else {
      throw err;
    }
  });
});
// 自定义401错误 end

// Middleware below this line is only reached if JWT token is valid
// 登录注册接口不验证'/api/login','/api/register'
// 验证失败会返回401错误
app.use(jwt({ secret: 'my-secret' }).unless({ path: [/^\/api\/login/, /^\/api\/register/]}));

// 不验证token的中间件 测试1
app.use(function(ctx, next){
  if (ctx.url.match(/^\/api\/login/)) {
    ctx.body = 'unprotected\n';
  } else {
    return next();
  }
});

// 验证token的中间件 测试2
app.use(function(ctx){
  if (ctx.url.match(/^\/api/)) {
    ctx.body = 'protected\n';
  }
});

app.listen(3000);

注意鉴权代码的位置,鉴权代码应该写在注册路由之前,写在koa-static(用了的话)等中间件之后,否则,请求静态资源路径也被鉴权。

如果不校验token,始终放行,配置passthrough: true

// 不校验token是否有效
app.use(jwt({ secret: 'my-secret', passthrough: true }));

如果secret有多个

app.use(jwt({ secret: ['old-my-secret', 'new-my-secret'] }));

4、怎么使用

a.前端发送请求时,token需要放在header里,格式为’Bearer[空格]token‘
Headers:{Authorization:'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.payload.sign'}

'Bearer’前缀是jwt标准要求的,参见:https://jwt.io/introduction/

b.后台直接使用ctx.state.user就可以获取payload里的值了,假设payload={name:‘txj’}
// koa-jwt中间件校验token成功才会执行下面的方法
router.get('/detail/:code', async (ctx, next) => {
	console.log('user:', ctx.state.user)  // {name:'txj',iat: 1644314969}
})

如果token校验失败,直接返回401至前台

c.如果不希望把值存在user属性里,比如想存在jwtdata里
app.use(jwt({ secret: 'secret_shhhhh',key: 'jwtdata' }).unless({ path: [/^\/api\/login/, /^\/api\/register/]}));

后面使用ctx.state.jwtdata取payload里的值

5、相对完整的版本

app.js

const Koa = require('koa');
const bodyParser = require('koa-bodyparser');
const router = require('./index.js')
const jwt = require('koa-jwt');

const app = new Koa();

// 用于cookie签名
app.keys = ['SHjUcKJS9TqcPWGk'];

// logger
app.use(async (ctx, next) => {
	const start = new Date;
	await next();
	const ms = new Date - start;
	console.log(`${ctx.method} ${ctx.url} - ${ms}`);
});

// 配置koa-bodyparser模块解析body
app.use(bodyParser());

// 自定义401错误
// app.use(function(ctx, next){
//   return next().catch((err) => {
//     if (401 == err.status) {
//       ctx.status = 401;
//       // ctx.body = 'Protected resource, use Authorization header to get access\n';
//       ctx.body = {error: err.originalError ? err.originalError.message : err.message};
//     } else {
//       throw err;
//     }
//   });
// });


// jwttoken鉴权
// 登录注册接口不验证'/api/login','/api/register'
// 验证失败会返回401错误
app.use(jwt({ secret: 'secret_shhhhh' }).unless({ path: [/^\/api\/login/, /^\/api\/register/]}));

// 配置路由中间件
app
	.use(router.routes())
	.use(router.allowedMethods());;


// 全局监听异常信息
app.on('error', err => {
	console.error('server error', err)
});


app.listen(8910);
console.log('listening on port 8910');

index.js

const Router = require('@koa/router');
const imageTextRoutes = require('./imageText.js')
const indexService = require('../service/index.js')

const router = new Router({
	prefix: '/api'
});


// 指定一个url匹配
router.get('/indexData', async (ctx) => {
		// 这里可以直接获取token里的payload
		console.log('cccc:', ctx.state.user)
		// let body = ctx.request.body;
		const data = await indexService.getIndexData({
			current: ctx.query.current,
			size: ctx.query.size
		})
		ctx.body = {
			success: true,
			data: data
		}
	})

router.use('/article', imageTextRoutes.routes(), imageTextRoutes.allowedMethods());

module.exports = router
 类似资料: