Json web token (JWT), 是爲了在網絡應用環境間傳遞聲明而執行的一種基於JSON的開放標準([(RFC 7519]).該token被設計爲緊湊且安全的,特別適用於分佈式站點的單點登陸(SSO)場景。JWT的聲明通常被用來在身份提供者和服務提供者間傳遞被認證的用戶身份信息,以便於從資源服務器獲取資源,也能夠增長一些額外的其它業務邏輯所必須的聲明信息,該token也可直接被用於認證,也可被加密。web
JWT認證流程npm
基於session和基於JWT的方式的主要區別就是用戶的狀態保存的位置,session是保存在服務端的,而JWT是保存在客戶端的。api
一旦簽發一個JWT,在到期以前就會始終有效,沒法中途廢棄。安全
# jwt
npm i egg-jwt
# 密碼加密
bcryptjs
# 數據校驗
egg-validate
複製代碼
config/plugin.jsbash
jwt: {
enable: true,
package: 'egg-jwt',
},
validate: {
enable: true,
package: 'egg-validate',
},
複製代碼
config/config.default.js服務器
// jwt
config.jwt = {
secret: '123456',
expiresIn: '24h',
};
// 參數
config.validate = {
enable: true,
package: 'egg-validate',
};
// 密碼加密
config.bcrypt = {
saltRounds: 10,
};
複製代碼
app/model/user.jsmarkdown
'use strict';
module.exports = app => {
const { STRING, INTEGER, DECIMAL, DATE } = app.Sequelize;
const User = app.model.define('users', {
id: { type: INTEGER, primaryKey: true, autoIncrement: true },
userName: {
type: STRING,
allowNull: false,
unique: true,
comment: '用戶名,惟一',
},
passWord: STRING,
});
return User;
};
複製代碼
app/controller/user.js網絡
// 校驗用戶註冊參數
const vUser = {
userName: { type: 'string', required: true },
passWord: { type: 'string', required: true },
};
//...
class UserController extends Controller {
// 用戶註冊
async rigist() {
const { ctx } = this;
// 接收並校驗參數
ctx.validate(vUser, ctx.request.body);
// 判斷用戶名是否重複
const users = await ctx.service.user.checkUserName(ctx.request.body);
if (users[0]) {
ctx.body = { status: false, msg: '用戶名已存在', data: users };
return;
}
await ctx.service.user.Rigist(ctx.request.body);
ctx.body = { status: true, msg: '註冊成功' };
}
// 用戶登錄
async login() {
const { ctx } = this;
// 接收並校驗參數
ctx.validate(vUser, ctx.request.body);
const data = await ctx.service.user.Login(ctx.request.body);
if (!data) {
ctx.status = 401;
ctx.body = { status: false, msg: '用戶名或密碼錯誤' };
return;
}
ctx.body = { status: true, msg: '登錄成功', data };
}
}
module.exports = UserController;
複製代碼
app/service/user.jssession
const bcrypt = require('bcryptjs');
// ......
// 檢查用戶名
async checkUserName(query) {
const { userName } = query;
const users = await this.ctx.model.User.findAll({
attributes: [ 'userName' ],
where: { userName },
});
return users;
}
// 用戶註冊
async Rigist(body) {
const { userName, passWord } = body;
// 對密碼加密
const hash = bcrypt.hashSync(passWord, this.config.bcrypt.saltRounds);
const user = await this.ctx.model.User.create({ userName, passWord: hash });
return user;
}
// 用戶登錄
async Login(body) {
const { userName, passWord } = body;
const user = await this.ctx.model.User.findOne({
where: { userName },
});
if (!user) return {};
const match = await bcrypt.compare(passWord, user.passWord);
if (match) {
const { id, userName } = user;
// 獲取jwt配置
const { jwt: { secret, expiresIn } } = this.app.config;
// 生成token
const token = this.app.jwt.sign({
id, userName,
}, secret, { expiresIn });
return { userName, token };
}
}
複製代碼
app/router.jsapp
router.post('/api/v1/user/rigist', controller.user.rigist); // 用戶註冊
router.post('/api/v1/user/login', controller.user.login); // 用戶登錄
// 在路由裏添加jwt中間件 便可使用jwt鑑權
router.put('/api/v1/user/:id', app.jwt, controller.user.update); // 修改用戶信息
複製代碼
若是接口使用了app.jwt 中間件
const userToken = this.ctx.state.user;
// 可獲取登錄時寫入token的內容 id, userName,
複製代碼
若是接口未使用app.jwt中間件 則沒法獲取 this.ctx.state.user;
對與某些接口可登錄也可不登錄,且獲取數據有差別時。
此時須要封裝解析token的方法
app/extend/utils.js
'use strict';
function getTokenInfo(jwt, auth, secret) {
// 判斷請求頭是否包含token
if (
auth.authorization &&
auth.authorization.split(' ')[0] === 'Bearer'
) {
const token = auth.authorization.split(' ')[1];
let decode = '';
if (token) {
decode = jwt.verify(token, secret);
}
return decode;
}
return;
}
module.exports = {
getTokenInfo,
};
複製代碼
在service裏使用
const { jwt: { secret } } = this.app.config;
// 若是存在token 解析token
const authInfo = getTokenInfo(this.ctx.app.jwt, this.ctx.headers, secret);
if (authInfo) {
// 便可獲取 token 內容
// authInfo.id authInfo.userName
}
複製代碼