(2000字)小白也能看懂的 Nodejs 之 基于JWT的身份认证(认知篇)

1,222 阅读7分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第1天,点击查看活动详情

最近开始研究nodejs后端的一些知识点,学到JWT用户身份认证技术,来做一个笔记总结。

一、跨域认证的问题

我们知道互联网服务离不开用户认证,传统的认证方式是基于session和cookie的,一般的流程是下面这样。

  1. 客户端(浏览器)向服务器发送用户名和密码。
  2. 服务器验证通过后,在当前会话(session)里面保存相关数据,比如用户的基本信息等等。
  3. 服务器向客户端返回一个session_id,并写入到Cookie中。
  4. 随后客户端每次向服务器发送请求,都会携带Cookie也就是保存的session_id发送给服务器。
  5. 服务器再根据客户端发送过来的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大概就是下面这个样子。

image.png 一个又臭又长的字符串,中间用.分割成三个部分。注意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中,消息体是透明的,使用签名可以保证消息不被篡改,但是不能实现数据加密功能。 也就是说,之前看到的这张图其实是没有加密过的,依然是能够被解析出来的,尽管它是一长串又臭又长的字符串,感觉像是加密过一样。严格来讲它只是个编码内容,是经过编码转换得来的,完全可以通过编码再还原回真实的数据。

image.png 为了提高安全性,建议服务端再做一层加密,传到客户端,客户端发起请求服务端再解密还原成真实的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的登录身份校验。