赞
踩
一、node.js
1、nodejs中间件
1】洋葱模型
洋葱模型顾名思义,指的是方法的执行像洋葱一样,每一层有3个参数(request、response和next)作为回调函数,由第3个参数回调函数next来驱动,一层一层往里执行,直到中心点后,再一层一层往外出来。
2】中间件是从http请求发起到响应结束过程中的处理方法,通常需要对请求和响应进行处理,如记录日志、ip过滤、查询字符串、请求体解析、Cookie处理、权限验证、参数验证、异常处理等,因此一个基本的中间件的形式如下:
const middleware = (req, res, next) => {
// TODO
next()
}
***中间件的原理:
最关键的就是中间件里面的next(),next函数实际上就是将其返回值用Promise包裹住,使其在业务处理完成后,通过Promise的then回调来继续处理中间件逻辑。当然也可以用async和await实现, 写法会更优雅和简单。
3】常用的一些插件
body-parser:HTTP请求体解析的中间件,使用这个模块可以解析JSON、Raw、文本、URL-encoded格式的请求体。
moment mysql node-xlsx request socket.io xml2js
4】如何和MySQL进行通信?
使用mysql模块,配置好相应的ip账号密码数据库名
mysql池需要设置连接次数(connectionLimit)和连接超时时间(connectTimeout)
2、有没有涉及到Cluster?(单核变多核)
1)理论
1】nodejs是单进程单线程的,而cluster是一个nodejs内置的模块,用于nodejs多核处理,轻松构建一个用于负载均衡的集群。
2】master是总控节点,worker是运行节点。然后根据CPU的核数,启动worker。我本地是双核双通道的CPU,所以被检测为4核,启动了4个worker。
3】每个worker进程是master通过使用child_process.fork()函数,基于IPC(Inter-Process Communication,进程间通信),实现与master进程间通信。
当worker使用server.listen(…)函数时 ,将参数序列传递给master进程。如果master进程已经匹配workers,会将传递句柄给工人。如果master没有匹配好worker,那么会创建一个worker,再传递并句柄传递给worker。
2】实际应用
在实际应用中,更多会用到pm2,因为pm2就是对cluster的进一步封装。
把应用部署到服务器所有的CPU上:pm2 start app.js -i max
比如服务器的CPU有8个核,那以上的命令就起了8个node进程
child_process模块,来实现进程的复制
问题:
这些进程间如何通信?
通过child_process 的 spawn() 方法的 stdio 选项可以建立IPC机制(Inter-Process Communication)
on监听消息 send发送消息
3、请简述一下node的多进程架构
面对node单线程对多核CPU使用不足的情况,Node提供了child_process模块,来实现进程的复制,node的多进程架构是主从模式,如下所示:
4、创建子进程的方法大致有:
spawn():启动一个子进程来执行命令
exec(): 启动一个子进程来执行命令,与spawn()不同的是其接口不同,它有一个回调函数获知子进程的状况
execFlie(): 启动一个子进程来执行可执行文件
fork(): 与spawn()类似,不同电在于它创建Node子进程需要执行js文件
spawn()与exec()、execFile()不同的是,后两者创建时可以指定timeout属性设置超时时间,一旦创建的进程超过设定的时间就会被杀死
exec()与execFile()不同的是,exec()适合执行已有命令,execFile()适合执行文件。
5、请问实现一个node子进程被杀死,然后自动重启代码的思路(递归)
在创建子进程的时候就让子进程监听exit事件,如果被杀死就重新fork一下
在上面基础上,实现限量重启,比如最多让其在1分钟内重启5次,超过了就报警给运维
思路大概是在创建worker的时候,就判断创建的这个worker是否在1分钟内重启次数超过5次
所以每一次创建worker的时候都要记录这个worker 创建时间,放入一个数组队列里面,每次创建worker都去取队列里前5条记录
如果这5条记录的时间间隔小于1分钟,就说明到了报警的时候了
(或者直接用pm2,更省事~)
6、pm2怎么做进程管理,进程挂掉怎么处理
pm2 基于 cluster模块 进行了封装
1】pm2特点
支持进程行为配置(可以给进程起名);支持集群模式;支持热重启;支持日志管理
同时,pm2还有可视化的监控界面。
比起forever,pm2清晰地看见整个集群的模式、状态,CPU利用率甚至是内存大小。
2】pm2启动命令
pm2 start app.js --name xxx
3】进程挂掉怎么处理
若检测到node相关进程挂了,pm2会立即将其重启
使用fork()创建子进程,子进程用于执行具体功能,主进程只是用于监控子进程,当主进程检测到子进程挂掉后,可以实现立即重新启动子进程
4】不用pm2怎么做进程管理
a、使用forever模块
b、使用supervisor模块
c、./node app.js & 运行于后台,然后在服务器上面写个shell脚本,定时去监听这个进程是否存活,如果不存活,则发送邮件报警并将其重启
7、nodejs服务如何热重启?(fs.watch)
1】用pm2
pm2 start app.js --watch
上述命令可以检测文件的改变,然后重新启动NodeJs服务
2】原理
通过监视文件被改动的时候,将缓冲区中已加载的对应模块清除,此时缓冲区中就不保留有该文件模块的代码,直至下一个请求该文件模块到来时,才会去重新加载一遍对应的模块,而正是改动之后的文件模块
Module._load = function(request, parent, isMain) {
var filename = Module._resolveFilename(request, parent);
var cachedModule = Module._cache[filename];
if (cachedModule) {
return cachedModule.exports;
}
var module = new Module(filename, parent);
Module._cache[filename] = module;
module.load(filename);
return module.exports;
};
require.cache = Module._cache;
app.use(‘/’, function(req, res, next){ //这里的路由要这样写才能使得清除缓存后的下一次请求能重新加载模块
routes(req, res, next);
});
fs.watch(require.resolve(‘./routes/index’), function () {
cleanCache(require.resolve(‘./routes/index’)); //清除该路径模块的缓存
try {
routes = require(‘./routes/index’);
} catch (ex) {
console.error(‘module update failed’);
}
});
function cleanCache(modulePath) {
var module = require.cache[modulePath];
// remove reference in module.parent
if (module.parent) {
module.parent.children.splice(module.parent.children.indexOf(module), 1); //释放老模块的资源
}
require.cache[modulePath] = null; //缓存置空
}
8、服务端渲染SSR?(express下面有res.render)
1】什么是服务端渲染(什么是ssr:server side render)
将组件或页面通过服务器生成html字符串,再发送到浏览器之后结合css显示出来
2】ssr的好处
a、利于seo
相对于MVVM框架的页面dom一般都是js生成的,爬虫一般不会等待js生成之后再爬取;而ssr可以渲染出来的dom,就可以提供给爬虫爬取
b、减少白屏时间
普通客户端用JS渲染加载所有所需文件时间较长,首页就会有一个很长的白屏等待时间;而ssr提供一个直接拼接好的dom字符串,加快渲染速度
3】缺点
对服务器压力大;对带宽要求高
9、nodejs如何解决跨域的问题?
1】使用CORS跨域模块
2】通用
app.all(‘', function(req, res, next) {
//设为指定的域
res.header(‘Access-Control-Allow-Origin’, "");
res.header(“Access-Control-Allow-Headers”, “X-Requested-With”);
res.header(‘Access-Control-Allow-Headers’, ‘Content-Type’);
res.header(“Access-Control-Allow-Methods”, “PUT,POST,GET,DELETE,OPTIONS”);
res.header(‘Access-Control-Allow-Credentials’, true);
res.header(“X-Powered-By”, ’ 3.2.1’);
next();
});
10、文件上传如何做断点续传和下载如何暂停
1】用到node的request 与 fs 模块
重要参数:http的header参数里面的Range 决定下载区间
若需要继续上传或暂停下载操作,可通过手动结束request请求 req.abort() 然后需要记住当前已下载的字节receivedBytes也就是range参数里的receivedBytes
继续下载时重新发起request请求,然后将上次已经接收的总文件字节传入到range 这样就实现了下载暂停功能
2】用 H5 的 File api
先得获得一个文件ID 文件HASH值的思路就是:MD5(文件名称+文件长度+文件修改时间+自定义的浏览器ID)
在上传文件之前,从服务端查询文件的断点续传信息, 决定从什么位置上传数据
关键技术点是:从上次上传的长度位置上传
var blob = fileObj.slice(start_offset,filesize)
function upload_file(fileObj, start_offset, fileid){}
3】另一种解决方案
前端
前端大文件上传网上的大部分文章已经给出了解决方案,核心是利用 Blob.prototype.slice 方法,和数组的 slice 方法相似,调用的 slice 方法可以返回原文件的某个切片
这样我们就可以根据预先设置好的切片最大数量将文件切分为一个个切片,然后借助 http 的可并发性,同时上传多个切片,这样从原本传一个大文件,变成了同时传多个小的文件切片,可以大大减少上传时间
另外由于是并发,传输到服务端的顺序可能会发生变化,所以我们还需要给每个切片记录顺序
服务端
服务端需要负责接受这些切片,并在接收到所有切片后合并切片
这里又引伸出两个问题
何时合并切片,即切片什么时候传输完成
如何合并切片
第一个问题需要前端进行配合,前端在每个切片中都携带切片最大数量的信息,当服务端接受到这个数量的切片时自动合并,也可以额外发一个请求主动通知服务端进行切片的合并
第二个问题,具体如何合并切片呢?这里可以使用 nodejs 的 读写流(readStream/writeStream),将所有切片的流传输到最终文件的流里
11、nodejs做中间层的好处
1】MVC时代的痛点
a、前后端职责出现重叠
前端写页面要考虑使用后端的模板语法;后端套用前端的静态页面,依旧要进行二次修改
b、性能问题
渲染,取值都在客户端进行,有性能的问题
c、重用问题
模版无法重用,造成维护上的麻烦与不一致
逻辑无法重用,前端的校验后端仍须在做一次
路由无法重用,前端的路由在后端未必存在
d、跨终端问题
业务太靠前,导致不同端重复实现
逻辑太靠前,造成维护上的不易
2】nodejs中间层
NodeJS作为中间层的全栈开发方案
有了NodeJS之后,前端可以更加专注于视图层,而让更多的数据逻辑放在Node层处理。
a、转发数据,串接服务
b、路由设计,控制逻辑
c、渲染页面,体验优化
d、中间层带来的性能问题,在异步ajax转成同步渲染过程中得到平衡
前端中间层后端浏览器服务器服务器HTML+CSS+JavaScriptNode.jsPHP(或其他各种各样的后端语言)跑在浏览器上的JS跑在服务器上的JS服务层CSS、JS加载运行转发数据,串接服务提供数据接口DOM操作路由设计,控制逻辑维持数据稳定公用模板、路由渲染页面,体验优化封装业务逻辑
12、nodejs文件查找优先级(文件查找机制)
require方法中的文件查找策略
由于Node.js中存在4类模块(原生模块和3种文件模块),尽管require方法极其简单,但是内部的加载却是十分复杂的,其加载优先级也各自不同。
简而言之,如果require绝对路径的文件,查找时不会去遍历每一个node_modules目录,其速度最快。其余流程如下:
1】从module path数组中取出第一个目录作为查找基准。
2】直接从目录中查找该文件,如果存在,则结束查找。如果不存在,则进行下一条查找。
3】尝试添加.js、.json、.node后缀后查找,若存在文件,则结束查找。若不存在,则进行下一条。
4】尝试将require的参数作为一个包来进行查找,读取目录下的package.json文件,取得main参数指定的文件。
5】尝试查找该文件,如果存在,则结束查找。如果不存在,则进行第3条查找。
6】如果继续失败,则取出module path数组中的下一个目录作为基准查找,循环第1至5个步骤。
7】如果继续失败,循环第1至6个步骤,直到module path中的最后一个值。
8】如果仍然失败,则抛出异常。
13、nodejs项目如何管理模块
npm则是包含在node.js里面的一个包管理工具,就如同linux中的yum仓库,rpm包管理;如同python中的pip包管理工具一样。而这些包管理工具都是予以使用的人们方便,同时解决各种包依赖之间的关系的。 等下面演示后,就会知道有npm去解决项目及包之间的依赖关系是多么的便利,省去了人手上的多少心力。让开发人员专注于代码上。
既然npm是包管理工具,那么它自己也和node.js分开自成一个网站,在npm的网站上面,就如同github,其仓库中保管了N多的开源项目,有世界上众多开发者提供的项目。我们只需要在npm的网站上搜索相关的就可以找到,然后在线上下载也行,直接在自己的项目中使用命令行安装也行。
npm 由三个独立的部分组成:
npm官方网站(仓库源)
注册表(registry)(package.json)
命令行工具 (CLI)
14、请介绍一下node里的模块是什么
Node中,每个文件模块都是一个对象,它的定义如下:
所有的模块都是 Module 的实例。可以看到,当前模块(module.js)也是 Module 的一个实例。
请介绍一下require的模块加载机制
这道题基本上就可以了解到面试者对Node模块机制的了解程度 基本上面试提到
1、先计算模块路径
2、如果模块在缓存里面,取出缓存
3、加载模块
4、输出模块的exports属性即可
// require 其实内部调用 Module._load 方法
Module._load = function(request, parent, isMain) {
// 计算绝对路径
var filename = Module._resolveFilename(request, parent);
// 第一步:如果有缓存,取出缓存
var cachedModule = Module._cache[filename];
if (cachedModule) {
return cachedModule.exports;
// 第二步:是否为内置模块
if (NativeModule.exists(filename)) {
return NativeModule.require(filename);
}
/******这里注意了/
// 第三步:生成模块实例,存入缓存
// 这里的Module就是我们上面的1.1定义的Module
var module = new Module(filename, parent);
Module._cache[filename] = module;
/******这里注意了/
// 第四步:加载模块
// 下面的module.load实际上是Module原型上有一个方法叫Module.prototype.load
try {
module.load(filename);
hadException = false;
} finally {
if (hadException) {
delete Module._cache[filename];
}
}
// 第五步:输出模块的exports属性
return module.exports;
};
接着上一题继续发问
加载模块时,为什么每个模块都有__dirname,__filename属性呢,new Module的时候我们看到1.1部分没有这两个属性的,那么这两个属性是从哪里来的
也就是说,每个module里面都会传入__filename, __dirname参数,这两个参数并不是module本身就有的,是外界传入的
我们知道node导出模块有两种方式,一种是exports.xxx=xxx和Module.exports={}有什么区别吗
exports其实就是module.exports
其实1.3问题的代码已经说明问题了,接着我引用廖雪峰大神的讲解,希望能讲的更清楚
module.exports vs exports
很多时候,你会看到,在Node环境中,有两种方法可以在一个模块中输出变量:
方法一:对module.exports赋值:
// hello.js
function hello() {
console.log(‘Hello, world!’);
}
function greet(name) {
console.log('Hello, ’ + name + ‘!’);
}
module.exports = {
hello: hello,
greet: greet
};
方法二:直接使用exports:
// hello.js
function hello() {
console.log(‘Hello, world!’);
}
function greet(name) {
console.log('Hello, ’ + name + ‘!’);
}
function hello() {
console.log(‘Hello, world!’);
}
exports.hello = hello;
exports.greet = greet;
但是你不可以直接对exports赋值:
// 代码可以执行,但是模块并没有输出任何变量:
exports = {
hello: hello,
greet: greet
};
如果你对上面的写法感到十分困惑,不要着急,我们来分析Node的加载机制:
首先,Node会把整个待加载的hello.js文件放入一个包装函数load中执行。在执行这个load()函数前,Node准备好了module变量:
var module = {
id: ‘hello’,
exports: {}
};
load()函数最终返回module.exports:
var load = function (exports, module) {
// hello.js的文件内容
…
// load函数返回:
return module.exports;
};
var exported = load(module.exports, module);
也就是说,默认情况下,Node准备的exports变量和module.exports变量实际上是同一个变量,并且初始化为空对象{},于是,我们可以写:
exports.foo = function () { return ‘foo’; };
exports.bar = function () { return ‘bar’; };
也可以写:
module.exports.foo = function () { return ‘foo’; };
module.exports.bar = function () { return ‘bar’; };
换句话说,Node默认给你准备了一个空对象{},这样你可以直接往里面加东西。
但是,如果我们要输出的是一个函数或数组,那么,只能给module.exports赋值:
module.exports = function () { return ‘foo’; };
给exports赋值是无效的,因为赋值后,module.exports仍然是空对象{}。
结论
如果要输出一个键值对象{},可以利用exports这个已存在的空对象{},并继续在上面添加新的键值;
如果要输出一个函数或数组,必须直接对module.exports对象赋值。
所以我们可以得出结论:直接对module.exports赋值,可以应对任何情况:
module.exports = {
foo: function () { return ‘foo’; }
};
或者:
module.exports = function () { return ‘foo’; };
最终,我们强烈建议使用module.exports = xxx的方式来输出模块变量,这样,你只需要记忆一种方法。
15、module.exports与exports,export与export default的区别
Node使用CommonJS规范,定义每个模块的内部,module变量代表当前模块,exports是module的属性,表示对外的接口。加载某个模块,实际上是加载该模块的module.exports属性。
Node为每隔模块提供了一个exports变量,指向module.exports,这等同于每个模块头部有这样的一行代码:var exports = module.exports
ES6使用export和import来导出/导入模块。
1】export与export default均可用于导出常量/函数/文件/模块等;
2】在一个文件或模块中,export/import可以有多个,export default只有一个;
3】通过export方式导出,在导入时需要加{},export default不需要;
4】export能导出变量/表达式,export default不可以。
CommonJS模块输出是一个值的拷贝,ES6模块输出是值的引用。
CommonJS模块是运行时同步加载,ES6模块是编译时输出接口。
CommonJS模块无论require多少次,都只会在第一次加载时全部运行一次,然后保存到缓存中,下次在require,会优先从缓存里面取。
总的来的:
module.exports与exports ,是CommonJS规范,被使用于Node.js中。export与export default 是ES6规范,被使用于React或Vue中
16、 js 的几种模块规范?
1】CommonJS 方案
它通过 require 来引入模块,通过 module.exports 定义模块的输出接口。这种模块加载方案是服务器端的解决方案,它是以同步的方式来引入模块的,因为在服务端文件都存储在本地磁盘,所以读取非常快,所以以同步的方式加载没有问题。但如果是在浏览器端,由于模块的加载是使用网络请求,因此使用异步加载的方式更加合适。
2】AMD 方案
这种方案采用异步加载的方式来加载模块,模块的加载不影响后面语句的执行,所有依赖这个模块的语句都定义在一个回调函数里,等到加载完成后再执行回调函数。require.js 实现了 AMD 规范。
3】CMD 方案
这种方案和 AMD 方案都是为了解决异步模块加载的问题,sea.js 实现了 CMD 规范。它和require.js的区别在于模块定义时对依赖的处理不同和对依赖模块的执行时机的处理不同。
4】ES6 提出的方案
使用 import 和 export 的形式来导入导出模块。
17、V8的内存限制是多少,为什么V8这样设计
64位系统下是1.4GB, 32位系统下是0.7GB。
因为1.5GB的垃圾回收堆内存,V8需要花费50毫秒以上,做一次非增量式的垃圾回收甚至要1秒以上。这是垃圾回收中引起Javascript线程暂停执行的事件,在这样的花销下,应用的性能和影响力都会直线下降。
18、node.js的事件循环
node虽然是单进程单线程的设计,但它也能实现高并发。原因在于它的主线程事件循环机制和底层线程池的实现。
1】定义
Node只运行在一个单一线程上,至少从Node.js开发者的角度是这样的。在底层, Node是通过libuv来实现多线程的。
Libuv库负责Node API的执行。它将不同的任务分配给不同的线程,形成一个事件循环, 以异步的方式将任务的执行结果返回给V8引擎。
2】过程
每个框将被称为事件循环的 “阶段”;当我们在事件循环的某个阶段执行某个回调时候,该回调可能会生成更多的回调。这些回调都是被添加到对应阶段的队列中。因此,长时间运行的回调可以允许轮询阶段的运行时间远远超过计时器的阀值。
a、timer:这个阶段执行通过setTimeout()和setInterval()设置的回调函数
b、I/O callback:执行延迟到下一个循环迭代的I/O回调
c、idle,prepare:系统调用,也就是libuv调用
d、poll:轮询阶段,检测新的I/O事件,执行与I/O相关的回调,(几乎所有的回调都是关闭回调,定时器调度的回调,以及setImmaditate()),node会在此阶段适当的阻塞
e、check:此阶段调用setImmadiate()设置的回调
f、close callbacks:一些关闭回调,比如说socket.on(‘close’,…)
3】node.js 与浏览器的 Event Loop 差异
浏览器环境下,微任务(包括嵌套的微任务)的任务队列,是每个宏任务执行完之后执行。
而在NodeJS事件环(Event Loop)中,只有同步代码执行完毕和其它队列之间切换的时候,才会去清空微任务队列。
4】当你想在node中执行异步代码,并且想尽可能快的执行,那么可以选择node中的setImmediate()函数。
setImmediate(() => {
//run something
})
19、nodejs如何实现集群
1)负载均衡
主要通过Nginx实现反向代理和负载均衡
1】将同一nodejs后端服务,部署在不同的服务器上,但连接的是同一数据库。
2】Nginx通过配置upstream绑定以上几台服务器的ip,在最前方配置location进行反向代理。
最开始的客户端请求,先直接请求到Nginx上面。
然后Nginx将不同用户的请求,按照指定的轮询方式,转发给后方不同的服务器。
2)高可用
利用pm2监控node进程,如果进程挂掉,利用pm2 API 操作事先写好的shell脚本,将挂掉的服务器ip从nginx的配置里面剔除(反之,进程恢复的话,就重新加上)。同时pm2会不断尝试去重启挂掉的进程。
二、后端
1、进程、线程和死锁
1】线程:线程是进程中的一个实体,是CPU调度的最小单位
2】进程:执行运算和CPU分配资源的最小单位
3】每个进程中至少包含一个线程,而这些线程都在共享进程的资源空间等,当线程发生变化的时候只会引起CPU执行的过程发生变化,不会改变进程所拥有的资源
4】死锁:两个或两个以上的进程在执行过程中,由于竞争资源而造成的一种阻塞。
2、前后端通信使用什么方案
1】http 和 websocket
2】webSocket与传统的http有什么优势
客户端与服务器只需要一个TCP连接,比http长轮询使用更少的连接
webSocket服务端可以推送数据到客户端
更轻量的协议头,减少数据传输量
3、解释一下restful架构(restful API)
如果一个架构符合REST原则,就称它为RESTful架构
restful API: 采用RESTful架构的后端API服务
1】rest原则
a、网络上的所有事物都被抽象为资源
b、每个资源都有一个唯一的资源标识符
c、同一个资源具有多种表现形式(xml,json等)
d、对资源的各种操作不会改变资源标识符(URI)
e、所有的操作都是无状态的
2】restful 本质上是使用 URL 来访问资源的一种方式
什么是RESTful架构:
(1)每一个URI代表一种资源;
(2)客户端和服务器之间,传递这种资源的某种表现层;
(3)客户端通过四个HTTP动词,对服务器端资源进行操作,实现"表现层状态转化"
3】资源(Resources)
REST的名称"表现层状态转化"中,省略了主语。“表现层"其实指的是"资源”(Resources)的"表现层"。
所谓"资源",就是网络上的一个实体,或者说是网络上的一个具体信息。它可以是一段文本、一张图片、一首歌曲、一种服务,总之就是一个具体的实在。你可以用一个URI(统一资源定位符)指向它,每种资源对应一个特定的URI。
4】请求方式
URL 就是我们平常使用的请求地址了,其中包括两部分:请求方式 与 请求路径
比较常见的请求方式是 GET 与 POST,但在 REST 中又提出了几种其它类型的请求方式,汇总起来有六种:GET、POST、PUT、DELETE、HEAD、OPTIONS。
尤其是前四种,正好与CRUD(Create-Retrieve-Update-Delete,增删改查)四种操作相对应,例如,GET(查)、POST(增)、PUT(改)、DELETE(删),这正是 REST 与 CRUD 的异曲同工之妙!需要强调的是,REST 是“面向资源”的,这里提到的资源,实际上就是我们常说的领域对象,在系统设计过程中,我们经常通过领域对象来进行数据建模。
5】无状态
REST 是一个“无状态”的架构模式,因为在任何时候都可以由任意的客户端发出请求到服务端,最终返回自己想要的数据,当前请求不会受到上次请求的影响。
4、xml 和 json的区别,请用四个词语来形容
· JSON相对于XML来讲,数据的体积小,传递的速度更快些
· JSON与JavaScript的交互更加方便,更容易解析处理,更好的数据交互
· XML对数据描述性比较好;
· JSON的速度要远远快于XML
5、纯后端的一个项目经验
低代码平台开发
6、jwt (上面也有介绍)
全称JSON WEB TOKEN
1】起源
a、cookie
缺陷:存在浏览器,不安全
b、服务端session
缺陷:session都是保存在内存中,而随着认证用户的增多,服务端的开销会明显增大
2】构成
头部(header) + 载荷(payload)+ 签证(signature)
a、header:一般放加密算法,通常直接使用 HMAC SHA256,需要经过base64加密
b、playload:有效的信息,比如 jwt签发者、面向的用户等一些信息,需要经过base64加密
c、signature
这个签证信息由三部分组成:
header (base64后的) + ‘.’ + payload (base64后的) 之后得到的字符,再利用secret 加密
3】应用
一般是在请求头里加入Authorization,并加上Bearer标注:
fetch(‘api/user/1’, {
headers: {
‘Authorization’: 'Bearer ’ + token
}
})
只要secret私钥 不泄露出去,一般都是比较安全的。
4】动态token
简单来说,就是token的时效性。如果一个token永久有效,时间持续越长这个token泄露的风险就越高。此外,还可以加入用户id放入token里面进行加密,这样可以用于一些身份认证操作。
解决:加入时间戳,后端接收到数据之后,利用解密得出来的时间戳和当前的时间进行对比即可。
5】安全的RESTful API
jwt认证机制 + https + 动态token
7、如何防止API被重复调用
1】给数据库增加唯一键约束
语法:add unique key
在数据库建表的时候在ID字段添加主键约束,用户名、邮箱、电话等字段加唯一性约束。确保数据库只可以添加一条数据。
注意:这样只能有效避免数据库重复插入相同数据,却不能防止重复提交表单。
2】判断token是否相同
混入时间戳(毫秒级别),如果大量的API请求的token都是一样的,则将其拒绝。
3】redis存储每次请求的key
混入时间戳,原理同上;或者使用唯一的不会重复的字符串作为唯一码。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。