express.js学习

157 阅读22分钟

express.js

初始express

官方给出的概念:Express是基于node.js平台,快速、开放、极简的web开发框架 通俗的理解: Express的作用和node.js内置的http模块类似,专门用来创建web服务器的 本质: 就是一个npm上的第三方包,提供了快速创建web服务器的便捷方法

Express的中文官网

Express能做什么

对于前端程序员来说,最常用的两种服务器,分别是:

  1. web网站服务器:专门对外提供web网页资源的服务器
  2. API接口服务器:专门对外提供API接口的服务器
    使用Express,我们可以方便,快速的创建web网站服务器或API接口的服务器

express基本使用

创建基本的web服务器

//导入express模块
const express = require('express');
//创建web服务器
const app = express()
//调用app.listen(端口号,启动成功后的回调函数),启动服务器
app.listen(80,()=>{
    ...
})

监听get请求

语法格式

//参数1:客户端请求的url地址
//参数2:请求对应的处理函数
//    req:请求对象(包含了与请求相关的属性和方法)
//    res:响应对象(包含了与响应相关的属性和方法)
app.get('请求url',function(req,res){/*处理函数*/})

监听post请求

语法格式

//参数1:客户端请求的url地址
//参数2:请求对应的处理函数
//    req:请求对象(包含了与请求相关的属性和方法)
//    res:响应对象(包含了与响应相关的属性和方法)
app.post('请求url',function(req,res){/*处理函数*/})

把内容响应给客户端

通过res.send()方法:

app.get('/user',(req,res)=>{
    //向客户端发送json对象
    res.send({name:'zs',age:20,gender:'男'})
})
app.get('/user',(req,res)=>{
    //向客户端发送文本内容
    res.send('请求成功')
})

获取url中携带的查询参数

通过req.query对象,可以访问到客户端通过查询字符串的形式,发送到服务器的参数:

app.get('/',(req,res)=>{
//req.query默认是一个空对象
//客户端使用?name=zs&age=20这种查询字符串形式,发送到服务器的参数
//可以通过req.query对象访问到,例如:
//req.query.name req.query.age
console.log(req.query);
})

获取url中的动态参数

通过req.params对象,可以访问到url中,通过:匹配到的动态参数:

//url地址中,可以通过:参数名的形式,匹配动态参数值
app.get('/user/:id/:username',(req.res)=>{
//req.params默认是一个空对象
//里面存放着通过:动态匹配到的参数值
console.log(req.params);
})

托管静态资源

express.static()

express提供了一个非常好用的函数,叫做express.static(),通过它,我们可以非常方便地创建一个静态资源服务器 例如:通过如下代码就可以将public目录下的图片,css文件,js文件对外开发访问了:

app.use(express.static('public'))

image.png 注意:express在指定的静态目录中查找文件,并对外提供资源的访问路径。因此,存放静态文件的目录名不会出现在url中

托管多个静态资源目录

如果托管多个静态资源目录,请多次调用express.static()函数:

app.use(express.static('public'))
app.use(express.static('files'))

注意:访问静态资源文件时,express.static()函数会根据目录的添加顺序查找所需的文件

挂载路径前缀

如果希望在托管的静态资源访问路径之前,挂载路径前缀,则可以使用如下的方式:

app.use('/public',express.static('public'));

image.png

使用nodemon工具

在编写调试Node.js项目的时候,如果修改了项目的代码,则需要频繁的手动close掉,然后再重新启动 非常繁琐。
现在,可以使用nodermon这个工具,它能够监听项目文件的变动,当代码被修改后,nodemon 会自动帮我们重启项目,极大方便了开发和调试。
当基于Node.js编写了一个网站应用的时候,传统的方式,是运行node app.js 命令,来启动项目。这样做的坏处是:代码被修改之后,需要手动重启项目。
现在,我们可以将node 命令替换为nodemon命令,使用nodemon app.js 来启动项目。这样做的好处是:代码被修改之后,会被nodemon监听到,从而实现自动重启项目的效果。 心

  1. 安装npm i -g nodemon
  2. 命令nodemon app.js

express路由

在express中,路由指的是客户端的请求与服务器处理函数之间的映射关系

express中的路由分三个部分组成,分别是请求的类型、请求的url地址、处理函数,格式如下:

app.METHOD(PATH,HANDLER);

image.png

路由匹配的过程

每当一个请求到达服务器之后,需要先经过路由的匹配,只有匹配成功之后,才会调用对应的处理函数。 在匹配时,会按照路由的顺序进行匹配,如果请求类型和请求的URL同时匹配成功,则Express 会将这次请求,转交给对应的function函数进行处理。

image.png 注意

  1. 按照定义的先后顺序进行匹配
  2. 请求类型和请求的url同时匹配成功,才会调用对应的处理函数

模块化路由

为了方便对路由进行模块化的管理,express不建议将路由直接挂载到app上,而是推荐将路由抽离为单独的模块。步骤如下:

  1. 创建路由模块对应的.js文件
  2. 调用express.Router()函数创建路由对象
  3. 向路由对象上挂载具体的路由
  4. 使用module.exports向外共享路由对象
  5. 使用app.use()函数注册路由模块

创建路由模块

var express = require('express');
var router = express.Router();
router.get('/user/list',function(req,res){
    res.send('GET')
})
router.post('/user/add',function(req,res){
    res.send('POST')
})
module.export=router;

注册路由模块

//导入路由模块
const userRouter = require('./router/user.js')
//使用app.use()注册路由模块
app.use(userRouter)

为路由模块添加前缀

类似于托管静态资源时,为静态资源同意挂载访问前缀一样,路由模块添加前缀的方式也非常简单:

//导入路由模块
const userRouter = require('./router/user.js')
//使用app.use()注册路由模块,并添加统一的访问前缀/api
app.use('/api',userRouter);

中间件

express中间件调用流程

当一个请求到达express的服务器之后,可以连续调用多个中间件,从而对这次请求进行预处理

image.png

基本格式

express的中间件,本质上就是一个function处理函数,express中间件的格式如下:

image.png 注意:中间件函数的形参列表中,必须包含next参数。而路由处理函数中只包含req和res

next函数

next函数是实现多个中间件连续调用的关键,它表示把流转关系转交给下一个中间件或路由

image.png

中间件初体验

定义中间件函数

格式:

//常量mw所指向的,就是一个中间件函数
const mw = function(req,res,next){
    //注意:当前中间件的业务处理完毕后,必须调用next()函数
    //表示把流转关系转交给下一个中间件或路由
    next()
}

全局生效的中间件

客户端发起的任何请求,到达服务器之后,都会触发中间件,叫做全局生效的中间件。 通过调用app.use(中间件函数),即可定义一个全局生效的中间件。示例如下:

const mw = function(req,res,next){
    ...
    next()
}
//全局生效的中间件
app.use(mw)

简化形式

app.use(function(req,res,next){
    ...
    next()
})

中间件的作用

多个中间件之间,共享一份req和res。基于这样的特性,我们可以在上游中间件中,统一为req或res对象添加自定义的属性或方法,供下游的中间件或路由进行使用

image.png

定义多个全局中间件

可以使用app.use()连续定义多个全局中间件。客户端请求到达服务器之后,会按照中间件定义的先后顺序依次进行调用,示例如下:

app.use(function(req,res,next){//第一个全局中间件
    ...
    next()
})
app.use(function(req,res,next){//第二个全局中间件
    ...
    next()
})
app.get('/user',(req,res)=>{//请求这个路由,会依次触发上述两个全局中间件
    res.send('Home page')
})

局部生效的中间件

不使用app.use()定义的中间件,叫做局部生效的中间件,示例如下:

//定义中间件函数mw1
const mw1 = function(req,res,next){
    ...
    next()
}
//mw1这个中间件只在”当前路由中生效“,这种用法属于”局部生效的中间件“
app.get('/',mw1,function(req,res){
    res.send('User')
})
//mw1这个收纳柜简介不会影响下面这个路由
app.get('/user',function(req,res){
    res.send('User')
})

定义多个局部生效的中间件

可以在路由中通过如下两个等价的方式,使用多个局部中间件

app.get('/',mw1,mw2,function(req,res){
    res.send('User')
})
app.get('/',[mw1,mw2],function(req,res){
    res.send('User')
})

了解使用中间件的5个注意事项

  1. 一定要在路由之前注册中间件
  2. 客户端发送过来的请求,可以连续调用多个中间件进行处理
  3. 执行完中间件的业务代码之后,不要忘记调用next()函数
  4. 为了防止代码逻辑混乱,调用next()函数后不要再写额外的代码
  5. 连续调用多个中间件时,多个中间件之间,共享req和res对象

中间件分类

  • 应用级别的中间件
  • 路由级别的中间件
  • 错误级别的中间件
  • express内置的中间件
  • 第三方的中间件

应用级别的中间件

通过app.use()或app.get()或app.post(),绑定到app实例上的中间件,叫做应用级别的中间件,示例如下:

app.use(function(req,res,next){//第一个全局中间件
    ...
    next()
})
app.get('/',mw1,function(req,res){
    res.send('User')
})

路由级别的中间件

绑定到express.Router()实例上的中间件,叫做路由级别的中间件。它的用法和应用级别中间件没有任何区别。只不过,应用级别中间件时绑定到app实例上,路由级别中间件绑定到router实例上,代码示例如下:

var app = express();
var router = express.Router()
router.use(function(req,res,next){
    ...
    next()
})
app.use('/',router)

错误级别的中间件

错误级别的中间件的作用:专门用来捕获整个项目发生的异常错误,从而防止项目异常崩溃的问题
格式:错误级别中间件的function处理函数中,必须有4个形参,形参顺序从前到后,分别是(err,req,res,next)

app.get('/',function(req,res){
    throw new Error('服务器内部发生了错误!')//抛出一个自定义错误
    res.send('Home Page');
})
app.use(function(err,req,res,next){
    console.log('发生错误'+err.message)//在服务器打印错误信息
    res.send('Error'+err.message)//向客户端响应错误相关的内容
})

注意:错误级别中间件,必须注册在所有路由之后

express内置中间件

自express 4.16.0版本开始,express内置了三个常用的中间件,极大的提高了express项目的开发效率和体验

  1. express.static快速托管静态资源的内置中间件,例如:HTML,图片,css样式等(无兼容性问题)
  2. express.json解析json格式的请求体数据(有兼容性问题,仅在4.16.0+版本可用)
  3. express.urlencoded解析URL-encoded格式的请求体数据(有兼容性问题,仅在4.16.0+版本可用)
//配置解析application/json格式数据的内置中间件
app.use(express.json)
//配置解析application/x-www-form-urlencoded格式数据的内置中间件
app.use(express.urlencoded({extended: false}))

express.json()

const app = express();
app.use(express.json());
app.post('/user',(req,res)=>{
    //在服务器。可以使用req.body这个属性,来接收客户端发送过来的请求体数据
    //默认情况下,如果不配置解析表单数据的中间件,则req.body默认等于undefined
    console.log(req.body)
    res.send('OK')
})

express.urlencoded()

const app = express();
app.use(express.urlencoded({extended: false}));
app.post('/user',(req,res)=>{
    
    console.log(req.body)
    res.send('OK')
})

第三方中间件

非express官方内置的,而是由第三方开发出来的中间件,叫做第三方中间件。在项目中,大家可以按需下载并配置第三方中间件,从而提高开发效率 例如:在express@4.16.0之前的版本中,经常使用body-parser这个第三方中间件,来解析请求体数据,步骤如下:

  1. 运行npm i body-parser安装中间件
  2. 使用require导入中间件
  3. 调用app.use注册并使用中间件

注意express内置的express.urlencoded中间件,就是基于body-parser这个第三方中间件进一步封装出来的

const parser = require('body-parser');
app.use(parser.urlencoded({extended: false}))
app.post('/user',(req,res)=>{
    console.log(req.body);
    res.send('ok');
})

自定义中间件

自己手动模拟一个类似于express.urlencoded这样的中间件,来解析POST提交到服务器的表单数据 实现步骤:

  1. 定义中间件
  2. 监听req的data事件

    在中间件中,需要监听req对象的data事件,来获取客户端发送到服务器的数据 如果数据量比较大,无法一次性发送完毕,则客户端会把数据切割后,分批发送到服务器。所以data事件可能会触发多次,每次触发data事件时,获取到数据只是完整数据的一部分,需要手动对接收到的数据进行拼接

  3. 监听req的end事件
  4. 使用querystring模块解析请求体数据
  5. 将解析出来的数据对象挂载为req.body
  6. 将自定义中间件封装成模块

text.js

const express = require("express")
const app = express();

//导入自己封装的中间件模块
const customBodyParser = require('./custom-body-parser')
//解析表单数据的中间件
app.use(customBodyParser)
app.post('/user',(req,res)=>{
      console.log(req.body);
      res.send(req.body);
})
app.listen(80,()=>{
      console.log(109);
})

custom-body-parser.js

const qs = require("querystring");
export default function bodyParser(req,res,next){
      //定义逻辑代码
      let str = '';
      //监听req对象的data事件(客户端发送过来的新的请求体数据)
      req.on('data',(chunk)=>{
            //拼接请求体数据,隐式转换为字符串
            str+=chunk
      })
      //监听req对象的end事件(请求体发送完毕后自动触发)
      req.on("end",()=>{
            // console.log(str);
            //调用qs.parse(),把字符串格式的请求体数据解析成对象模式
            const body = qs.parse(str);
            req.body = body;
            console.log(body);
            next();
      })
}

使用Express编写接口

创建基本的服务器

// 引入express模块
const express = require('express');
// 创建express的服务器实例
const app = express();

// 调用app.listen方法,指定端口号并启动web服务器
app.listen(8000, () => {
    console.log("已启动");
})

创建API路由模块

apiRouter.js

const express = require('express')
const apiRouter = express.Router();

//挂载对应路由

module.exports = apiRouter

web服务器文件

//app.js导入并注册路由模块
const apiRouter = require("./apiRouter.js");
app.use('/api',apiRouter)

编写GET接口

apiRouter.js

//挂载对应路由
apiRouter.get('/get',(req,res)=>{
      //获取到客户端通过查询字符串,发送到服务器的数据
      const query = req.query
      //调用res.send()方法,把数据响应给客户端
      res.send({
            status: 0,//状态,0表示成功,1表示失败
            msg: 'GET请求成功',//状态描述
            data: query//需要响应给客户端的具体数据
      })
})

编写POST接口

apiRouter.post('/post',(req,res)=>{
      //通过req.body获取请求体中包含的url-encoded格式的数据
      const body = req.body
      //调用res.send()方法,向客户端响应结果
      res.send({
            status: 0,
            msg: 'POST请求成功!',
            data: body
      })
})

解决跨域问题

CORS跨域资源共享

在使用之前先下载npm i cors

const cors = require("cors");
app.use(cors()); //使用cors中间件

CORS响应头部

Access-Control-Allow-Origin
响应头部可以携带一个Access-Control-Allow-Origin字段,其语法如下:
Access-Control-Allow-Origin:<origin>|*
其中origin参数的值指定了允许访问该资源的外域URL 例如,下面的字段值只允许来自 itcast.cn 的请求

res.setHeader('Access-Control-Allow-Origin','http://itcast.cn')

如果指定了Access-Control-Allow-Origin字段的值为通配符*,表示允许来自任何域的请求,示例代码如下:

res.setHeader('Access-Control-Allow-Origin','*')

Access-Control-Allow-Headers
默认情况下,CORS仅支持客服端向服务器发送如下9个请求头: Accept、Accept-Language、Content-Language、DPR、Downlink、Save-Data、Viewport-Width、Width、Content-Type(值仅限于text/plain、 multipart/form-data、application/x-www-form-urlencoded三者之一)
如果客户端向服务器发送了额外的请求头信息,则需要在服务器端,通过Access-Control-Allow-Headers对额外的请求头进行声明,否则这次请求会失败

//允许客户端额外向服务器发送Content-Type请求头和X-Custom-Header请求头
//注意:多个请求头之间使用英文的逗号进行分割
res.setHeader('Access-Control-Allow-Headers','Content-Type','X-Custom-Header');

Access-Control-Allow-Methods 默认情况下,CORS仅支持客户端发起GET、POST、HEAD请求
如果客户端希望通过PUT、DELETE等方式请求服务器的资源,则需要在服务器端,通过Access-Control-Allow-Methods来指明实际请求所允许使用的HTTP方法

//只允许POST、GET、DELETE、HEAD请求方法
res.setHeader('Access-Control-Allow-Methods','POST,GET,DELETE,HEAD')
//允许所有的HTTP请求方法
res.setHeader('Access-Control-Allow-Methods','*')

CORS请求分类

客户端在请求CORS接口时,根据请求方式和请求头的不同,可以将CORS的请求分为两个大类,分别是 简单请求预检请求


简单请求 同时满足以下两个条件的请求,就属于简单请求:

  1. 请求方式:GET、POST、HEAD三者之一
  2. HTTP头部信息不超过以下几种字段:无自定义头部字段、Accept、Accept-Language、Content-Language、DPR、Downlink、Save-Data、Viewport-Width、Width、Content-Type(值仅限于text/plain、 multipart/form-data、application/x-www-form-urlencoded三者之一)

预检请求 只要符合以下任何一个条件的请求,都需要进行预检请求:

  1. 请求方式为GET、POST、HEAD之外的请求Method类型
  2. 请求头包含自定义头部字段
  3. 向服务器发送了application/json格式的数据

在浏览器与服务器正式通信之前,浏览器会先发送Option请求进行预检,以获知服务器是否允许该实际请求,所以这一次的option请求称为“预检请求”。服务器成功响应预检请求后,才会发送真正的请求,并且携带真实数据


简单请求和预检请求的区别

  1. 简单请求的特点:客户端与服务器之间只会发生一次请求
  2. 预检请求的特点:客户端与服务器之间会发生两次请求,OPTION预检请求成功之后,才会发起真正的请求

编写JSONP接口

概念:浏览器端通过<script>标签的src属性,请求服务器上的数据,同时,服务器返回一个函数的调用。这种的方式叫做JSONP
特点

  1. JSONP不属于真正的Ajax请求,因为它没有使用XMLHttpRequest这个对象
  2. JSONP仅支持GET请求,不支持POST、PUT、DELETE等请求

创建JSONP接口的注意事项 如果项目中已经配置了CORS跨域资源共享,为了防止冲突必须在配置CORS中间件之前声明JSONP的接口。否则JSONP接口会被处理成开启了CORS的接口。

//优先创建JSONP接口【这个接口不会被处理成CORS接口】
app.get('/api/jsonp'.(req,res)=>{
...
})
//再配置CORS中间件【后续的所有的接口,都会被处理成CORS接口】
app.use(cors())
//这样一个开启了CORS的接口
app.get('/api/get',(req,res)=>{
...
})

实现JSONP接口的步骤

  1. 获取客户端发送过来的回调函数的名字
  2. 得到要通过JSONP形式发送给客户端的数据
  3. 根据前两步得到的数据,拼接出一个函数调用的字符串
  4. 把上一步拼接得到的字符串,响应给客户端<script>标签进行解析执行 后端代码
app.get('/api/jsonp',(req,res)=>{
    //1. 获取客户端发送过来的回调函数的名字
    const funcName = req.query.callback;
    //2. 得到要通过JSONP形式发送给客户端的数据
    const data = {name: 'zs',age :22}
    //3. 根据前两步得到的数据,拼接出一个函数调用的字符串
    const scriptStr = `${funcName}(${JSON.stringify(data)})`
    //4. 把上一步拼接得到的字符串,响应给客户端`<script>`标签进行解析执行
    res.send(scriptStr)
})

前端jquery代码

$('#jsonp').on('click', function () {
    $.ajax({
        type: 'get',
        url: 'http://127.0.0.1/api/jsonp',
        dataType: 'jsonp',//表示发起jsonp请求
        success: function (res) {
           console.log(res);
        }
   })
})

在express中操作MySOL

  1. 安装操作MySQL数据库的第三方模块:npm i mysql
  2. 通过MySQL模块连接到MySQL数据库
  3. 通过MySQL模块执行SQL语句

配置mysql 在使用MySQL模块操作MySQL数据库之前,必须先对mysql模块进行必要的配置:

//导入mysql模块
const mysql = require('mysql')
//建立与mysql数据库的连接
const db = mysql.createPool({
      host: '127.0.0.1',//数据库的IP地址
      user: 'root',//登录数据库的账号
      password: 'ling',//登录数据库的密码
      database: ''//指定要操作哪个数据库
})

错误 Client does not support authentication protocol requested by server; consider upgrading MySQL client 解决博客


查询语句

db.query('select * from tags',(err,results)=>{
      if(err) return console.log(err.message);
      console.log(results);
})

插入语句

const user = {username: 'tom',password: '123'};
const sqlStr = 'INSERT INTO users (username,password) VALUES (?,?)'
db.query(sqlStr,[user.username,user.password],(err.results)=>{
    if(err) return console.log(err.message);
    //如果执行的是insert into 插入语句,则results是一个对象
    //可以通过affectedRows的属性,来判断是否插入成功
    if(results.affectedRows === 1){
        console.log('插入成功')
    }
})

便捷插入

const user = {username: 'tom',password: '123'};
const sqlStr = 'INSERT INTO users SET ?'
db.query(sqlStr,user,(err.results)=>{
    if(err) return console.log(err.message);
    //如果执行的是insert into 插入语句,则results是一个对象
    //可以通过affectedRows的属性,来判断是否插入成功
    if(results.affectedRows === 1){
        console.log('插入成功')
    }
})

更新语句

const user = {id: 6,username: 'tom',password: '123'};
const sqlStr = 'update users set username=?,password=? where id=?'
db.query(sqlStr,[user.username,user.password,user.id],(err.results)=>{
    if(err) return console.log(err.message);
    //如果执行的是insert into 插入语句,则results是一个对象
    //可以通过affectedRows的属性,来判断是否插入成功
    if(results.affectedRows === 1){
        console.log('更新成功')
    }
})

便捷更新

const user = {id: 6,username: 'tom',password: '123'};
const sqlStr = 'update users set ? where id=?'
db.query(sqlStr,[user,user.id],(err.results)=>{
    if(err) return console.log(err.message);
    //如果执行的是insert into 插入语句,则results是一个对象
    //可以通过affectedRows的属性,来判断是否插入成功
    if(results.affectedRows === 1){
        console.log('更新成功')
    }
})

删除语句

const sqlStr = 'delete from users where id=?'
//如果sql语句中有多个占位符,则必须使用数组为每个占位符指定具体的值
//如果sql语句中只有一个占位符,则可以省略数祖
db.query(sqlStr,8,(err.results)=>{
    if(err) return console.log(err.message);
    //如果执行的是insert into 插入语句,则results是一个对象
    //可以通过affectedRows的属性,来判断是否插入成功
    if(results.affectedRows === 1){
        console.log('更新成功')
    }
})

在express中使用session

web开发模式

  1. 基于服务端渲染的传统web开发模式
  2. 基于前后端分离的新型web开发模式

服务端渲染

服务器发送给客户端的HTML页面,是在服务器通过字符串的拼接,动态生成的。因此,客户端不需要使用Ajax这样的技术额外请求页面的数据

优点:

  1. 前端耗时少。因为服务器负责动态生成HTML内容,浏览器只需要直接渲染页面即可。尤其是移动端,更省电
  2. 有利于SEO。因为服务器端响应的是完整的HTML页面内容,所以爬虫更容易获取信息,更有利于SEO

缺点:

  1. 占用服务器端资源。即服务器端完成HTML页面内容的拼接,如果请求较多,会对服务器造成一定的访问压力
  2. 不利于前后端分离,开发效率低。使用服务器端渲染,则无法进行分工合作,尤其对于前端复杂度高的项目,不利于项目高效的开发

前后端分离

前后端分离的开发模式,依赖于Ajax技术的广泛应用。简而言之,前后端分离的web开发模式,就是后端只负责提供api接口,前端使用Ajax调用接口的开发模式

优点:

  1. 开发体验好。前端专注于UI页面的开发,后端专注于api的开发,且前端有更多的选择性
  2. 用户体验好。Ajax技术广泛应用,极大的提高了用户的体验,可以轻松实现页面的局部刷新
  3. 减轻了服务器端的渲染压力。因为页面最终是在每个用户的浏览器生成

缺点:

  1. 不利于SEO。因为完整的HTML页面需要在客户端动态拼接完成,所以爬虫对无法爬取页面的有效信息。(解决方案,利用Vue,React等前端框架的SSR技术能够很好的解决SEO问题)

不同开发模式下的身份认证

对于服务端渲染和前后端分离这两种开发模式:

  1. 服务端渲染推荐使用Session认证机制
  2. 前后端分离推荐使用JWT认证机制

Session认证机制

什么是Cookie

Cookie是存储在用户浏览器中一段不超过4kB的字符串。它由一个key和一value和其他几个用于控制Cookie有效期、安全性、使用范围的可选属性组成 不同域名下的cookie各自独立,每当客户端发起请求时,会自动把当前域名下所有未过期的cookie一同发送到服务器

cookie的几大特性:

  1. 自动发送
  2. 域名独立
  3. 过期限制
  4. 4kB限制

session的工作原理

image.png

在express中使用express-session

  1. 安装express-session中间件:npm i express-session
  2. 配置express-session中间件
//导入session中间件
var session = require('express-session');
//配置Session中间件
app.use(session({
    secret: 'xxx',//secret属性的值可以为任意字符串
    resave: false,//固定写法
    saveUninitialized: true//固定写法
}))
  1. session中存数据,首次登录
app.get('/api/login',(req,res)=>{
    if(!req.body.username!=='admin'||req.body.password!=='00000'){
      return res.send({status: 1,msg: '登录失败'})
    }
    req.session.user = req.body;
    req.session.islogin= true;
    res.send({status: 0,msg: 'success'})
})
  1. session中取数据
app.get('/api/username',(req,res)=>{
    if(!req.session.islogin){
      return res.send({status: 1,msg: 'fail'})
    }
    res.send({status: 0,msg: 'success',username: req.session.user.username})
})
  1. 清空session
app.post('/api/logout',(req,res)=>{
    req.session.destroy();
    res.send({
        status: 0,
        msg: '退出登录成功'
    })
})

JWT认证机制

session认证的局限性

session认证机制需要配合Cookie才能实现,由于Cookie默认不支持跨域访问。所以当涉及到前端跨域请求后端接口的时候,需要做许多额外的配置,才能实现跨域Session认证

JWT工作原理

image.png

JWT的组成部分

Header头部、Payload有效荷载、Signature签名。三者之间使用英文’.‘分隔,示例如下:

image.png

  1. Payload部分才是真正的用户信息,它是用户信息经过加密之后生成的字符串
  2. Header和Signature是安全性相关的部分,只是为了保证Token的安全性

image.png

JWT的使用方式

客户端到服务器返回的JWT之后,通常会将它存储到localStorage或sessionStorage 此后,客户端每次与服务器通信,都要带上这个JWT的字符串,从而进行身份认证,推荐的做法是把JWT放在HTTP请求头Authorization字段中

在express中使用

  1. 安装npm i jsonwebtoken express-jwt jsonwebtoken: 用于生成JWT字符串 express-jwt: 用于将jwt字符串解析还原成json对象
  2. 定义secret密钥
  3. 登录成功后生成JWT字符串 调用jsonwebtoken包提供的sign()方法,将用户的信息加密成JWT字符串,响应给客户端:
app.post('/api/login',(req,res)=>{
    res.send({
        status: 200,
        msg: '登陆成功'token: jwt.sign({username: userinfo.username},secretKey,{expiresIn:'30s'})
    })
})