Web身份认证——【 JWT 认证】
学习目标:
一、session 认证的局限性
在上一期中,大家一起学习了 Cookie认证、Session认证。我们知Session认证(存储在服务器的内存中) 要比Cookie认证(存储在客户端浏览器中) 更为安全,但是Session认证 需要配合Cookie才能实现,比如我们在配置session中间件的时候,要设置Cookie的name,maxAge……等属性,并在session存储的时候生成对应的Cookie字符串,并响应给客户端。
由于Cookie默认不支持跨域访问,所以当涉及到前端跨域请求后端接口的时候,需要 做很多额外配置,才能实现session跨域认证。所以为了解决跨域认证问题,我们不得不使用JWT认证。
二、JWT认证
1、什么是 JWT认证
JWT(英文全称JSON Web Token)是目前最流行的跨域认证解决方案。它是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519).该token被设计为紧凑且安全的,特别适用于分布式站点的单点登录(SSO)场景。JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,该token也可直接被用于认证,也可被加密。
翻译成人话就是:Jwt是一种认证方式,可以生成一段字符串,用来进行用户身份认证的的凭证,而这段字符串叫 Token(译为:令牌),并且可以对这这段字符串进行加密……等工作。
2、JWT的特点
- 体积小,因而传输速度快
- 传输方式多样,可以通过URL/POST参数/HTTP头部等方式传输
- 严格的结构化。它自身(在payload中)就包含了所有于用户相关的验证信息,如用户可访问路由、访问有效期……等信息,服务器无需再去连接数据库验证信息的有效性,并且
payload
支持位你的应用而定制化 - 支持跨域验证,可以应用于单点登录
3、JWT的组成
JWT是由Auth0
提出的通过json
进行加密签名来实现授权验证的方案,编码之后的JWT被称为token字符串或者token令牌看起来就像一堆乱码,一般人很难破解。比如eyJ0eXAiOiJKV1QiLCJhbGciOiJIQTI1NiJ9.eyJyb2xlIjpbeyJyb2xlSWQiOjEsInVzZXJuYW1lIjoiemhhb3poYW96aGFvMiJ9XX0=.26d6c858e666ee510eb14640689e1bb121
。
细心的人估计能看出来这段乱码由两个.
分成了三段,那么这三段数据牵扯到了JWT的组成。
JWT的组成:Header.payload.Signature
。翻译成人话就是JWT是由头部、有效荷载、签名三部分组成
,并且由两个点隔开来区分,那么每一段都是一些什么数据呢?我们来看一下:
-
头部(Header):第一段为头部,主要保存加密算法等头部信息,其内容本质为一段经过
base64
编码的json对象。 base64编码具体是什么我还不怎么清除,我目前只知道base64编码可以将图片进行编码,直接存储在数据库中,通过数据库中的base64编码可以在前端中的img标签中使用src属性直接展示图片。
-
有效荷载(payload):第二段为有效荷载,主要保存服务器端自定义的用户数据,如用户ID、账号、昵称……等一些无关紧要的东西,不过放心,肯定不会存用户的隐私,比如说密码、身份证……等。
简单来说,第二段—有效荷载(payload)才真正存储的是用户的数据,它是用户信息经过加密之后生成的字符串。
-
签名(signature):第三段为签名,为一段加密后的字段,服务器在接受到客户端头部(Header)中的jwt后便通过与这段字段的对比可以得知头部和有效荷载是否为服务器提供,是否被修改。
-
总结:Header和Signature是安全性相关的部分,只是为了保证用户数据的安全性。
4、jwt的工作原理
从上图中,我们可以很清除的看到 jwt 认证的工作机制其实和 cookie 认证的机制有些类似,但是还是有一定的区别的,它的步骤如下:
- 在客户端调用登录路由的时候,jwt不会认证,而是在登录成功之后获取用户的相关数据对象,并将数据对象进行加密,生成token令牌或token字符串,并响应给客户端。
- 客户端浏览器不会自动帮你存储token字符串,所以在通过ajax调用登录接口的时候,登录成功之后,会返回一个token令牌,然后我们要将token令牌存储到本地,可以使用
localStorage
和sessionStorage
两种本地存储技术来存储。 - 在客户端再次发起请求,调用其他接口的时候,就要将本地存储中的token令牌拿出来,通过
Authorization请求头
发送给服务器。 - 服务器通过一个中间件将请求头中的token令牌还原成用户的数据对象,并存储在
request(请求对象)
中的user属性中。 - 服务器通过将user属性对象中的属性点出来,或者直接判断user是否为空来对用户做身份认证
- 如果身份认证成功,则响应相应的数据给客户端浏览器,否则相应认证失败的信息
- 前端程序员调用这个接口,如果认证失败,则响应给客户弹框来提示用户登录,否则认证成功之后,将数据渲染到页面中。
简单来说jwt的认证顺序大概为:用户的信息通过token字符串的形式,保存在客户端浏览器的本地中,服务器通过还原token字符串的形式来认证用户的身份。
三、jwt在Node.js中的使用
1、导入生成token令牌的包
npm i jsonwebtoken
2、导入对token令牌进行解密的包
npm i express-jwt
3、导入这两个包
const jwt = require('jsonwebtoken')
const expressJWT = require('express-jwt')
4、定义secret签名(密钥)
为了保证用户数据的绝对安全,防止JWT字符串在网络传输过程中被别人破解,我们需要专门定义一个用于加密和解密的密钥。
- 当生成JWT字符串的时候,需要使用secret密钥对用户的数据进行二次加密(俗称加盐),最终得到加密好的token令牌
- 当把token令牌解析还原成json对象的时候,需要使用secret密钥进行解密
定义 secret 密钥,建议将密钥命名为 secretKey
const secretKey = 'inlett 0_0!'
5、配置jwt并生成token令牌
/*
参数1:用户的数据,对象形式
参数2:密钥,字符串形式
参数3:配置属性,对象形式
expiresIn为设置token令牌的有效时间,1h为一小时,1m为一分钟,1s为一秒
*/
const tokenStr = jwt.sign({ username: userinfo.username }, secretKey, { expiresIn: '30s' })
// 响应给客户端
res.send({
status: 200,
message: '登录成功!',
token: 'Bearer '+tokenStr, // 要发送给客户端的 token 字符串
})
6、解析还原token令牌
客户端每次在访问那些有权限的接口时,都需要主动通过请求头中的Authorizatoin字段,将token令牌发送到服务器中进行身份认证
此时,服务器可以通过express-jwt中间件,自动将客户端发送过来的token令牌解析还原成json对象
// 1.前端向服务器发送token令牌
$.ajax({
// 请求类型
type: "GET",
// 路由地址
url: "127.0.0.1:8080/infor/infor",
// 请求头参数
headers: {
// 通过Authorization字段向服务器发送localStory中存储的token令牌
Authorization:localStorage.getItem("loginToken")},
// 成功之后的处理函数
success: function(results) {
代码块...
});
// 2.使用express-jwt中间件解析token的时候要对其进行配置
app.use(expressJWT({
// [1]密钥
secret: secretKey,
// [2]解密的算法
algorithms: ["HS256"]
// [3]不需要进行身份认证的路由
}).unless({
// 主要:需要使用正则匹配
path: [
/^\/api\//,
...
]
}));
7、使用 req.user 获取用户信息
当express-jwt这个中间件配置成功之后,即可在那些有权限的接口中使用req.user
对象,来访问token令牌解析出来的用户信息
// 这是一个有权限的 API 接口
app.get('/admin/getinfo', function (req, res) {
// 使用 req.user 获取用户信息,并使用 data 属性将用户信息发送给客户端
console.log(req.user)
res.send({
status: 200,
message: '获取用户信息成功!',
data: req.user, // 要发送给客户端的用户信息
})
})
8、捕获解析 token 失败后产生的错误
当 express-jwt 中间件解析token令牌的时候,如果客户端发送过来的token令牌是过期的或者是不合法的,那么服务器将抛出一个解析token令牌失败的异常,那么这样就会影响整个项目的正常运行.
那么,我们可以通过写一个异常类型的全局中间件来捕获这个异常,并做出一定的处理.
// 使用全局错误处理中间件,捕获解析 JWT 失败后产生的错误
app.use((err, req, res, next) => {
// 这次错误是由 token 解析失败导致的
if (err.name === 'UnauthorizedError') {
return res.send({
status: 401,
message: '无效的token',
})
}
res.send({
status: 500,
message: '未知的错误',
})
})
四、总结
那么到今天我们已经学习了三种身份认证方式,即cookie、session、jwt。其主要分为session和jwt,那么我们需要知道我们为什么需要使用jwt?
Session认证机制需要配合Cookie才能实现 。由于Cookie默认不支持跨域访问,所以,当涉及到 前端跨域请求后端接口的时候, 需要做很多额外的配置 ,才能实现跨域 Session 认证。
那么有些人肯定还是不清除在什么情况需要使用哪种身份认证方式,别急,我给大家总结好了:
- 当前端请求后端接口 不存在跨域问题 的时候, 推荐使用 Session 身份认证机制。
wt?
Session认证机制需要配合Cookie才能实现 。由于Cookie默认不支持跨域访问,所以,当涉及到 前端跨域请求后端接口的时候, 需要做很多额外的配置 ,才能实现跨域 Session 认证。
那么有些人肯定还是不清除在什么情况需要使用哪种身份认证方式,别急,我给大家总结好了:
- 当前端请求后端接口 不存在跨域问题 的时候, 推荐使用 Session 身份认证机制。
- 当前端需要跨域请求后端接口的时候,不推荐使用 Session 身份认证机制,推荐使用 JWT 认证机制。