一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第1天,点击查看活动详情。
最近开始研究nodejs后端的一些知识点,学到JWT用户身份认证技术,来做一个笔记总结。
一、跨域认证的问题
我们知道互联网服务离不开用户认证,传统的认证方式是基于session和cookie的,一般的流程是下面这样。
- 客户端(浏览器)向服务器发送用户名和密码。
- 服务器验证通过后,在当前会话(session)里面保存相关数据,比如用户的基本信息等等。
- 服务器向客户端返回一个session_id,并写入到Cookie中。
- 随后客户端每次向服务器发送请求,都会携带Cookie也就是保存的session_id发送给服务器。
- 服务器再根据客户端发送过来的session_id,找到之前在服务端创建的session对象(session没有或者过期了就重新创建一个呗),由此识别用户的身份(session其实就是保留在服务端的一个用来存储用户相关信息的容器对象,实际开发中可以用缓存数据库redis做一层cache,这里就不展开说了)。
但是这种模式的问题在于,扩展性不太好。单机当然没有问题,如果是服务器集群或者是跨域的服务导向架构,就要求session数据共享,每台服务器都能够读取session(这也是为什么在开发日常项目的时候服务端要做一层缓存的原因)。
举个🌰来说吧,A网站和B网站是同一家公司的关联服务,现在leader给你提个需求,要求用户只要在其中一个网站登录,再访问另一个网站就会自动登录,请问应该怎么实现呢?
一种解决方案是session数据持久化,写入到数据库级别的持久层,各种服务收到请求后,都向持久层请求数据。这种方案的优点是架构清晰,缺点是工程量比较大,另外玩意持久层挂掉了,就会导致单点失效问题。
另一种方案是服务器索性不保存session数据了,所有数据保存在客户端,每次请求再携带这些数据返回给服务器,咱们接下来要说的JWT技术就是这种方案的典型代表。
二、JWT原理
JSON Web Token(JWT)是目前最流行的跨域认证解决方案。
JWT的原理是,服务器认证以后,生成一个JSON对象,发回给用户,就像下面这样。
{
"userName":"koto",
"password":"xxxxxx",
"expire-time":"YYYY-mm-ss"
}
以后,客户端与服务端通信的时候都要发送回这个JSON对象。服务端完全只靠这个对象认证用户身份。为了防止用户篡改数据,服务器在生成这个对象的时候,会加上一个签名(详见后文)。
这样服务器就不用再保存任何session数据了,也就是说服务器变成无状态了,从而更容易实现扩展。
三、JWT的数据结构
推荐一个JWT的解析网站 JSON Web Tokens
实际的JWT大概就是下面这个样子。
一个又臭又长的字符串,中间用.分割成三个部分。注意JWT内部是没有换行的,这里只是为了便于展示,将它写成了三行。
JWT的三个部分依次如下:
- Header(头部)
- Payload(负载)
- Signature(签名)
写成一行就是下面这个样子
Header.Payload.Signature
Header
Header部分是一个JSON对象,描述JWT的元数据,通常是下面这样:
{
"alg":"HS256",
"typ":"JWT"
}
alg代表签名的算法(algorithm),默认是HMAC SHA256(写成HS256);typ代表这个令牌(token)的类型,JWT令牌统一写为JWT。
最后将上面的JSON对象使用Base64URL算法转换为字符串(咱没必要研究这个算法所以不做讨论)。
Payload
Payload部分也是一个JSON对象,用来存放实际需要传递的数据。JWT规定了7个官方字段,供选用。
- iss(issuer): 签发人
- exp(expiration time): 过期时间
- sub(subject): 主题
- aud(audience): 受众
- nbf(Not Before): 生效时间
- iat(Issued At): 签发时间
- iti(JWT ID): 编号 除了官方字段,我们还可以在这个部分定义私有字段,下面是一个例子。
{
"sub":"123",
"name":"koto",
"admin":true
}
注意JWT默认不是加密的,任何人都可以读到,所以不要把秘密信息放在这个部分。
这个JSON对象也要使用Base64URL算法转成字符串。
Signature
Signature部分是对前面两部分的签名,防止数据被篡改。 首先,需要指定一个密钥(secretKey),这个密钥只有服务器才知道,不能泄露给用户,然后使用Header里指定的签名算法( 前面讲过默认是HMAC SHA256),按照下面的公式产生签名。
HAMCSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secretKey
)
算出签名后,把header、Payload、Signature三个部分拼成一个字符串,每个部分用.分隔,就可以返回给用户。
在JWT中,消息体是透明的,使用签名可以保证消息不被篡改,但是不能实现数据加密功能。 也就是说,之前看到的这张图其实是没有加密过的,依然是能够被解析出来的,尽管它是一长串又臭又长的字符串,感觉像是加密过一样。严格来讲它只是个编码内容,是经过编码转换得来的,完全可以通过编码再还原回真实的数据。
为了提高安全性,建议服务端再做一层加密,传到客户端,客户端发起请求服务端再解密还原成真实的JWT,再经过编码解析出真实的数据,就是这样一层层的抽丝剥茧。
四、JWT的使用方式
客户端收到服务端返回的JWT,可以存储在Cookie里,也可以存储在LocalStorage。
此后,客户端每次与服务器通信,都要带上这个JWT。你可以把它放在Cookie里面自动发送,但是由于Cookie本身存在跨域不共享的限制,所以更好的方法是放在Http请求头信息的Authorization字段里。格式如下:
Authorization: Bearer <token>
另一种做法是,跨域的时候,JWT就放在POST请求的数据体里面。
五、JWT的几个特点
下面总结一下JWT的几个特点:
(1)JWT默认是不加密的,但也是可以加密的。生成原始的token建议使用密钥再加密一次提高安全性。
(2)JWT不仅可以用于认证,也可以用户交换信息。有效使用JWT可以降低服务器查询数据库的次数。
(3)JWT最大的缺点是,由于服务器不保存session状态,因此无法在使用过程中废止某个token,或者更改token的权限,也就是说一旦JWT签发了,在到期之前就会始终有效,除非服务器内部部署了额外的逻辑。这一点和session就不一样,session对象完全由服务端掌控,生杀大权都在服务器爸爸手里。
(4)JWT本身包含了认证信息,一旦泄露,任何人都可以获得该令牌的所有权限。为了减少盗用,JWT的有效期应该设置得比较短,对于一些比较重要的权限,使用时应该再次对用户进行认证。
(5)为了减少盗用,JWT不应该使用HTTP协议明文传输,要使用HTTPS协议传输。
六、结束语
好了,JWT的身份认证之认知篇就讲到这里啦,这是我参与「掘金日新计划 · 4 月更文挑战」的第1天,如果对您有帮助请给我留下一个小小的赞吧ღ( ´・ᴗ・` )比心,愿与诸君共勉~
下一篇,我将介绍在nodejs的web开发框架Express、Koa中实操两波基于JWT的登录身份校验。