背景
继续上篇
完成对云服务器的初步了解,以及nginx的配置,以及MySQL安装,建表操作以及使用Node.js框架Koa进行简单的数据增删改查功能。 但是针对一个完善一点网站,还需要一个权限认证(登入,登出,注册,api的身份验证),接下来就是Node写的jwt(jsonwebtoken) 搭建token身份验证模块。
目标功能
- 登录
- 注册
- 登出
- 所有API的身份验证
// loginconst User = require('../controller/user');router.post('/user/login', User.login);router.post('/user/register', User.register);router.get('/user/loginout', User.loginOut);复制代码
基于Token
一个等同于用户名和密码的,能够进行身份验证的令牌过程
```javascript
- 客户端使用用户名和密码请求登录
- 服务端收到请求后验证是否成功登录
- 成功:返回一个Token给客户端
- 失败:返回失败提示信息
- 客户端收到Token后存储Token
- 每次发起请求时将Token发给服务端
- 服务端收到请求后,验证Token的合法性
- 成功:返回客户端所需数据
- 失败:返回验证失败的信息
复制代码 ```流程图
CryptoJS前端加解密
crypto-js是一个纯 javascript 写的加密算法类库 ,可以非常方便地在 javascript 进行 MD5、SHA1、SHA2、SHA3、RIPEMD-160 哈希散列,进行 AES、DES、Rabbit、RC4、Triple DES 加解密。
这里是将前端传过来的密码加密与数据库存的密码作对照。当然也可以客户端使用相同的公钥加密,然后服务端使用公钥解密然后做对照是否一致。
安装crypto-js后,代码如下。 ```javascript const CryptoJS = require(‘crypto-js’); /**
- 加密 / function encrypt(word) { const key = CryptoJS.enc.Utf8.parse(‘yyq1234567890yyq’);//16位随机公钥 const srcs = CryptoJS.enc.Utf8.parse(word); const encrypted = CryptoJS.AES.encrypt(srcs, key, { mode: CryptoJS.mode.ECB, padding: CryptoJS.pad.Pkcs7 }); return encrypted.toString(); } /*
- 解密
*/
function decrypt(word) {
// 需要16位
const key = CryptoJS.enc.Utf8.parse(‘yyq1234567890yyq’);//16位随机公钥
const srcs = CryptoJS.enc.Utf8.stringify(word);
const decrypt = CryptoJS.AES.decrypt(srcs, key, {
mode: CryptoJS.mode.ECB,
padding: CryptoJS.pad.Pkcs7
});
console.log(decrypt);
return CryptoJS.enc.Utf8.stringify(decrypt).toString();
}
module.exports = {
encrypt,
decrypt
};
复制代码
openssl genrsa -out rsa_private_key.pem 1024 openssl rsa -in rsa_private_key.pem -pubout -out rsa_public_key.pem 复制代码<a name="OvfV3"></a>## JWT(Json Web Tokens)生成Token的解决方案有许多,但是我这里使用的是`JWT`。`Json web token (JWT)`,是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准。JWT是由三段信息构成的,将这三段信息文本用.链接一起就构成了Jwt字符串。就像这样:`XXX.XXX.XXXX` <a name="osiCe"></a>#### 利用OpenSSL生成私钥和公钥划重点,后面生成令牌`toekn`的时候,使用私钥加密以及后面验证的时候使用公钥解密获取用户信息
<a name="11UGd"></a>### 代码贴一下```javascript// 引入模块依赖const fs = require('fs');const path = require('path');//jsonwebtoken需要安装一下const jwt = require('jsonwebtoken');// 创建 token 类class Jwt {constructor(data) {//token需要带上的信息 例如:用户idthis.data = data;}// 生成tokengenerateToken() {const data = this.data;const created = Date.now();//私钥 加密const cert = fs.readFileSync(path.join(__dirname, '../pem/rsa_private_key.pem')); // 私钥 可以自己生成const token = jwt.sign({data,exp: created + 60 * 30 * 1000},cert,{ algorithm: 'RS256' });return token;}// 校验tokenverifyToken() {const token = this.data;const cert = fs.readFileSync(path.join(__dirname, '../pem/rsa_public_key.pem')); // 公钥 可以自己生成let res;try {//公钥 解密const result = jwt.verify(token, cert, { algorithms: ['RS256'] }) || {};const { exp = 0 } = result;const current = Date.now();//验证时效性if (current <= exp) {res = result.data || {};}} catch (e) {res = 'err';}return res;}}module.exports = Jwt;复制代码
所有API的身份验证
const passUrl = ['/user/login', '/user/register', '/404', '/user/loginout'];app.use(async(ctx, next) => {// 我这里知识把登陆和注册请求去掉了,其他的多有请求都需要进行token校验if (!~passUrl.findIndex(item => ctx.request.url === item)) {const token = ctx.headers.token;if (!token) {ctx.body = { status: 403, msg: 'token不能为空' };}const jwt = new JwtUtil(token);const result = jwt.verifyToken();// 如果考验通过就next,否则就返回登陆信息不正确if (result == 'err' || !result) {ctx.body = { status: 403, msg: '登录已过期,请重新登录' };return false;} else {// 可解析出用户idconsole.log(result);// 查询Id 再验证tokenconst res = await User.findOne({where: { id: Number(result) }});// Token不存在或者不一致if (res.token !== token || !res.token) {ctx.body = { status: 403, msg: '登录已过期,请重新登录' };return false;}}}await next();});复制代码
登录API
判断用户名和密码是否正确,然后生成并保存token,再返回给客户端// 登录const login = async ctx => {const bodyData = ctx.request.body || {};const userName = bodyData.userName;const passWord = bodyData.passWord;if (!userName || !passWord) {ctx.body = {code: 300,msg: '用户名密码不能为空!'};return false;}try {let result = await User.findAll({where: {userName: userName}});if (result.length) {result = result[0];console.log(result);// 利用aes 密码解密来判断密码是否正确const aes = encryptionAndDecryption.encrypt(passWord);if (result.passWord === aes) {// 登陆成功,添加token验证const _id = result.id.toString();// 将用户id传入并生成tokenconst jwt = new JwtUtil(_id);const token = jwt.generateToken();console.log('login id:' + _id);console.log('login token:' + token);// 将token存入 后面可以改成redis缓存const updateRes = await User.update({token},{where: {id: _id}});// 将 token 返回给客户端ctx.body = { status: 200, msg: '登陆成功', token: token };} else {ctx.body = { status: 500, msg: '账号密码错误' };return false;}} else {ctx.body = { status: 500, msg: '账号密码错误' };}} catch (error) {ctx.body = { status: 500, msg: error };}};复制代码
注册API
简单的注册,判断用户名是否存在,然后生成并保存token,再返回给客户端// 注册const register = async ctx => {const bodyData = ctx.request.body || {};const userName = bodyData.userName;const passWord = bodyData.passWord;if (!userName || !passWord) {ctx.body = {code: 300,msg: '用户名密码不能为空!'};return false;}try {const result = await User.findAll({where: {userName: userName}});if (result.length) {ctx.body = {code: 300,msg: '用户名已存在'};return false;}// 更新数据库const res = await User.create({userName,passWord: encryptionAndDecryption.encrypt(passWord)});// 登陆成功,添加token验证const _id = res.dataValues.id.toString();// 将用户id传入并生成tokenconst jwt = new JwtUtil(_id);const token = jwt.generateToken();// 将token存入await User.update({ token },{where: {id: _id}});ctx.body = {code: 100,data: '创建成功',token: token};} catch (err) {ctx.body = {code: 300,data: err};}};复制代码
登出API
清空服务端存储的token记录const loginOut = async ctx => {const jwt = new JwtUtil(ctx.headers.token);const result = jwt.verifyToken();// 将token存入const res = await User.update({ token: '' },{where: {id: result}});console.log(res);ctx.body = {code: 100,msg: '登出成功'};};复制代码
数据库User表结构
const sequelize = require('../utils/sequelize');const Sequelize = require('sequelize');const moment = require('moment');// 定义表结构const user = sequelize.define('user',{id: {type: Sequelize.INTEGER(11), // 设置字段类型primaryKey: true, // 设置为主键autoIncrement: true // 自增},userName: {type: Sequelize.STRING},passWord: {type: Sequelize.STRING},token: {type: Sequelize.TEXT,allowNull: true},createdAt: {type: Sequelize.DATE,defaultValue: Sequelize.NOW,get() {// this.getDataValue 获取当前字段valuereturn moment(this.getDataValue('createdAt')).format('YYYY-MM-DD HH:mm');}},updatedAt: {type: Sequelize.DATE,defaultValue: Sequelize.NOW,get() {return moment(this.getDataValue('updatedAt')).format('YYYY-MM-DD HH:mm');}}},{// sequelize会自动使用传入的模型名(define的第一个参数)的复数做为表名 设置true取消默认设置freezeTableName: true});module.exports = user;复制代码
多想一下
其实这里还可以加上菜单权限,在访问登录接口成功,生成token返回的同时,查询到权限列表返回给客户端。另外再编写一个\auth路由来获取权限列表。当然这个过程还是通过Token获取用户信息。//前后端约定一套权限码//服务端返回用户具有的权限CODE// 例如:['USER', //用户菜单'MY', // 我的菜单]//客户端获取之后通过服务端返回的权限码进行判断渲染对应的内容复制代码
关于这个项目
- 服务端收到请求后验证是否成功登录
在编写调试Node.js项目,修改代码后,需要频繁的手动close掉,然后再重新启动,非常不方便。所以推荐使用
Node自动重启工具 nodemon//全局安装一下npm install -g nodemon//使用方法//app.js入口文件 8181//端号,也可以不带nodemon app.js 8181复制代码
还有就是ES6语法和代码格式化,这里不再赘述。安装
babel和eslint即可。最后
这里主要记录代码过程,有些概念没有细说,参考资料如下:JWT , crypto-js

附上代码地址[https://gitee.com/wisdom_QQ/koa](https://gitee.com/wisdom_QQ/koa)
看完了,请点赞!!!
看完了,请点赞!!!
看完了,请点赞!!!
