目前主流的Web开发模式有两种,分别是:
服务端渲染
的传统 Web 开发模式前后端分离
的新型 Web 开发模式服务器发送给客户端的HTML页面
,是在服务器
通过字符串的拼接,动态生成
的。因此,客户端不需要
使用 ajax
这样的技术额外请求页面的数据
代码示例:
app.get('/index.html',(req,res) => {
// 1. 要渲染的数据
const user = { name: 'AIpoem', age: 20 }
// 2. 服务器端通过字符串的拼接,动态生成HTML内容
const html = `姓名:
${user.name},年龄:${user.age}`
// 3.生成的HTML页面响应给客户端
// 客户端拿到的是带有真实数据的HTML页面
res.send(html)
})
优点:
前端耗时少
。因为服务器端负责动态生成HTML页面,浏览器只需要直接渲染页面即可有利于SEO
。因为服务器端响应的是完整的HTML页面,所以爬虫更容易爬取获得信息,更有利于SEO缺点:
占用服务器端资源
。即服务器端完成HTML页面内容的拼接,如果请求较多,会对服务器造成一定的访问压力不利于前后端分离
,开发效率低。使用服务器端渲染,则无法进行分工工作。对于前端复杂度高的项目,不利于项目高效开发。前后端分离的Web开发模式就是后端
只负责提供API接口
,前端
使用Ajax调用接口
的开发模式
优点:
开发体验好
。前端专注于UI页面开发,后端专注于api的开发用户体验好
。ajax技术实现页面的局部刷新,极大提高用户的体验减轻了服务器端的渲染压力
。因为页面最终是在每个用户的浏览器中生成的缺点:
不利于SEO
。因为完整的HTML页面需要在客户端动态拼接完成,所以爬虫无法爬取页面的有效信息需要考虑业务场景
。
具体使用哪种开发模式并不绝对,为了同时兼顾首页的渲染速度
和前后端分离的开发效率
,一些网站采用了首屏服务器端渲染
+其他页面前后端分离
的开发模式
身份认证
又称鉴权
,是指通过一定的手段,完成对用户身份的确认。
对于服务端渲染和前后端分离这两种开发模式来说,分别有着不同的身份认证方案:
Session认证机制
JWT认证机制
HTTP协议的无状态性,指的是客户端
的每次HTTP请求
都是独立
的,连续多个请求之间没有直接的关系,服务器
不会主动保留
每次HTTP请求的状态
好比超市收银员和多个客户之间的关系,如果用户不携带会员卡,收银员是无法甄别用户是否是vip用户的
会员卡身份认证方式,在web开发模式中就叫做Cookie
当用户首次登录成功之后,服务器就会向客户端分发一个Cookie
,Cookie
就是身份认证的标识,今后只要再次请求
服务器,必须将Cookie
发送给服务器
,进行客户端的身份认证
Cookie
是存储在用户浏览器
中的一段不超过 4KB 的字符串
它由一个名称
(Name)、一个值
(Value)和其他几个用于控制Cookie的有效期
、安全性
、使用范围
的可选属性
组成。
不同域名
下的Cookie
各自独立。每当客户端
发起请求时,会自动
把当前域名
下所有未过期的Cookie
一同发送到服务器
Cookie的几大特性:
客户端第一次请求服务器
的时候,服务器
通过响应头
的形式,向客户端
发送一个身份认证的Cookie
,客户端会自动
将Cookie
保存在浏览器
中
随后,当浏览器每次请求服务器
的时候,浏览器会自动
将身份认证相关的Cookie
,通过请求头
的形式发送给服务器
,服务器即可验明客户端的身份
由于Cookie是存储在浏览器中的,而且浏览器也提供了读写Cookie的API,因此Cookie很容易被伪造,不具有安全性。
⚠️:千万不要使用Cookie存储重要且隐私的数据!如用户的身份信息、密码等
用户的敏感信息都是保存在服务器,而客户端只保存Cookie,这样能保证信息的安全性
npm install express-session
express-session 中间件安装完成后,需要通过app.use()
来注册session
中间件
// 1. 导入session中间件
const session = require('express-session')
// 2. 配置session中间件
app.use(session({
secret: 'AIpoem', // secret属性的值可以为任意字符串
resave: false, // 固定写法
saveUninitialized: true // 固定写法
}))
当express-session
中间件配置成功后,即可通过req.session
来访问和使用session
对象,从而存储用户的关键信息:
// 当用户调用了登录接口
app.post('/api/login',(req,res) => {
// 判断用户提交的登录信息是否正确
if(req.body.username !== 'admin' || req.body.password !== '000000') {
return res.send({ status: 1, msg: '登录失败'})
}
req.session.user = req.body // 将用户的信息,存储到Session中
req.session.isLogin = true // 将用户的登录状态,存储到Session中
req.send({ status: 0, msg: '登录成功' })
})
可以直接从req.session
对象上获取之前存储的数据
// 获取用户姓名的接口
app.get('/api/username', (req,res) => {
// 判断用户是否登录
if (!req.session.isLogin) {
return res.send({ status: 1, msg: '未登录' })
}
res.send({ status: 0, msg: '已登录', username: req.session.username })
})
调用req.session.destroy()
函数,即可清空服务器保存的session信息
// 退出登录的接口
app.post('/api/logout', (req,res) => {
// 清空当前客户端对应的 session 信息
req.session.destroy()
res.send({ status: 0, msg: '退出登录成功' })
})
Session认证机制
需要配合Cookie
才能实现。由于Cookie
默认不支持跨域访问。所以当涉及到前端跨域请求后端接口
的时候,需要做很多额外的配置,才能实现跨域 Session 认证
⚠️:
不存在跨域问题
的时候,推荐使用Session认证机制
存在跨域问题
的时候,推荐使用JWT认证机制
JWT:JSON Web Token
用户的敏感信息通过Token字符串的形式,保存在浏览器中。服务器通过还原Token字符串的形式来认证用户身份
JWT通常由三部分组成,分别是Header
(头部)、Payload
(有效荷载)、Signature
(签名)
三者之间使用英文的.
分隔。格式如下:
Header.Payload.Signature
Header
和 Signature
是安全性相关的部分,这是为了保证 Token
的安全性Payload
部分是真正的用户信息
,它是用户信息经过加密
之后生成的字符串客户端收到服务器返回的JWT之后,通常会将它存储在localStorage
或sessionStorage
中
此后,客户端每次向服务器发送请求,都要带上这个JWT
推荐做法是将JWT放在HTTP请求头
的Authorization
字段中:
Authorization: Bearer token
安装jsonwebtoken
和express-jwt
npm install jsonwebtoken express-jwt
jsonwebtoken
用于生成JWT字符串
express-jwt
用于将JWT字符串
解析还原
成JSON对象
使用require()
函数,分别导入
const jwt = require('jsonwebtoken')
const expressJWT = require('express-jwt')
为了保证JWT字符串的安全性
,防止JWT字符串在网络传输过程中被别人破解,我们需要专门定义一个用于加密
和解密
的secret密钥:
生成JWT字符串
的时候,需要使用secret密钥
对用户的信息进行加密
,最终得到加密好的JWT字符串解析还原
成JSON对象
的时候,需要使用secret密钥
进行解密
这个密钥自定义就行:
const secretKey = 'AIpoem zhendehenbucuo ^-^'
在登录成功后要生成JWT字符串,调用jsonwebtoken
包提供的sign()
方法,将用户的信息加密成JWT字符串
,响应给客户端:
// 当用户调用了登录接口
app.post('/api/login',(req,res) => {
// 判断用户提交的登录信息是否正确
if(req.body.username !== 'admin' || req.body.password !== '000000') {
return res.send({ status: 1, msg: '登录失败'})
}
// 用户登录成功
res.send({
status: 200,
msg: '登录成功',
// 调用jwt.sign()生成JWT字符串,三个参数分别是:用户信息对象、加密密钥、配置对象(配置当前token的有效期)
token: jwt.sign({ username: req.body.username }, secretKey, { expiresIn: '10h' })
})
})
客户端每次在访问那些有权限接口的时候,都需要主动通过请求头
中的Authorization
字段,将Token
字符串发送到服务器进行身份认证
此时,服务器可以通过express-jwt
这个中间件,自动将客户端发送过来的Token
解析还原成JSON对象
// 使用app.use()来注册中间件
// expressJWT({ secret: secretKey, algorithms: ['HS256'] })就是来解析Token的中间件
// .unless({ path: [/^\/api\//] })用来指定哪些接口不需要访问权限,这里是凡是以/api为开头的接口都不需要访问权限
app.use(expressJWT({ secret: secretKey, algorithms: ['HS256'] }).unless({ path: [/^\/api\//] }))
当express-jwt
中间件配置成功后,即可在有权限的接口
中,使用 req.user
对象,来访问从JWT字符串中解析出来的用户信息
// 这是一个有权限的api接口
app.get('/admin/getinfo',(req,res) => {
res.send({
status: 200,
msg: '获取用户信息成功!',
data: req.user
})
})
这里解析出来的用户信息是前面我们使用jwt.sign()
传的第一个参数
下面演示一下:
用postman请求登录接口,成功返回了token
请求获取用户信息的接口,将刚刚得到的token添加到请求头中一起发送,成功获取到了用户信息
当使用express-jwt
解析Token
字符串时,如果客户端发送过来的Token
字符串过期或不合法
,会产生一个解析失败的错误,影响项目的正常运行。
我们可以通过Express的全局错误处理中间件
,捕获这个错误并进行相关处理
app.use((err,req,res,next) => {
// token解析失败导致的错误
if(err.name === 'UnauthorizedError') {
return res.send({ status: 401, msg: '无效的token'})
}
// 其它原因导致的错误
res.send({ status: 500, msg: '未知错误' })
})
演示:
没有配置全局错误处理中间件时,这里用postman发一个错误的token:
配置了全局错误处理中间件之后,再次发送请求:
完